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