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 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 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 210 exynos4210_probe(struct uart_bas *bas) 211 { 212 213 return (0); 214 } 215 216 static void 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 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 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 272 exynos4210_term(struct uart_bas *bas) 273 { 274 /* XXX */ 275 } 276 277 static void 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 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 314 exynos4210_rxready(struct uart_bas *bas) 315 { 316 317 return (exynos4210_rxready_impl(bas, false)); 318 } 319 320 static int 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 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 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 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 434 exynos4210_bus_setsig(struct uart_softc *sc, int sig) 435 { 436 437 return (0); 438 } 439 440 static int 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 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 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 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 543 exynos4210_bus_flush(struct uart_softc *sc, int what) 544 { 545 546 return (0); 547 } 548 549 static int 550 exynos4210_bus_getsig(struct uart_softc *sc) 551 { 552 553 return (0); 554 } 555 556 static int 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