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