exynos_uart.c revision 1.1 1 /* $NetBSD: exynos_uart.c,v 1.1 2018/07/05 13:11:58 jmcneill Exp $ */
2
3 /*-
4 * Copyright (c) 2013-2018 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Matt Thomas of 3am Software Foundry and Jared McNeill.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include "locators.h"
33
34 #include <sys/cdefs.h>
35
36 __KERNEL_RCSID(1, "$NetBSD: exynos_uart.c,v 1.1 2018/07/05 13:11:58 jmcneill Exp $");
37
38 #define cn_trap() \
39 do { \
40 console_debugger(); \
41 cn_trapped = 1; \
42 } while (/* CONSTCOND */ 0)
43
44 #include <sys/param.h>
45 #include <sys/bus.h>
46 #include <sys/device.h>
47 #include <sys/conf.h>
48 #include <sys/intr.h>
49 #include <sys/systm.h>
50 #include <sys/time.h>
51 #include <sys/termios.h>
52 #include <sys/kauth.h>
53 #include <sys/lwp.h>
54 #include <sys/tty.h>
55
56 #include <dev/cons.h>
57
58 #include <dev/fdt/fdtvar.h>
59
60 #include <arm/samsung/sscom_reg.h>
61
62 static int exynos_uart_match(device_t, cfdata_t, void *);
63 static void exynos_uart_attach(device_t, device_t, void *);
64
65 static int exynos_uart_intr(void *);
66
67 static int exynos_uart_cngetc(dev_t);
68 static void exynos_uart_cnputc(dev_t, int);
69 static void exynos_uart_cnpollc(dev_t, int);
70
71 static void exynos_uart_start(struct tty *);
72 static int exynos_uart_param(struct tty *, struct termios *);
73
74 extern struct cfdriver exuart_cd;
75
76 struct exynos_uart_softc {
77 device_t sc_dev;
78 bus_space_tag_t sc_bst;
79 bus_space_handle_t sc_bsh;
80 u_int sc_freq;
81 void *sc_ih;
82
83 bool sc_console;
84 struct tty *sc_tty;
85
86 int sc_ospeed;
87 tcflag_t sc_cflag;
88
89 u_char sc_buf[1024];
90 };
91
92 #define RD4(sc, reg) \
93 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
94 #define WR4(sc, reg, val) \
95 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
96
97 static bus_addr_t exynos_uart_consaddr;
98
99 static struct exynos_uart_softc exynos_uart_cnsc;
100
101 static struct cnm_state exynos_uart_cnm_state;
102
103 struct consdev exynos_uart_consdev = {
104 .cn_getc = exynos_uart_cngetc,
105 .cn_putc = exynos_uart_cnputc,
106 .cn_pollc = exynos_uart_cnpollc,
107 .cn_dev = NODEV,
108 .cn_pri = CN_NORMAL,
109 };
110
111 static dev_type_open(exynos_uart_open);
112 static dev_type_open(exynos_uart_close);
113 static dev_type_read(exynos_uart_read);
114 static dev_type_write(exynos_uart_write);
115 static dev_type_ioctl(exynos_uart_ioctl);
116 static dev_type_tty(exynos_uart_tty);
117 static dev_type_poll(exynos_uart_poll);
118 static dev_type_stop(exynos_uart_stop);
119
120 const struct cdevsw exuart_cdevsw = {
121 .d_open = exynos_uart_open,
122 .d_close = exynos_uart_close,
123 .d_read = exynos_uart_read,
124 .d_write = exynos_uart_write,
125 .d_ioctl = exynos_uart_ioctl,
126 .d_stop = exynos_uart_stop,
127 .d_tty = exynos_uart_tty,
128 .d_poll = exynos_uart_poll,
129 .d_mmap = nommap,
130 .d_kqfilter = ttykqfilter,
131 .d_discard = nodiscard,
132 .d_flag = D_TTY
133 };
134
135 static int exynos_uart_cmajor = -1;
136
137 static const char * const compatible[] = {
138 "samsung,exynos4210-uart",
139 NULL
140 };
141
142 CFATTACH_DECL_NEW(exynos_uart, sizeof(struct exynos_uart_softc),
143 exynos_uart_match, exynos_uart_attach, NULL, NULL);
144
145 static int
146 exynos_uart_match(device_t parent, cfdata_t cf, void *aux)
147 {
148 struct fdt_attach_args * const faa = aux;
149
150 return of_match_compatible(faa->faa_phandle, compatible);
151 }
152
153 static void
154 exynos_uart_attach(device_t parent, device_t self, void *aux)
155 {
156 struct exynos_uart_softc * const sc = device_private(self);
157 struct fdt_attach_args * const faa = aux;
158 const int phandle = faa->faa_phandle;
159 char intrstr[128];
160 struct clk *clk_uart, *clk_uart_baud0;
161 struct tty *tp;
162 int major, minor;
163 bus_addr_t addr;
164 bus_size_t size;
165
166 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
167 aprint_error(": couldn't get registers\n");
168 return;
169 }
170 if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
171 aprint_error(": failed to decode interrupt\n");
172 return;
173 }
174 clk_uart = fdtbus_clock_get(phandle, "uart");
175 if (clk_uart == NULL || clk_enable(clk_uart) != 0) {
176 aprint_error(": failed to enable uart clock\n");
177 return;
178 }
179 clk_uart_baud0 = fdtbus_clock_get(phandle, "clk_uart_baud0");
180 if (clk_uart_baud0 == NULL || clk_enable(clk_uart_baud0) != 0) {
181 aprint_error(": failed to enable clk_uart_baud0 clock\n");
182 return;
183 }
184
185 const bool is_console = exynos_uart_consaddr == addr;
186
187 sc->sc_dev = self;
188 sc->sc_bst = faa->faa_bst;
189 sc->sc_console = is_console;
190 if (is_console) {
191 sc->sc_bsh = exynos_uart_cnsc.sc_bsh;
192 } else {
193 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
194 aprint_error(": failed to map registers\n");
195 return;
196 }
197 }
198 sc->sc_freq = clk_get_rate(clk_uart_baud0);
199
200 sc->sc_ih = fdtbus_intr_establish(phandle, 0, IPL_SERIAL,
201 0, exynos_uart_intr, sc);
202 if (sc->sc_ih == NULL) {
203 aprint_error(": failed to establish interrupt on %s\n",
204 intrstr);
205 return;
206 }
207
208 if (exynos_uart_cmajor == -1) {
209 /* allocate a major number */
210 int bmajor = -1, cmajor = -1;
211 int error = devsw_attach("exuart", NULL, &bmajor,
212 &exuart_cdevsw, &cmajor);
213 if (error) {
214 aprint_error(": couldn't allocate major number\n");
215 return;
216 }
217 exynos_uart_cmajor = cmajor;
218 }
219
220 major = cdevsw_lookup_major(&exuart_cdevsw);
221 minor = device_unit(self);
222
223 tp = sc->sc_tty = tty_alloc();
224 tp->t_oproc = exynos_uart_start;
225 tp->t_param = exynos_uart_param;
226 tp->t_dev = makedev(major, minor);
227 tp->t_sc = sc;
228 tty_attach(tp);
229
230 aprint_naive("\n");
231 if (is_console) {
232 cn_tab->cn_dev = tp->t_dev;
233 aprint_normal(": console");
234 }
235 aprint_normal("\n");
236
237 if (is_console)
238 delay(10000);
239
240 /* Initialize device */
241 WR4(sc, SSCOM_UFCON,
242 __SHIFTIN(2, UFCON_TXTRIGGER) |
243 __SHIFTIN(1, UFCON_RXTRIGGER) |
244 UFCON_TXFIFO_RESET | UFCON_RXFIFO_RESET |
245 UFCON_FIFO_ENABLE);
246 /* Configure PIO mode with RX timeout interrupts */
247 WR4(sc, SSCOM_UCON,
248 __SHIFTIN(3, UCON_RXTO) |
249 UCON_TOINT | UCON_ERRINT |
250 UCON_TXMODE_INT | UCON_RXMODE_INT);
251
252 /* Disable interrupts */
253 WR4(sc, SSCOM_UINTM, ~0u);
254
255 aprint_normal_dev(self, "interrupting on %s\n", intrstr);
256 }
257
258 static int
259 exynos_uart_cngetc(dev_t dev)
260 {
261 struct exynos_uart_softc * const sc = &exynos_uart_cnsc;
262 uint32_t status;
263 int s, c;
264
265 s = splserial();
266
267 status = RD4(sc, SSCOM_UTRSTAT);
268 if ((status & UTRSTAT_RXREADY) == 0) {
269 splx(s);
270 return -1;
271 }
272
273 c = bus_space_read_1(sc->sc_bst, sc->sc_bsh, SSCOM_URXH);
274 #if defined(DDB)
275 extern int db_active;
276 if (!db_active)
277 #endif
278 {
279 int cn_trapped __unused = 0;
280 cn_check_magic(dev, c, exynos_uart_cnm_state);
281 }
282
283 splx(s);
284
285 return c & 0xff;
286 }
287
288 static void
289 exynos_uart_cnputc(dev_t dev, int c)
290 {
291 struct exynos_uart_softc * const sc = &exynos_uart_cnsc;
292 int s;
293
294 s = splserial();
295 while ((RD4(sc, SSCOM_UFSTAT) & UFSTAT_TXFULL) != 0)
296 ;
297
298 bus_space_write_1(sc->sc_bst, sc->sc_bsh, SSCOM_UTXH, c);
299
300 splx(s);
301 }
302
303
304 static void
305 exynos_uart_cnpollc(dev_t dev, int on)
306 {
307 }
308
309 static void
310 exynos_uart_cnattach(bus_space_tag_t bst, bus_space_handle_t bsh,
311 int ospeed, tcflag_t cflag)
312 {
313 struct exynos_uart_softc *sc = &exynos_uart_cnsc;
314
315 cn_tab = &exynos_uart_consdev;
316 cn_init_magic(&exynos_uart_cnm_state);
317 cn_set_magic("\047\001");
318
319 sc->sc_bst = bst;
320 sc->sc_bsh = bsh;
321 sc->sc_ospeed = ospeed;
322 sc->sc_cflag = cflag;
323 }
324
325 static int
326 exynos_uart_open(dev_t dev, int flag, int mode, lwp_t *l)
327 {
328 struct exynos_uart_softc *sc =
329 device_lookup_private(&exuart_cd, minor(dev));
330 struct tty *tp = sc->sc_tty;
331
332 if (kauth_authorize_device_tty(l->l_cred,
333 KAUTH_DEVICE_TTY_OPEN, tp) != 0) {
334 return EBUSY;
335 }
336
337 if ((tp->t_state & TS_ISOPEN) == 0 && tp->t_wopen == 0) {
338 tp->t_dev = dev;
339 ttychars(tp);
340 tp->t_iflag = TTYDEF_IFLAG;
341 tp->t_oflag = TTYDEF_OFLAG;
342 tp->t_lflag = TTYDEF_LFLAG;
343 if (sc->sc_console) {
344 tp->t_ispeed = tp->t_ospeed = exynos_uart_cnsc.sc_ospeed;
345 tp->t_cflag = exynos_uart_cnsc.sc_cflag;
346 } else {
347 tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED;
348 tp->t_cflag = TTYDEF_CFLAG;
349 }
350 ttsetwater(tp);
351 }
352 tp->t_state |= TS_CARR_ON;
353
354 /* Enable RX and error interrupts */
355 WR4(sc, SSCOM_UINTM, ~0u & ~(UINT_RXD|UINT_ERROR));
356
357 return tp->t_linesw->l_open(dev, tp);
358 }
359
360 static int
361 exynos_uart_close(dev_t dev, int flag, int mode, lwp_t *l)
362 {
363 struct exynos_uart_softc *sc =
364 device_lookup_private(&exuart_cd, minor(dev));
365 struct tty *tp = sc->sc_tty;
366
367 tp->t_linesw->l_close(tp, flag);
368 ttyclose(tp);
369
370 /* Disable interrupts */
371 WR4(sc, SSCOM_UINTM, ~0u);
372
373 return 0;
374 }
375
376 static int
377 exynos_uart_read(dev_t dev, struct uio *uio, int flag)
378 {
379 struct exynos_uart_softc *sc =
380 device_lookup_private(&exuart_cd, minor(dev));
381 struct tty *tp = sc->sc_tty;
382
383 return tp->t_linesw->l_read(tp, uio, flag);
384 }
385
386 static int
387 exynos_uart_write(dev_t dev, struct uio *uio, int flag)
388 {
389 struct exynos_uart_softc *sc =
390 device_lookup_private(&exuart_cd, minor(dev));
391 struct tty *tp = sc->sc_tty;
392
393 return tp->t_linesw->l_write(tp, uio, flag);
394 }
395
396 static int
397 exynos_uart_poll(dev_t dev, int events, lwp_t *l)
398 {
399 struct exynos_uart_softc *sc =
400 device_lookup_private(&exuart_cd, minor(dev));
401 struct tty *tp = sc->sc_tty;
402
403 return tp->t_linesw->l_poll(tp, events, l);
404 }
405
406 static int
407 exynos_uart_ioctl(dev_t dev, u_long cmd, void *data, int flag, lwp_t *l)
408 {
409 struct exynos_uart_softc *sc =
410 device_lookup_private(&exuart_cd, minor(dev));
411 struct tty *tp = sc->sc_tty;
412 int error;
413
414 error = tp->t_linesw->l_ioctl(tp, cmd, data, flag, l);
415 if (error != EPASSTHROUGH)
416 return error;
417
418 return ttioctl(tp, cmd, data, flag, l);
419 }
420
421 static struct tty *
422 exynos_uart_tty(dev_t dev)
423 {
424 struct exynos_uart_softc *sc =
425 device_lookup_private(&exuart_cd, minor(dev));
426
427 return sc->sc_tty;
428 }
429
430 static void
431 exynos_uart_stop(struct tty *tp, int flag)
432 {
433 }
434
435 static void
436 exynos_uart_start(struct tty *tp)
437 {
438 struct exynos_uart_softc *sc = tp->t_sc;
439 u_char *p = sc->sc_buf;
440 int s, brem;
441
442 s = spltty();
443
444 if (tp->t_state & (TS_TTSTOP | TS_BUSY | TS_TIMEOUT)) {
445 splx(s);
446 return;
447 }
448 tp->t_state |= TS_BUSY;
449
450 splx(s);
451
452 for (brem = q_to_b(&tp->t_outq, sc->sc_buf, sizeof(sc->sc_buf));
453 brem > 0;
454 brem--, p++) {
455 while ((RD4(sc, SSCOM_UFSTAT) & UFSTAT_TXFULL) != 0)
456 ;
457
458 bus_space_write_1(sc->sc_bst, sc->sc_bsh,
459 SSCOM_UTXH, *p);
460 }
461
462 s = spltty();
463 tp->t_state &= ~TS_BUSY;
464 if (ttypull(tp)) {
465 tp->t_state |= TS_TIMEOUT;
466 callout_schedule(&tp->t_rstrt_ch, 1);
467 }
468 splx(s);
469 }
470
471 static int
472 exynos_uart_param(struct tty *tp, struct termios *t)
473 {
474 struct exynos_uart_softc *sc = tp->t_sc;
475
476 if (tp->t_ospeed == t->c_ospeed &&
477 tp->t_cflag == t->c_cflag)
478 return 0;
479
480 uint32_t ulcon = 0, ubrdiv;
481 switch (ISSET(t->c_cflag, CSIZE)) {
482 case CS5:
483 ulcon |= ULCON_LENGTH_5;
484 break;
485 case CS6:
486 ulcon |= ULCON_LENGTH_6;
487 break;
488 case CS7:
489 ulcon |= ULCON_LENGTH_7;
490 break;
491 case CS8:
492 ulcon |= ULCON_LENGTH_8;
493 break;
494 }
495 switch (ISSET(t->c_cflag, PARENB|PARODD)) {
496 case PARENB|PARODD:
497 ulcon |= ULCON_PARITY_ODD;
498 break;
499 case PARENB:
500 ulcon |= ULCON_PARITY_EVEN;
501 break;
502 default:
503 ulcon |= ULCON_PARITY_NONE;
504 break;
505 }
506 if (ISSET(t->c_cflag, CSTOPB))
507 ulcon |= ULCON_STOP;
508 WR4(sc, SSCOM_ULCON, ulcon);
509
510 ubrdiv = (sc->sc_freq / 16) / t->c_ospeed - 1;
511 WR4(sc, SSCOM_UBRDIV, ubrdiv);
512
513 tp->t_ispeed = t->c_ispeed;
514 tp->t_ospeed = t->c_ospeed;
515 tp->t_cflag = t->c_cflag;
516
517 return 0;
518 }
519
520 static int
521 exynos_uart_intr(void *priv)
522 {
523 struct exynos_uart_softc *sc = priv;
524 struct tty *tp = sc->sc_tty;
525 uint32_t uintp, uerstat, ufstat, c;
526
527 uintp = RD4(sc, SSCOM_UINTP);
528
529 for (;;) {
530 int cn_trapped = 0;
531
532 uerstat = RD4(sc, SSCOM_UERSTAT);
533 if (uerstat & UERSTAT_BREAK) {
534 cn_check_magic(tp->t_dev, CNC_BREAK,
535 exynos_uart_cnm_state);
536 if (cn_trapped)
537 continue;
538 }
539
540 ufstat = RD4(sc, SSCOM_UFSTAT);
541 if (__SHIFTOUT(ufstat, UFSTAT_RXCOUNT) == 0) {
542 break;
543 }
544
545 c = bus_space_read_1(sc->sc_bst, sc->sc_bsh, SSCOM_URXH);
546 cn_check_magic(tp->t_dev, c & 0xff, exynos_uart_cnm_state);
547 if (cn_trapped)
548 continue;
549 tp->t_linesw->l_rint(c & 0xff, tp);
550 }
551
552 WR4(sc, SSCOM_UINTP, uintp);
553
554 return 1;
555 }
556
557 /*
558 * Console support
559 */
560
561 static int
562 exynos_uart_console_match(int phandle)
563 {
564 return of_match_compatible(phandle, compatible);
565 }
566
567 static void
568 exynos_uart_console_consinit(struct fdt_attach_args *faa, u_int uart_freq)
569 {
570 const int phandle = faa->faa_phandle;
571 bus_space_tag_t bst = faa->faa_bst;
572 bus_space_handle_t bsh;
573 bus_addr_t addr;
574 bus_size_t size;
575 tcflag_t flags;
576 int speed;
577
578 speed = fdtbus_get_stdout_speed();
579 if (speed < 0)
580 speed = 115200; /* default */
581 flags = fdtbus_get_stdout_flags();
582
583 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0)
584 panic("exynos_uart: couldn't get registers");
585 if (bus_space_map(bst, addr, size, 0, &bsh) != 0)
586 panic("exynos_uart: couldn't map registers");
587
588 exynos_uart_consaddr = addr;
589
590 exynos_uart_cnattach(bst, bsh, speed, flags);
591 }
592
593 static const struct fdt_console exynos_uart_console = {
594 .match = exynos_uart_console_match,
595 .consinit = exynos_uart_console_consinit,
596 };
597
598 FDT_CONSOLE(exynos_uart, &exynos_uart_console);
599