1 /* $NetBSD: meson_uart.c,v 1.8 2025/09/06 22:53:47 thorpej Exp $ */ 2 3 /*- 4 * Copyright (c) 2013 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. 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 "opt_console.h" 33 #include "locators.h" 34 35 #include <sys/cdefs.h> 36 37 __KERNEL_RCSID(1, "$NetBSD: meson_uart.c,v 1.8 2025/09/06 22:53:47 thorpej Exp $"); 38 39 #define cn_trap() \ 40 do { \ 41 console_debugger(); \ 42 cn_trapped = 1; \ 43 } while (/* CONSTCOND */ 0) 44 45 #include <sys/param.h> 46 #include <sys/bus.h> 47 #include <sys/device.h> 48 #include <sys/conf.h> 49 #include <sys/intr.h> 50 #include <sys/systm.h> 51 #include <sys/time.h> 52 #include <sys/termios.h> 53 #include <sys/kauth.h> 54 #include <sys/lwp.h> 55 #include <sys/tty.h> 56 57 #include <ddb/db_active.h> 58 59 #include <dev/cons.h> 60 61 #include <dev/fdt/fdtvar.h> 62 #include <dev/fdt/fdt_console.h> 63 64 #include <arm/amlogic/meson_uart.h> 65 66 static int meson_uart_match(device_t, cfdata_t, void *); 67 static void meson_uart_attach(device_t, device_t, void *); 68 69 static int meson_uart_intr(void *); 70 static void meson_uart_rxsoft(void *); 71 72 static int meson_uart_cngetc(dev_t); 73 static void meson_uart_cnputc(dev_t, int); 74 static void meson_uart_cnpollc(dev_t, int); 75 76 static void meson_uart_start(struct tty *); 77 static int meson_uart_param(struct tty *, struct termios *); 78 79 extern struct cfdriver mesonuart_cd; 80 81 static const struct device_compatible_entry compat_data[] = { 82 { .compat = "amlogic,meson6-uart" }, 83 { .compat = "amlogic,meson8-uart" }, 84 { .compat = "amlogic,meson8b-uart" }, 85 { .compat = "amlogic,meson-gx-uart" }, 86 DEVICE_COMPAT_EOL 87 }; 88 89 struct meson_uart_softc { 90 device_t sc_dev; 91 bus_space_tag_t sc_bst; 92 bus_space_handle_t sc_bsh; 93 kmutex_t sc_intr_lock; 94 void *sc_ih; 95 void *sc_sih; 96 97 struct tty *sc_tty; 98 99 int sc_ospeed; 100 unsigned int sc_rbuf_w; /* write ptr of sc_rbuf[] */ 101 unsigned int sc_rbuf_r; /* read ptr of sc_rbuf[] */ 102 tcflag_t sc_cflag; 103 104 u_char sc_buf[1024]; 105 #define MESON_RBUFSZ 128 /* must be 2^n */ 106 u_char sc_rbuf[MESON_RBUFSZ]; /* good enough for sizeof RXFIFO */ 107 }; 108 109 static int meson_uart_console_phandle = -1; 110 111 static struct meson_uart_softc meson_uart_cnsc; 112 113 static struct cnm_state meson_uart_cnm_state; 114 115 struct consdev meson_uart_consdev = { 116 .cn_getc = meson_uart_cngetc, 117 .cn_putc = meson_uart_cnputc, 118 .cn_pollc = meson_uart_cnpollc, 119 .cn_dev = NODEV, 120 .cn_pri = CN_NORMAL, 121 }; 122 123 static dev_type_open(meson_uart_open); 124 static dev_type_open(meson_uart_close); 125 static dev_type_read(meson_uart_read); 126 static dev_type_write(meson_uart_write); 127 static dev_type_ioctl(meson_uart_ioctl); 128 static dev_type_tty(meson_uart_tty); 129 static dev_type_poll(meson_uart_poll); 130 static dev_type_stop(meson_uart_stop); 131 132 const struct cdevsw mesonuart_cdevsw = { 133 .d_open = meson_uart_open, 134 .d_close = meson_uart_close, 135 .d_read = meson_uart_read, 136 .d_write = meson_uart_write, 137 .d_ioctl = meson_uart_ioctl, 138 .d_stop = meson_uart_stop, 139 .d_tty = meson_uart_tty, 140 .d_poll = meson_uart_poll, 141 .d_mmap = nommap, 142 .d_kqfilter = ttykqfilter, 143 .d_discard = nodiscard, 144 .d_flag = D_TTY 145 }; 146 147 static int meson_uart_cmajor = -1; 148 149 CFATTACH_DECL_NEW(meson_uart, sizeof(struct meson_uart_softc), 150 meson_uart_match, meson_uart_attach, NULL, NULL); 151 152 static int 153 meson_uart_match(device_t parent, cfdata_t cf, void *aux) 154 { 155 struct fdt_attach_args * const faa = aux; 156 157 return of_compatible_match(faa->faa_phandle, compat_data); 158 } 159 160 static void 161 meson_uart_attach(device_t parent, device_t self, void *aux) 162 { 163 struct meson_uart_softc * const sc = device_private(self); 164 struct fdt_attach_args * const faa = aux; 165 const int phandle = faa->faa_phandle; 166 char intrstr[128]; 167 bus_addr_t addr; 168 bus_size_t size; 169 struct tty *tp; 170 int major, minor, error; 171 uint32_t misc, control; 172 173 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 174 aprint_error(": couldn't get registers\n"); 175 return; 176 } 177 178 sc->sc_dev = self; 179 sc->sc_bst = faa->faa_bst; 180 181 error = bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh); 182 if (error != 0) { 183 aprint_error(": couldn't map registers\n"); 184 return; 185 } 186 187 if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { 188 aprint_error(": failed to decode interrupt\n"); 189 return; 190 } 191 192 mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SERIAL); 193 sc->sc_ih = fdtbus_intr_establish_xname(phandle, 0, IPL_SERIAL, 194 FDT_INTR_MPSAFE, meson_uart_intr, sc, device_xname(self)); 195 if (sc->sc_ih == NULL) { 196 aprint_error(": failed to establish interrupt on %s\n", 197 intrstr); 198 return; 199 } 200 201 sc->sc_sih = softint_establish(SOFTINT_SERIAL, meson_uart_rxsoft, sc); 202 if (sc->sc_sih == NULL) { 203 aprint_error(": failed to establish softint\n"); 204 return; 205 } 206 207 if (meson_uart_cmajor == -1) { 208 /* allocate a major number */ 209 int bmajor = -1, cmajor = -1; 210 error = devsw_attach("mesonuart", NULL, &bmajor, 211 &mesonuart_cdevsw, &cmajor); 212 if (error) { 213 aprint_error(": couldn't allocate major number\n"); 214 return; 215 } 216 meson_uart_cmajor = cmajor; 217 } 218 219 major = cdevsw_lookup_major(&mesonuart_cdevsw); 220 minor = device_unit(self); 221 222 tp = sc->sc_tty = tty_alloc(); 223 tp->t_oproc = meson_uart_start; 224 tp->t_param = meson_uart_param; 225 tp->t_dev = makedev(major, minor); 226 tp->t_sc = sc; 227 tty_attach(tp); 228 229 aprint_naive("\n"); 230 if (meson_uart_console_phandle == phandle) { 231 cn_tab->cn_dev = tp->t_dev; 232 aprint_normal(": console"); 233 } 234 aprint_normal("\n"); 235 236 aprint_normal_dev(self, "interrupting on %s\n", intrstr); 237 238 misc = bus_space_read_4(sc->sc_bst, sc->sc_bsh, UART_MISC_REG); 239 misc &= ~UART_MISC_TX_IRQ_CNT; 240 misc |= __SHIFTIN(0, UART_MISC_TX_IRQ_CNT); 241 misc &= ~UART_MISC_RX_IRQ_CNT; 242 misc |= __SHIFTIN(1, UART_MISC_RX_IRQ_CNT); 243 bus_space_write_4(sc->sc_bst, sc->sc_bsh, UART_MISC_REG, misc); 244 245 control = bus_space_read_4(sc->sc_bst, sc->sc_bsh, UART_CONTROL_REG); 246 control &= ~(UART_CONTROL_TX_INT_EN|UART_CONTROL_RX_INT_EN); 247 bus_space_write_4(sc->sc_bst, sc->sc_bsh, UART_CONTROL_REG, control); 248 } 249 250 static int 251 meson_uart_cngetc(dev_t dev) 252 { 253 bus_space_tag_t bst = meson_uart_cnsc.sc_bst; 254 bus_space_handle_t bsh = meson_uart_cnsc.sc_bsh; 255 uint32_t status; 256 int s, c; 257 258 s = splserial(); 259 260 status = bus_space_read_4(bst, bsh, UART_STATUS_REG); 261 if (status & UART_STATUS_RX_EMPTY) { 262 splx(s); 263 return -1; 264 } 265 266 c = bus_space_read_4(bst, bsh, UART_RFIFO_REG) & 0xff; 267 if (!db_active) { 268 int cn_trapped __unused = 0; 269 cn_check_magic(dev, c, meson_uart_cnm_state); 270 } 271 272 splx(s); 273 274 return c; 275 } 276 277 static void 278 meson_uart_cnputc(dev_t dev, int c) 279 { 280 bus_space_tag_t bst = meson_uart_cnsc.sc_bst; 281 bus_space_handle_t bsh = meson_uart_cnsc.sc_bsh; 282 int s; 283 284 s = splserial(); 285 286 while ((bus_space_read_4(bst, bsh, UART_STATUS_REG) & UART_STATUS_TX_FULL) != 0) 287 ; 288 289 bus_space_write_4(bst, bsh, UART_WFIFO_REG, c); 290 291 splx(s); 292 } 293 294 295 static void 296 meson_uart_cnpollc(dev_t dev, int on) 297 { 298 } 299 300 static int 301 meson_uart_open(dev_t dev, int flag, int mode, lwp_t *l) 302 { 303 struct meson_uart_softc *sc = 304 device_lookup_private(&mesonuart_cd, minor(dev)); 305 struct tty *tp = sc->sc_tty; 306 uint32_t control; 307 308 if (kauth_authorize_device_tty(l->l_cred, 309 KAUTH_DEVICE_TTY_OPEN, tp) != 0) { 310 return EBUSY; 311 } 312 313 if ((tp->t_state & TS_ISOPEN) == 0 && tp->t_wopen == 0) { 314 tp->t_dev = dev; 315 ttychars(tp); 316 tp->t_iflag = TTYDEF_IFLAG; 317 tp->t_oflag = TTYDEF_OFLAG; 318 tp->t_cflag = TTYDEF_CFLAG; 319 tp->t_lflag = TTYDEF_LFLAG; 320 tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED; 321 ttsetwater(tp); 322 323 control = bus_space_read_4(sc->sc_bst, sc->sc_bsh, UART_CONTROL_REG); 324 control |= UART_CONTROL_RX_INT_EN; 325 bus_space_write_4(sc->sc_bst, sc->sc_bsh, UART_CONTROL_REG, control); 326 } 327 tp->t_state |= TS_CARR_ON; 328 329 return tp->t_linesw->l_open(dev, tp); 330 } 331 332 static int 333 meson_uart_close(dev_t dev, int flag, int mode, lwp_t *l) 334 { 335 struct meson_uart_softc *sc = 336 device_lookup_private(&mesonuart_cd, minor(dev)); 337 struct tty *tp = sc->sc_tty; 338 uint32_t control; 339 340 tp->t_linesw->l_close(tp, flag); 341 ttyclose(tp); 342 343 if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) { 344 control = bus_space_read_4(sc->sc_bst, sc->sc_bsh, UART_CONTROL_REG); 345 control &= ~UART_CONTROL_RX_INT_EN; 346 bus_space_write_4(sc->sc_bst, sc->sc_bsh, UART_CONTROL_REG, control); 347 } 348 349 return 0; 350 } 351 352 static int 353 meson_uart_read(dev_t dev, struct uio *uio, int flag) 354 { 355 struct meson_uart_softc *sc = 356 device_lookup_private(&mesonuart_cd, minor(dev)); 357 struct tty *tp = sc->sc_tty; 358 359 return tp->t_linesw->l_read(tp, uio, flag); 360 } 361 362 static int 363 meson_uart_write(dev_t dev, struct uio *uio, int flag) 364 { 365 struct meson_uart_softc *sc = 366 device_lookup_private(&mesonuart_cd, minor(dev)); 367 struct tty *tp = sc->sc_tty; 368 369 return tp->t_linesw->l_write(tp, uio, flag); 370 } 371 372 static int 373 meson_uart_poll(dev_t dev, int events, lwp_t *l) 374 { 375 struct meson_uart_softc *sc = 376 device_lookup_private(&mesonuart_cd, minor(dev)); 377 struct tty *tp = sc->sc_tty; 378 379 return tp->t_linesw->l_poll(tp, events, l); 380 } 381 382 static int 383 meson_uart_ioctl(dev_t dev, u_long cmd, void *data, int flag, lwp_t *l) 384 { 385 struct meson_uart_softc *sc = 386 device_lookup_private(&mesonuart_cd, minor(dev)); 387 struct tty *tp = sc->sc_tty; 388 int error; 389 390 error = tp->t_linesw->l_ioctl(tp, cmd, data, flag, l); 391 if (error != EPASSTHROUGH) 392 return error; 393 394 return ttioctl(tp, cmd, data, flag, l); 395 } 396 397 static struct tty * 398 meson_uart_tty(dev_t dev) 399 { 400 struct meson_uart_softc *sc = 401 device_lookup_private(&mesonuart_cd, minor(dev)); 402 403 return sc->sc_tty; 404 } 405 406 static void 407 meson_uart_stop(struct tty *tp, int flag) 408 { 409 } 410 411 static void 412 meson_uart_start(struct tty *tp) 413 { 414 struct meson_uart_softc *sc = tp->t_sc; 415 u_char *p = sc->sc_buf; 416 int s, brem; 417 418 s = spltty(); 419 420 if (tp->t_state & (TS_TTSTOP | TS_BUSY | TS_TIMEOUT)) { 421 splx(s); 422 return; 423 } 424 tp->t_state |= TS_BUSY; 425 426 splx(s); 427 428 for (brem = q_to_b(&tp->t_outq, sc->sc_buf, sizeof(sc->sc_buf)); 429 brem > 0; 430 brem--, p++) { 431 while ((bus_space_read_4(sc->sc_bst, sc->sc_bsh, 432 UART_STATUS_REG) & UART_STATUS_TX_FULL) != 0) 433 ; 434 435 bus_space_write_4(sc->sc_bst, sc->sc_bsh, 436 UART_WFIFO_REG, *p); 437 } 438 439 s = spltty(); 440 tp->t_state &= ~TS_BUSY; 441 if (ttypull(tp)) { 442 tp->t_state |= TS_TIMEOUT; 443 callout_schedule(&tp->t_rstrt_ch, 1); 444 } 445 splx(s); 446 } 447 448 static int 449 meson_uart_param(struct tty *tp, struct termios *t) 450 { 451 452 tp->t_ispeed = t->c_ispeed; 453 tp->t_ospeed = t->c_ospeed; 454 tp->t_cflag = t->c_cflag; 455 456 return 0; 457 } 458 459 static int 460 meson_uart_intr(void *priv) 461 { 462 struct meson_uart_softc *sc = priv; 463 struct tty *tp = sc->sc_tty; 464 uint32_t status, c; 465 466 mutex_spin_enter(&sc->sc_intr_lock); 467 for (;;) { 468 int cn_trapped = 0; 469 status = bus_space_read_4(sc->sc_bst, sc->sc_bsh, 470 UART_STATUS_REG); 471 if (status & UART_STATUS_RX_EMPTY) { 472 break; 473 } 474 if (status & UART_STATUS_BREAK) { 475 cn_check_magic(tp->t_dev, CNC_BREAK, 476 meson_uart_cnm_state); 477 if (cn_trapped) 478 continue; 479 } 480 481 c = bus_space_read_4(sc->sc_bst, sc->sc_bsh, UART_RFIFO_REG); 482 cn_check_magic(tp->t_dev, c & 0xff, meson_uart_cnm_state); 483 if (cn_trapped) 484 continue; 485 486 if ((sc->sc_rbuf_w - sc->sc_rbuf_r) >= (MESON_RBUFSZ - 1)) 487 continue; 488 sc->sc_rbuf[sc->sc_rbuf_w++ & (MESON_RBUFSZ - 1)] = c; 489 } 490 mutex_spin_exit(&sc->sc_intr_lock); 491 492 if (sc->sc_rbuf_w != sc->sc_rbuf_r) 493 softint_schedule(sc->sc_sih); 494 495 return 0; 496 } 497 498 static void 499 meson_uart_rxsoft(void *priv) 500 { 501 struct meson_uart_softc *sc = priv; 502 struct tty *tp = sc->sc_tty; 503 int c; 504 505 while (sc->sc_rbuf_w != sc->sc_rbuf_r) { 506 c = sc->sc_rbuf[sc->sc_rbuf_r++ & (MESON_RBUFSZ - 1)]; 507 if (tp->t_linesw->l_rint(c & 0xff, tp) == -1) 508 break; 509 } 510 } 511 512 static int 513 meson_uart_console_match(int phandle) 514 { 515 return of_compatible_match(phandle, compat_data); 516 } 517 518 static void 519 meson_uart_console_consinit(struct fdt_attach_args *faa, u_int uart_freq) 520 { 521 struct meson_uart_softc *sc = &meson_uart_cnsc; 522 const int phandle = faa->faa_phandle; 523 bus_addr_t addr; 524 bus_size_t size; 525 int error; 526 527 fdtbus_get_reg(phandle, 0, &addr, &size); 528 529 sc->sc_bst = faa->faa_bst; 530 sc->sc_ospeed = fdtbus_get_stdout_speed(); 531 if (sc->sc_ospeed < 0) 532 sc->sc_ospeed = 115200; 533 sc->sc_cflag = fdtbus_get_stdout_flags(); 534 535 error = bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh); 536 if (error != 0) 537 panic("failed to map console, error = %d", error); 538 539 cn_tab = &meson_uart_consdev; 540 cn_init_magic(&meson_uart_cnm_state); 541 cn_set_magic("\047\001"); 542 543 meson_uart_console_phandle = phandle; 544 } 545 546 static const struct fdt_console meson_uart_console = { 547 .match = meson_uart_console_match, 548 .consinit = meson_uart_console_consinit, 549 }; 550 551 FDT_CONSOLE(meson_uart, &meson_uart_console); 552