1/* 2 * Copyright 2013, winocm. <winocm@icloud.com> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without modification, 6 * are permitted provided that the following conditions are met: 7 * 8 * Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * Redistributions in binary form must reproduce the above copyright notice, this 12 * list of conditions and the following disclaimer in the documentation and/or 13 * other materials provided with the distribution. 14 * 15 * If you are going to use this software in any form that does not involve 16 * releasing the source to this project or improving it, let me know beforehand. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29/* 30 * Platform Expert for Samsung S5L8930X devices. 31 * 32 * Now includes S5L8920X and S5L8922X! 33 */ 34 35#if defined(BOARD_CONFIG_S5L8930X) || defined(BOARD_CONFIG_S5L8920X) || defined(BOARD_CONFIG_S5L8922X) 36 37#include <mach/mach_types.h> 38 39#include <IOKit/IOPlatformExpert.h> 40 41#include <pexpert/pexpert.h> 42#include <pexpert/arm/protos.h> 43#include <pexpert/arm/boot.h> 44 45#include <machine/machine_routines.h> 46 47#include <vm/pmap.h> 48#include <arm/pmap.h> 49 50/* 51 * This is board specific stuff. 52 */ 53#define KPRINTF_PREFIX "PE_SamsungS5L: " 54 55#include "pe_s5l8930x.h" 56 57#define HwReg(x) *((volatile unsigned long*)(x)) 58 59extern void rtclock_intr(arm_saved_state_t * regs); 60extern void rtc_configure(uint64_t hz); 61 62#define uart_base gS5L8930XUartBase 63vm_offset_t gS5L8930XUartBase; 64vm_offset_t gS5L8930XClockGateBase; 65 66vm_offset_t gS5L8930XPmgrBase; 67 68/* The 8930 has 4 PL192 compatible VICs. */ 69vm_offset_t gS5L8930XVic0Base; 70vm_offset_t gS5L8930XVic1Base; 71vm_offset_t gS5L8930XVic2Base; 72vm_offset_t gS5L8930XVic3Base; 73 74vm_offset_t gS5L8930XTimerBase; 75 76#ifdef BOARD_CONFIG_S5L8930X 77static boolean_t avoid_uarts = FALSE; 78#else 79/* Busted... */ 80static boolean_t avoid_uarts = TRUE; 81#endif 82 83uint64_t clock_decrementer = 0; 84static boolean_t clock_initialized = FALSE; 85static boolean_t clock_had_irq = FALSE; 86static uint64_t clock_absolute_time = 0; 87 88static void s5l8930x_clock_gate_switch(int gate, int state) 89{ 90 uint32_t __register; 91 92 assert(gS5L8930XClockGateBase); 93 94 if (gate > 0x3f) 95 return; 96 97 __register = CLK_REG_OFF + (gate << 2); 98 99#if defined(BOARD_CONFIG_S5L8920X) || defined(BOARD_CONFIG_S5L8922X) 100 __register -= CLK_REG_OFF; 101 __register += 0x78; 102#endif 103 104 if (state) { 105 HwReg(gS5L8930XClockGateBase + __register) = HwReg(gS5L8930XClockGateBase + __register) | 0xF; 106 } else { 107 HwReg(gS5L8930XClockGateBase + __register) = HwReg(gS5L8930XClockGateBase + __register) & ~0xF; 108 } 109 110 /* 111 * Wait for the state change to take effect. 112 */ 113 while ((HwReg(gS5L8930XClockGateBase + __register) & 0xF) != ((HwReg(gS5L8930XClockGateBase + __register) >> 4) & 0xF)) 114 barrier(); 115 116 return; 117} 118 119static void timer_configure(void) 120{ 121 /* 122 * DUMMY 123 */ 124 uint64_t hz = 24000000; 125 gPEClockFrequencyInfo.timebase_frequency_hz = hz; 126 127 clock_decrementer = 10000; 128 kprintf(KPRINTF_PREFIX "decrementer frequency = %llu\n", clock_decrementer); 129 130 rtc_configure(hz); 131 return; 132} 133 134void S5L8930X_putc(int c) 135{ 136 if(c == '\n') S5L8930X_putc('\r'); 137 138 /* 139 * Wait for FIFO queue to empty. 140 */ 141 while (HwReg(gS5L8930XUartBase + UFSTAT) & UART_UFSTAT_TXFIFO_FULL) 142 barrier(); 143 144 HwReg(gS5L8930XUartBase + UTXH) = c; 145 return; 146} 147 148int S5L8930X_getc(void) 149{ 150 /* 151 * Wait for a character. 152 */ 153 int i = 0x80; 154 uint32_t ufstat = HwReg(gS5L8930XUartBase + UFSTAT); 155 boolean_t can_read = FALSE; 156 157 can_read = (ufstat & UART_UFSTAT_RXFIFO_FULL) | (ufstat & 0xF); 158 if(can_read) 159 return HwReg(gS5L8930XUartBase + URXH); 160 else 161 return -1; 162 163 return -1; 164} 165 166void S5L8930X_uart_init(void) 167{ 168 uint32_t divisorValue; 169 /* 170 * xxx map pmgr 171 */ 172#ifdef BOARD_CONFIG_S5L8930X 173 gS5L8930XPmgrBase = ml_io_map(0xBF102000, PAGE_SIZE); 174 assert(gS5L8930XPmgrBase); 175#elif defined(BOARD_CONFIG_S5L8922X) || defined(BOARD_CONFIG_S5L8920X) 176 gS5L8930XPmgrBase = ml_io_map(0xBF100000, PAGE_SIZE); 177 assert(gS5L8930XPmgrBase); 178#endif 179 180 /* 181 * XXX: The UART init routine is also the Core Platform mapping routine... 182 */ 183 gS5L8930XVic0Base = ml_io_map(VIC(0), PAGE_SIZE); 184 gS5L8930XVic1Base = ml_io_map(VIC(1), PAGE_SIZE); 185 gS5L8930XVic2Base = ml_io_map(VIC(2), PAGE_SIZE); 186 gS5L8930XVic3Base = ml_io_map(VIC(3), PAGE_SIZE); 187 assert(gS5L8930XVic0Base && gS5L8930XVic1Base && gS5L8930XVic2Base && gS5L8930XVic3Base); 188 189 /* 190 * Clocks. 191 */ 192 gS5L8930XTimerBase = ml_io_map(TIMER0_BASE, PAGE_SIZE); 193 assert(gS5L8930XTimerBase); 194 195 /* 196 * Map the UARTs... 197 */ 198 gS5L8930XUartBase = ml_io_map(UART0_BASE, PAGE_SIZE); 199 200 /* 201 * Also map the ClockGate Base 202 */ 203 gS5L8930XClockGateBase = ml_io_map(CLOCK_GATE_BASE, PAGE_SIZE); 204 205 assert(gS5L8930XUartBase && gS5L8930XClockGateBase); 206 207 if (avoid_uarts) 208 return; 209 210 /* 211 * Enable clock gate. 212 */ 213 s5l8930x_clock_gate_switch(UART_CLOCKGATE, TRUE); 214 215 /* 216 * Set 8-bit frames. 217 */ 218 HwReg(gS5L8930XUartBase + ULCON) = UART_8BITS; 219 220 /* 221 * Use polling for RX/TX. 222 */ 223 HwReg(gS5L8930XUartBase + UCON) = ((UART_UCON_MODE_IRQORPOLL << UART_UCON_RXMODE_SHIFT) | (UART_UCON_MODE_IRQORPOLL << UART_UCON_TXMODE_SHIFT)); 224 225 /* 226 * Set clock. 227 */ 228 HwReg(gS5L8930XUartBase + UCON) = (HwReg(gS5L8930XUartBase + UCON) & (~UART_CLOCK_SELECTION_MASK)) | (1 << UART_CLOCK_SELECTION_SHIFT); 229 230 /* 231 * Set baud to 115200. 232 */ 233 divisorValue = CLOCK_HZ / (115200 * 16) - 1; 234 235 HwReg(gS5L8930XUartBase + UBRDIV) = (HwReg(gS5L8930XUartBase + UBRDIV) & (~UART_DIVVAL_MASK)) | divisorValue; 236 237 /* 238 * Reset FIFO 239 */ 240 HwReg(gS5L8930XUartBase + UFCON) = UART_FIFO_RESET_RX | UART_FIFO_RESET_TX; 241 242 /* 243 * Enable FIFO 244 */ 245 HwReg(gS5L8930XUartBase + UFCON) = UART_FIFO_ENABLE; 246 247 PE_kputc = S5L8930X_putc; 248 249 kprintf(KPRINTF_PREFIX "serial is up\n"); 250 251 return; 252} 253 254void S5L8930X_interrupt_init(void) 255{ 256 /* 257 * Disable interrupts 258 */ 259 ml_set_interrupts_enabled(FALSE); 260 261 /* 262 * Goddamn am I paranoid. 263 */ 264 assert(gS5L8930XVic0Base && gS5L8930XVic1Base && gS5L8930XVic2Base && gS5L8930XVic3Base); 265 266 /* 267 * Disable all interrupts. 268 */ 269 HwReg(gS5L8930XVic0Base + VICINTENCLEAR) = 0xFFFFFFFF; 270 HwReg(gS5L8930XVic1Base + VICINTENCLEAR) = 0xFFFFFFFF; 271 HwReg(gS5L8930XVic2Base + VICINTENCLEAR) = 0xFFFFFFFF; 272 HwReg(gS5L8930XVic3Base + VICINTENCLEAR) = 0xFFFFFFFF; 273 274 HwReg(gS5L8930XVic0Base + VICINTENABLE) = 0; 275 HwReg(gS5L8930XVic1Base + VICINTENABLE) = 0; 276 HwReg(gS5L8930XVic2Base + VICINTENABLE) = 0; 277 HwReg(gS5L8930XVic3Base + VICINTENABLE) = 0; 278 279 /* 280 * Please use IRQs. I don't want to implement a FIQ based timer decrementer handler. 281 */ 282 HwReg(gS5L8930XVic0Base + VICINTSELECT) = 0; 283 HwReg(gS5L8930XVic1Base + VICINTSELECT) = 0; 284 HwReg(gS5L8930XVic2Base + VICINTSELECT) = 0; 285 HwReg(gS5L8930XVic3Base + VICINTSELECT) = 0; 286 287 /* 288 * Unmask all interrupt levels. 289 */ 290 HwReg(gS5L8930XVic0Base + VICSWPRIORITYMASK) = 0xFFFF; 291 HwReg(gS5L8930XVic1Base + VICSWPRIORITYMASK) = 0xFFFF; 292 HwReg(gS5L8930XVic2Base + VICSWPRIORITYMASK) = 0xFFFF; 293 HwReg(gS5L8930XVic3Base + VICSWPRIORITYMASK) = 0xFFFF; 294 295 /* 296 * Set vector addresses to interrupt numbers. 297 */ 298 int i; 299 for (i = 0; i < 0x20; i++) { 300 HwReg(gS5L8930XVic0Base + VICVECTADDRS + (i * 4)) = (0x20 * 0) + i; 301 HwReg(gS5L8930XVic1Base + VICVECTADDRS + (i * 4)) = (0x20 * 1) + i; 302 HwReg(gS5L8930XVic2Base + VICVECTADDRS + (i * 4)) = (0x20 * 2) + i; 303 HwReg(gS5L8930XVic3Base + VICVECTADDRS + (i * 4)) = (0x20 * 3) + i; 304 } 305 306 return; 307} 308 309uint64_t S5L8930X_timer_value(void); 310void S5L8930X_timer_enabled(int enable); 311 312void S5L8930X_timebase_init(void) 313{ 314 assert(gS5L8930XTimerBase); 315 316 /* 317 * Set rtclock stuff 318 */ 319 timer_configure(); 320 321 /* 322 * Disable the timer. 323 */ 324 S5L8930X_timer_enabled(FALSE); 325 326 /* 327 * Enable the interrupt. 328 */ 329 HwReg(gS5L8930XVic0Base + VICINTENABLE) = HwReg(gS5L8930XVic0Base + VICINTENABLE) | (1 << 5) | (1 << 6); 330 331 /* 332 * Enable interrupts. 333 */ 334 ml_set_interrupts_enabled(TRUE); 335 336 /* 337 * Wait for it. 338 */ 339 kprintf(KPRINTF_PREFIX "waiting for system timer to come up...\n"); 340 S5L8930X_timer_enabled(TRUE); 341 342 clock_initialized = TRUE; 343 344 while (!clock_had_irq) 345 barrier(); 346 347 return; 348} 349 350void S5L8930X_handle_interrupt(void *context) 351{ 352 uint32_t current_irq = HwReg(gS5L8930XVic0Base + VICADDRESS); 353 /* 354 * Timer IRQs are handeled by us. 355 */ 356 if (current_irq == 6) { 357 /* 358 * Disable timer 359 */ 360 S5L8930X_timer_enabled(FALSE); 361 362 /* 363 * Update absolute time 364 */ 365 clock_absolute_time += (clock_decrementer - (int64_t) S5L8930X_timer_value()); 366 367 /* 368 * Resynchronize deadlines. 369 */ 370 rtclock_intr((arm_saved_state_t *) context); 371 372 /* 373 * EOI. 374 */ 375 HwReg(gS5L8930XVic0Base + VICADDRESS) = 0; 376 377 /* 378 * Enable timer. 379 */ 380 S5L8930X_timer_enabled(TRUE); 381 382 /* 383 * We had an IRQ. 384 */ 385 clock_had_irq = TRUE; 386 } else { 387 irq_iokit_dispatch(current_irq); 388 } 389 390 return; 391} 392 393uint64_t S5L8930X_get_timebase(void) 394{ 395 uint32_t timestamp; 396 397 if (!clock_initialized) 398 return 0; 399 400 timestamp = S5L8930X_timer_value(); 401 402 if (timestamp) { 403 uint64_t v = clock_absolute_time; 404 v += (uint64_t) (((uint64_t) clock_decrementer) - (uint64_t) (timestamp)); 405 return v; 406 } else { 407 clock_absolute_time += clock_decrementer; 408 return clock_absolute_time; 409 } 410} 411 412uint64_t S5L8930X_timer_value(void) 413{ 414 uint64_t ret = (uint64_t) ((uint32_t) 0xFFFFFFFF - (uint32_t) HwReg(gS5L8930XTimerBase + TIMER0_VAL)); 415 416 /* 417 * HACK 418 */ 419 if (ret >= clock_decrementer) 420 ret = 0; 421 422 return ret; 423} 424 425void S5L8930X_timer_enabled(int enable) 426{ 427 /* 428 * How do you disable this timer? 429 */ 430 if (!enable) { 431 HwReg(gS5L8930XTimerBase + TIMER0_CTRL) = 2; 432 HwReg(gS5L8930XTimerBase + TIMER0_CTRL) = 0; 433 } else { 434 HwReg(gS5L8930XTimerBase + TIMER0_VAL) = 0xFFFFFFFF; 435 HwReg(gS5L8930XTimerBase + TIMER0_CTRL) = 3; 436 HwReg(gS5L8930XTimerBase + TIMER0_CTRL) = 1; 437 HwReg(gS5L8930XTimerBase + TIMER0_VAL) = clock_decrementer; 438 } 439 return; 440} 441 442/* 443 * Stub for printing out to framebuffer. 444 */ 445void vcputc(__unused int l, __unused int u, int c); 446 447static void _fb_putc(int c) 448{ 449 if (c == '\n') { 450 vcputc(0, 0, '\r'); 451 } 452 vcputc(0, 0, c); 453 if (avoid_uarts) 454 S5L8930X_putc(c); 455} 456 457void S5L8930X_framebuffer_init(void) 458{ 459 char tempbuf[16]; 460 461 /* 462 * Technically, iBoot should initialize this.. Haven't bothered 463 * to reverse this part properly, if you're using a 16-bit panel, then use 464 * the 'rgb565' boot-argument if you care about a working framebuffer... 465 */ 466 PE_state.video.v_depth = 4 * (8); // 32bpp 467 if (PE_parse_boot_argn("rgb565", tempbuf, sizeof(tempbuf))) { 468 PE_state.video.v_depth = 2 * (8); // 16bpp 469 } 470 471 kprintf(KPRINTF_PREFIX "framebuffer initialized\n"); 472 473 /* 474 * Enable early framebuffer. 475 */ 476 477 if (PE_parse_boot_argn("-early-fb-debug", tempbuf, sizeof(tempbuf))) { 478 initialize_screen((void *) &PE_state.video, kPEAcquireScreen); 479 } 480 481 if (PE_parse_boot_argn("-graphics-mode", tempbuf, sizeof(tempbuf))) { 482 initialize_screen((void *) &PE_state.video, kPEGraphicsMode); 483 } else { 484 initialize_screen((void *) &PE_state.video, kPETextMode); 485 } 486 return; 487} 488 489int S5L8930X_halt_restart(int type) 490{ 491#ifdef BOARD_CONFIG_S5L8930X 492 /* 493 * Just reboot. 494 */ 495 assert(gS5L8930XPmgrBase); 496 HwReg(gS5L8930XPmgrBase + 0x2C) = 0; 497 HwReg(gS5L8930XPmgrBase + 0x24) = 1; 498 HwReg(gS5L8930XPmgrBase + 0x20) = 0x80000000; 499 HwReg(gS5L8930XPmgrBase + 0x2C) = 4; 500 HwReg(gS5L8930XPmgrBase + 0x20) = 0; 501#elif defined(BOARD_CONFIG_S5L8920X) 502 assert(gS5L8930XPmgrBase); 503 HwReg(gS5L8930XPmgrBase + 0x21C) = 0; 504 HwReg(gS5L8930XPmgrBase + 0x214) = 1; 505 HwReg(gS5L8930XPmgrBase + 0x210) = 0x80000000; 506 HwReg(gS5L8930XPmgrBase + 0x21C) = 4; 507 HwReg(gS5L8930XPmgrBase + 0x210) = 0; 508#elif defined(BOARD_CONFIG_S5L8922X) 509 assert(gS5L8930XPmgrBase); 510 HwReg(gS5L8930XPmgrBase + 0x21C) = 0; 511 HwReg(gS5L8930XPmgrBase + 0x214) = 1; 512 HwReg(gS5L8930XPmgrBase + 0x210) = 0x80000000; 513 HwReg(gS5L8930XPmgrBase + 0x21C) = 4; 514 HwReg(gS5L8930XPmgrBase + 0x210) = 0; 515#endif 516 /* 517 * xxx never reached 518 */ 519 return 0; 520} 521 522void PE_init_SocSupport_S5L8930X(void) 523{ 524 gPESocDispatch.uart_getc = S5L8930X_getc; 525 gPESocDispatch.uart_putc = S5L8930X_putc; 526 gPESocDispatch.uart_init = S5L8930X_uart_init; 527 528 gPESocDispatch.interrupt_init = S5L8930X_interrupt_init; 529 gPESocDispatch.timebase_init = S5L8930X_timebase_init; 530 531 gPESocDispatch.get_timebase = S5L8930X_get_timebase; 532 533 gPESocDispatch.handle_interrupt = S5L8930X_handle_interrupt; 534 535 gPESocDispatch.timer_value = S5L8930X_timer_value; 536 gPESocDispatch.timer_enabled = S5L8930X_timer_enabled; 537 538 gPESocDispatch.framebuffer_init = S5L8930X_framebuffer_init; 539 540 char tempbuf[16]; 541 if (PE_parse_boot_argn("-avoid-uarts", tempbuf, sizeof(tempbuf))) { 542 avoid_uarts = 1; 543 } 544 545 if (PE_parse_boot_argn("-force-uarts", tempbuf, sizeof(tempbuf))) { 546 avoid_uarts = 0; 547 } 548 549 S5L8930X_framebuffer_init(); 550 S5L8930X_uart_init(); 551 552 PE_halt_restart = S5L8930X_halt_restart; 553} 554 555void PE_init_SocSupport_stub(void) 556{ 557 PE_early_puts("PE_init_SocSupport: Initializing for S5L8930X\n"); 558 PE_init_SocSupport_S5L8930X(); 559} 560 561#endif /* !BOARD_CONFIG_S5L8930X) || !BOARD_CONFIG_S5L8920X) || !BOARD_CONFIG_S5L8922X */ 562