xref: /freebsd/sys/arm64/apple/exynos_uart.c (revision ba2336d3044c681462224c12879ecc8f659be54a)
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