1 /*
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2003 Marcel Moolenaar
5 * Copyright (c) 2007-2009 Andrew Turner
6 * Copyright (c) 2013 Ruslan Bukin <br@bsdpad.com>
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include <sys/param.h>
32 #include <sys/systm.h>
33 #include <sys/bus.h>
34 #include <sys/conf.h>
35 #include <sys/cons.h>
36 #include <sys/rman.h>
37 #include <machine/bus.h>
38 #include <machine/intr.h>
39
40 #include <dev/uart/uart.h>
41 #include <dev/uart/uart_cpu.h>
42 #include <dev/uart/uart_cpu_fdt.h>
43 #include <dev/uart/uart_bus.h>
44
45 #include <arm64/apple/exynos_uart.h>
46
47 #include "uart_if.h"
48
49 struct exynos_uart_cfg;
50
51 #define DEF_CLK 100000000
52
53 static int sscomspeed(long, long);
54 static int exynos4210_uart_param(struct uart_bas *, int, int, int, int);
55
56 /*
57 * Low-level UART interface.
58 */
59 static int exynos4210_probe(struct uart_bas *bas);
60 static void exynos4210_init_common(struct exynos_uart_cfg *cfg,
61 struct uart_bas *bas, int, int, int, int);
62 static void exynos4210_init(struct uart_bas *bas, int, int, int, int);
63 static void exynos4210_s5l_init(struct uart_bas *bas, int, int, int, int);
64 static void exynos4210_term(struct uart_bas *bas);
65 static void exynos4210_putc(struct uart_bas *bas, int);
66 static int exynos4210_rxready(struct uart_bas *bas);
67 static int exynos4210_getc(struct uart_bas *bas, struct mtx *mtx);
68
69 extern SLIST_HEAD(uart_devinfo_list, uart_devinfo) uart_sysdevs;
70
71 static struct uart_ops uart_exynos4210_ops;
72 static struct uart_ops uart_s5l_ops;
73 static kobj_method_t exynos4210_methods[];
74 static kobj_method_t s5l_methods[];
75 static struct ofw_compat_data compat_data[];
76
77 enum exynos_uart_type {
78 EXUART_4210,
79 EXUART_S5L,
80 };
81
82 struct exynos_uart_cfg {
83 enum exynos_uart_type cfg_type;
84 uint64_t cfg_uart_full_mask;
85 };
86
87 struct exynos_uart_class {
88 struct uart_class base;
89 struct exynos_uart_cfg cfg;
90 };
91
92 static struct exynos_uart_class uart_ex4210_class = {
93 .base = {
94 "exynos4210 class",
95 exynos4210_methods,
96 1,
97 .uc_ops = &uart_exynos4210_ops,
98 .uc_range = 8,
99 .uc_rclk = 0,
100 .uc_rshift = 0
101 },
102 .cfg = {
103 .cfg_type = EXUART_4210,
104 .cfg_uart_full_mask = UFSTAT_TXFULL,
105 },
106 };
107
108
109 static struct exynos_uart_class uart_s5l_class = {
110 .base = {
111 "s5l class",
112 s5l_methods,
113 1,
114 .uc_ops = &uart_s5l_ops,
115 .uc_range = 8,
116 .uc_rclk = 0,
117 .uc_rshift = 0
118 },
119 .cfg = {
120 .cfg_type = EXUART_S5L,
121 .cfg_uart_full_mask = UFSTAT_S5L_TXFULL,
122 },
123 };
124
125 static int
sscomspeed(long speed,long frequency)126 sscomspeed(long speed, long frequency)
127 {
128 int x;
129
130 if (speed <= 0 || frequency <= 0)
131 return (-1);
132 x = (frequency / 16) / speed;
133 return (x-1);
134 }
135
136 static int
exynos4210_uart_param(struct uart_bas * bas,int baudrate,int databits,int stopbits,int parity)137 exynos4210_uart_param(struct uart_bas *bas, int baudrate, int databits,
138 int stopbits, int parity)
139 {
140 int brd, ulcon;
141
142 ulcon = 0;
143
144 switch(databits) {
145 case 5:
146 ulcon |= ULCON_LENGTH_5;
147 break;
148 case 6:
149 ulcon |= ULCON_LENGTH_6;
150 break;
151 case 7:
152 ulcon |= ULCON_LENGTH_7;
153 break;
154 case 8:
155 ulcon |= ULCON_LENGTH_8;
156 break;
157 default:
158 return (EINVAL);
159 }
160
161 switch (parity) {
162 case UART_PARITY_NONE:
163 ulcon |= ULCON_PARITY_NONE;
164 break;
165 case UART_PARITY_ODD:
166 ulcon |= ULCON_PARITY_ODD;
167 break;
168 case UART_PARITY_EVEN:
169 ulcon |= ULCON_PARITY_EVEN;
170 break;
171 case UART_PARITY_MARK:
172 case UART_PARITY_SPACE:
173 default:
174 return (EINVAL);
175 }
176
177 if (stopbits == 2)
178 ulcon |= ULCON_STOP;
179
180 uart_setreg(bas, SSCOM_ULCON, ulcon);
181
182 /* baudrate may be negative, in which case we just leave it alone. */
183 if (baudrate > 0) {
184 brd = sscomspeed(baudrate, bas->rclk);
185 uart_setreg(bas, SSCOM_UBRDIV, brd);
186 }
187
188 return (0);
189 }
190
191 static struct uart_ops uart_exynos4210_ops = {
192 .probe = exynos4210_probe,
193 .init = exynos4210_init,
194 .term = exynos4210_term,
195 .putc = exynos4210_putc,
196 .rxready = exynos4210_rxready,
197 .getc = exynos4210_getc,
198 };
199
200 static struct uart_ops uart_s5l_ops = {
201 .probe = exynos4210_probe,
202 .init = exynos4210_s5l_init,
203 .term = exynos4210_term,
204 .putc = exynos4210_putc,
205 .rxready = exynos4210_rxready,
206 .getc = exynos4210_getc,
207 };
208
209 static int
exynos4210_probe(struct uart_bas * bas)210 exynos4210_probe(struct uart_bas *bas)
211 {
212
213 return (0);
214 }
215
216 static void
exynos4210_init_common(struct exynos_uart_cfg * cfg,struct uart_bas * bas,int baudrate,int databits,int stopbits,int parity)217 exynos4210_init_common(struct exynos_uart_cfg *cfg, struct uart_bas *bas,
218 int baudrate, int databits, int stopbits, int parity)
219 {
220
221 if (bas->rclk == 0)
222 bas->rclk = DEF_CLK;
223
224 KASSERT(bas->rclk != 0, ("exynos4210_init: Invalid rclk"));
225
226 bas->driver1 = cfg;
227
228 /* Clear interrupts */
229 if (cfg->cfg_type == EXUART_S5L) {
230 uart_setreg(bas, SSCOM_UTRSTAT, 0);
231 } else {
232 uart_setreg(bas, SSCOM_UCON, 0);
233 uart_setreg(bas, SSCOM_UFCON,
234 UFCON_TXTRIGGER_8 | UFCON_RXTRIGGER_8 |
235 UFCON_TXFIFO_RESET | UFCON_RXFIFO_RESET |
236 UFCON_FIFO_ENABLE);
237 }
238
239 exynos4210_uart_param(bas, baudrate, databits, stopbits, parity);
240
241 /* Enable UART. */
242 if (cfg->cfg_type == EXUART_S5L) {
243 uart_setreg(bas, SSCOM_UCON, uart_getreg(bas, SSCOM_UCON) |
244 UCON_TOINT | UCON_S5L_RXTHRESH | UCON_S5L_RX_TIMEOUT |
245 UCON_S5L_TXTHRESH);
246 } else {
247 uart_setreg(bas, SSCOM_UCON, uart_getreg(bas, SSCOM_UCON) |
248 UCON_TXMODE_INT | UCON_RXMODE_INT | UCON_TOINT);
249 uart_setreg(bas, SSCOM_UMCON, UMCON_RTS);
250 }
251 }
252
253 static void
exynos4210_init(struct uart_bas * bas,int baudrate,int databits,int stopbits,int parity)254 exynos4210_init(struct uart_bas *bas, int baudrate, int databits, int stopbits,
255 int parity)
256 {
257
258 return (exynos4210_init_common(&uart_ex4210_class.cfg, bas, baudrate,
259 databits, stopbits, parity));
260 }
261
262 static void
exynos4210_s5l_init(struct uart_bas * bas,int baudrate,int databits,int stopbits,int parity)263 exynos4210_s5l_init(struct uart_bas *bas, int baudrate, int databits, int stopbits,
264 int parity)
265 {
266
267 return (exynos4210_init_common(&uart_s5l_class.cfg, bas, baudrate,
268 databits, stopbits, parity));
269 }
270
271 static void
exynos4210_term(struct uart_bas * bas)272 exynos4210_term(struct uart_bas *bas)
273 {
274 /* XXX */
275 }
276
277 static void
exynos4210_putc(struct uart_bas * bas,int c)278 exynos4210_putc(struct uart_bas *bas, int c)
279 {
280 struct exynos_uart_cfg *cfg;
281
282 cfg = bas->driver1;
283
284 while ((bus_space_read_4(bas->bst, bas->bsh, SSCOM_UFSTAT) &
285 cfg->cfg_uart_full_mask) != 0)
286 continue;
287
288 uart_setreg(bas, SSCOM_UTXH, c);
289 uart_barrier(bas);
290 }
291
292 static int
exynos4210_rxready_impl(struct uart_bas * bas,bool intr)293 exynos4210_rxready_impl(struct uart_bas *bas, bool intr)
294 {
295 struct exynos_uart_cfg *cfg;
296 int ufstat, utrstat;
297
298 cfg = bas->driver1;
299 if (!intr || cfg->cfg_type != EXUART_S5L) {
300 utrstat = bus_space_read_4(bas->bst, bas->bsh, SSCOM_UTRSTAT);
301
302 if ((utrstat & UTRSTAT_RXREADY) != 0)
303 return (1);
304 if (cfg->cfg_type != EXUART_S5L)
305 return (0);
306 }
307
308 ufstat = bus_space_read_4(bas->bst, bas->bsh, SSCOM_UFSTAT);
309
310 return ((ufstat & (UFSTAT_RXCOUNT | UFSTAT_RXFULL)) != 0);
311 }
312
313 static int
exynos4210_rxready(struct uart_bas * bas)314 exynos4210_rxready(struct uart_bas *bas)
315 {
316
317 return (exynos4210_rxready_impl(bas, false));
318 }
319
320 static int
exynos4210_getc(struct uart_bas * bas,struct mtx * mtx)321 exynos4210_getc(struct uart_bas *bas, struct mtx *mtx)
322 {
323
324 while (!exynos4210_rxready(bas)) {
325 continue;
326 }
327
328 return (uart_getreg(bas, SSCOM_URXH));
329 }
330
331 static int exynos4210_bus_probe(struct uart_softc *sc);
332 static int exynos4210_bus_attach(struct uart_softc *sc);
333 static int exynos4210_bus_flush(struct uart_softc *, int);
334 static int exynos4210_bus_getsig(struct uart_softc *);
335 static int exynos4210_bus_ioctl(struct uart_softc *, int, intptr_t);
336 static int exynos4210_bus_ipend(struct uart_softc *);
337 static int s5l_bus_ipend(struct uart_softc *);
338 static int exynos4210_bus_param(struct uart_softc *, int, int, int, int);
339 static int exynos4210_bus_receive(struct uart_softc *);
340 static int exynos4210_bus_setsig(struct uart_softc *, int);
341 static int exynos4210_bus_transmit(struct uart_softc *);
342
343 static kobj_method_t exynos4210_methods[] = {
344 KOBJMETHOD(uart_probe, exynos4210_bus_probe),
345 KOBJMETHOD(uart_attach, exynos4210_bus_attach),
346 KOBJMETHOD(uart_flush, exynos4210_bus_flush),
347 KOBJMETHOD(uart_getsig, exynos4210_bus_getsig),
348 KOBJMETHOD(uart_ioctl, exynos4210_bus_ioctl),
349 KOBJMETHOD(uart_ipend, exynos4210_bus_ipend),
350 KOBJMETHOD(uart_param, exynos4210_bus_param),
351 KOBJMETHOD(uart_receive, exynos4210_bus_receive),
352 KOBJMETHOD(uart_setsig, exynos4210_bus_setsig),
353 KOBJMETHOD(uart_transmit, exynos4210_bus_transmit),
354 {0, 0 }
355 };
356
357 static kobj_method_t s5l_methods[] = {
358 KOBJMETHOD(uart_probe, exynos4210_bus_probe),
359 KOBJMETHOD(uart_attach, exynos4210_bus_attach),
360 KOBJMETHOD(uart_flush, exynos4210_bus_flush),
361 KOBJMETHOD(uart_getsig, exynos4210_bus_getsig),
362 KOBJMETHOD(uart_ioctl, exynos4210_bus_ioctl),
363 KOBJMETHOD(uart_ipend, s5l_bus_ipend),
364 KOBJMETHOD(uart_param, exynos4210_bus_param),
365 KOBJMETHOD(uart_receive, exynos4210_bus_receive),
366 KOBJMETHOD(uart_setsig, exynos4210_bus_setsig),
367 KOBJMETHOD(uart_transmit, exynos4210_bus_transmit),
368 {0, 0 }
369 };
370
371 int
exynos4210_bus_probe(struct uart_softc * sc)372 exynos4210_bus_probe(struct uart_softc *sc)
373 {
374
375 sc->sc_txfifosz = 16;
376 sc->sc_rxfifosz = 16;
377
378 return (0);
379 }
380
381 static int
exynos4210_bus_attach(struct uart_softc * sc)382 exynos4210_bus_attach(struct uart_softc *sc)
383 {
384 struct exynos_uart_class *class;
385 struct exynos_uart_cfg *cfg;
386
387 sc->sc_hwiflow = 0;
388 sc->sc_hwoflow = 0;
389
390 class = (struct exynos_uart_class *)ofw_bus_search_compatible(sc->sc_dev,
391 compat_data)->ocd_data;
392 MPASS(class != NULL);
393
394 cfg = &class->cfg;
395 MPASS(sc->sc_sysdev == NULL || cfg == sc->sc_sysdev->bas.driver1);
396 sc->sc_bas.driver1 = cfg;
397
398 return (0);
399 }
400
401 static int
exynos4210_bus_transmit(struct uart_softc * sc)402 exynos4210_bus_transmit(struct uart_softc *sc)
403 {
404 struct exynos_uart_cfg *cfg;
405 int i;
406 int reg;
407
408 cfg = sc->sc_bas.driver1;
409 uart_lock(sc->sc_hwmtx);
410
411 /* tx fifo has room, fire away. */
412 for (i = 0; i < sc->sc_txdatasz; i++) {
413 uart_setreg(&sc->sc_bas, SSCOM_UTXH, sc->sc_txbuf[i]);
414 uart_barrier(&sc->sc_bas);
415 }
416
417 if (cfg->cfg_type == EXUART_S5L) {
418 sc->sc_txbusy = 1;
419 } else {
420 /* unmask TX interrupt */
421 reg = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh,
422 SSCOM_UINTM);
423 reg &= ~(1 << 2);
424 bus_space_write_4(sc->sc_bas.bst, sc->sc_bas.bsh, SSCOM_UINTM,
425 reg);
426 }
427
428 uart_unlock(sc->sc_hwmtx);
429
430 return (0);
431 }
432
433 static int
exynos4210_bus_setsig(struct uart_softc * sc,int sig)434 exynos4210_bus_setsig(struct uart_softc *sc, int sig)
435 {
436
437 return (0);
438 }
439
440 static int
exynos4210_bus_receive(struct uart_softc * sc)441 exynos4210_bus_receive(struct uart_softc *sc)
442 {
443 struct uart_bas *bas;
444
445 bas = &sc->sc_bas;
446 uart_lock(sc->sc_hwmtx);
447
448 while (exynos4210_rxready_impl(bas, true)) {
449 if (uart_rx_full(sc)) {
450 sc->sc_rxbuf[sc->sc_rxput] = UART_STAT_OVERRUN;
451 break;
452 }
453
454 uart_rx_put(sc, uart_getreg(&sc->sc_bas, SSCOM_URXH));
455 }
456
457 uart_unlock(sc->sc_hwmtx);
458
459 return (0);
460 }
461
462 static int
exynos4210_bus_param(struct uart_softc * sc,int baudrate,int databits,int stopbits,int parity)463 exynos4210_bus_param(struct uart_softc *sc, int baudrate, int databits,
464 int stopbits, int parity)
465 {
466 int error;
467
468 if (sc->sc_bas.rclk == 0)
469 sc->sc_bas.rclk = DEF_CLK;
470
471 KASSERT(sc->sc_bas.rclk != 0, ("exynos4210_init: Invalid rclk"));
472
473 uart_lock(sc->sc_hwmtx);
474 error = exynos4210_uart_param(&sc->sc_bas, baudrate, databits, stopbits,
475 parity);
476 uart_unlock(sc->sc_hwmtx);
477
478 return (error);
479 }
480
481 static int
s5l_bus_ipend(struct uart_softc * sc)482 s5l_bus_ipend(struct uart_softc *sc)
483 {
484 int ipend;
485 uint32_t uerstat, utrstat;
486
487 ipend = 0;
488 uart_lock(sc->sc_hwmtx);
489 utrstat = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh,
490 SSCOM_UTRSTAT);
491
492 if (utrstat & (UTRSTAT_S5L_RXTHRESH | UTRSTAT_S5L_RX_TIMEOUT))
493 ipend |= SER_INT_RXREADY;
494
495 if (utrstat & UTRSTAT_S5L_TXTHRESH)
496 ipend |= SER_INT_TXIDLE;
497
498 uerstat = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh,
499 SSCOM_UERSTAT);
500 if ((uerstat & UERSTAT_BREAK) != 0)
501 ipend |= SER_INT_BREAK;
502
503 bus_space_write_4(sc->sc_bas.bst, sc->sc_bas.bsh, SSCOM_UTRSTAT,
504 utrstat);
505 uart_unlock(sc->sc_hwmtx);
506
507 return (ipend);
508 }
509
510 static int
exynos4210_bus_ipend(struct uart_softc * sc)511 exynos4210_bus_ipend(struct uart_softc *sc)
512 {
513 uint32_t ints;
514 int reg;
515 int ipend;
516
517 uart_lock(sc->sc_hwmtx);
518 ints = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh, SSCOM_UINTP);
519 bus_space_write_4(sc->sc_bas.bst, sc->sc_bas.bsh, SSCOM_UINTP, ints);
520
521 ipend = 0;
522 if ((ints & UINTP_TXEMPTY) != 0) {
523 if (sc->sc_txbusy != 0)
524 ipend |= SER_INT_TXIDLE;
525
526 /* mask TX interrupt */
527 reg = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh,
528 SSCOM_UINTM);
529 reg |= UINTM_TXINTR;
530 bus_space_write_4(sc->sc_bas.bst, sc->sc_bas.bsh,
531 SSCOM_UINTM, reg);
532 }
533
534 if ((ints & UINTP_RXREADY) != 0) {
535 ipend |= SER_INT_RXREADY;
536 }
537
538 uart_unlock(sc->sc_hwmtx);
539 return (ipend);
540 }
541
542 static int
exynos4210_bus_flush(struct uart_softc * sc,int what)543 exynos4210_bus_flush(struct uart_softc *sc, int what)
544 {
545
546 return (0);
547 }
548
549 static int
exynos4210_bus_getsig(struct uart_softc * sc)550 exynos4210_bus_getsig(struct uart_softc *sc)
551 {
552
553 return (0);
554 }
555
556 static int
exynos4210_bus_ioctl(struct uart_softc * sc,int request,intptr_t data)557 exynos4210_bus_ioctl(struct uart_softc *sc, int request, intptr_t data)
558 {
559
560 return (EINVAL);
561 }
562
563 static struct ofw_compat_data compat_data[] = {
564 {"apple,s5l-uart", (uintptr_t)&uart_s5l_class.base},
565 {"samsung,exynos4210-uart", (uintptr_t)&uart_ex4210_class.base},
566 {NULL, (uintptr_t)NULL},
567 };
568 UART_FDT_CLASS_AND_DEVICE(compat_data);
569