610 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (C) 2018 Socionext Inc.4 */5 6#include <linux/clk.h>7#include <linux/console.h>8#include <linux/module.h>9#include <linux/of_irq.h>10#include <linux/platform_device.h>11#include <linux/serial_core.h>12#include <linux/tty.h>13#include <linux/tty_flip.h>14 15#define USIO_NAME "mlb-usio-uart"16#define USIO_UART_DEV_NAME "ttyUSI"17 18static struct uart_port mlb_usio_ports[CONFIG_SERIAL_MILBEAUT_USIO_PORTS];19 20#define RX 021#define TX 122static int mlb_usio_irq[CONFIG_SERIAL_MILBEAUT_USIO_PORTS][2];23 24#define MLB_USIO_REG_SMR 025#define MLB_USIO_REG_SCR 126#define MLB_USIO_REG_ESCR 227#define MLB_USIO_REG_SSR 328#define MLB_USIO_REG_DR 429#define MLB_USIO_REG_BGR 630#define MLB_USIO_REG_FCR 1231#define MLB_USIO_REG_FBYTE 1432 33#define MLB_USIO_SMR_SOE BIT(0)34#define MLB_USIO_SMR_SBL BIT(3)35#define MLB_USIO_SCR_TXE BIT(0)36#define MLB_USIO_SCR_RXE BIT(1)37#define MLB_USIO_SCR_TBIE BIT(2)38#define MLB_USIO_SCR_TIE BIT(3)39#define MLB_USIO_SCR_RIE BIT(4)40#define MLB_USIO_SCR_UPCL BIT(7)41#define MLB_USIO_ESCR_L_8BIT 042#define MLB_USIO_ESCR_L_5BIT 143#define MLB_USIO_ESCR_L_6BIT 244#define MLB_USIO_ESCR_L_7BIT 345#define MLB_USIO_ESCR_P BIT(3)46#define MLB_USIO_ESCR_PEN BIT(4)47#define MLB_USIO_ESCR_FLWEN BIT(7)48#define MLB_USIO_SSR_TBI BIT(0)49#define MLB_USIO_SSR_TDRE BIT(1)50#define MLB_USIO_SSR_RDRF BIT(2)51#define MLB_USIO_SSR_ORE BIT(3)52#define MLB_USIO_SSR_FRE BIT(4)53#define MLB_USIO_SSR_PE BIT(5)54#define MLB_USIO_SSR_REC BIT(7)55#define MLB_USIO_SSR_BRK BIT(8)56#define MLB_USIO_FCR_FE1 BIT(0)57#define MLB_USIO_FCR_FE2 BIT(1)58#define MLB_USIO_FCR_FCL1 BIT(2)59#define MLB_USIO_FCR_FCL2 BIT(3)60#define MLB_USIO_FCR_FSET BIT(4)61#define MLB_USIO_FCR_FTIE BIT(9)62#define MLB_USIO_FCR_FDRQ BIT(10)63#define MLB_USIO_FCR_FRIIE BIT(11)64 65static void mlb_usio_stop_tx(struct uart_port *port)66{67 writew(readw(port->membase + MLB_USIO_REG_FCR) & ~MLB_USIO_FCR_FTIE,68 port->membase + MLB_USIO_REG_FCR);69 writeb(readb(port->membase + MLB_USIO_REG_SCR) & ~MLB_USIO_SCR_TBIE,70 port->membase + MLB_USIO_REG_SCR);71}72 73static void mlb_usio_tx_chars(struct uart_port *port)74{75 struct tty_port *tport = &port->state->port;76 int count;77 78 writew(readw(port->membase + MLB_USIO_REG_FCR) & ~MLB_USIO_FCR_FTIE,79 port->membase + MLB_USIO_REG_FCR);80 writeb(readb(port->membase + MLB_USIO_REG_SCR) &81 ~(MLB_USIO_SCR_TIE | MLB_USIO_SCR_TBIE),82 port->membase + MLB_USIO_REG_SCR);83 84 if (port->x_char) {85 writew(port->x_char, port->membase + MLB_USIO_REG_DR);86 port->icount.tx++;87 port->x_char = 0;88 return;89 }90 if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) {91 mlb_usio_stop_tx(port);92 return;93 }94 95 count = port->fifosize -96 (readw(port->membase + MLB_USIO_REG_FBYTE) & 0xff);97 98 do {99 unsigned char ch;100 101 if (!uart_fifo_get(port, &ch))102 break;103 104 writew(ch, port->membase + MLB_USIO_REG_DR);105 port->icount.tx++;106 } while (--count > 0);107 108 writew(readw(port->membase + MLB_USIO_REG_FCR) & ~MLB_USIO_FCR_FDRQ,109 port->membase + MLB_USIO_REG_FCR);110 111 writeb(readb(port->membase + MLB_USIO_REG_SCR) | MLB_USIO_SCR_TBIE,112 port->membase + MLB_USIO_REG_SCR);113 114 if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)115 uart_write_wakeup(port);116 117 if (kfifo_is_empty(&tport->xmit_fifo))118 mlb_usio_stop_tx(port);119}120 121static void mlb_usio_start_tx(struct uart_port *port)122{123 u16 fcr = readw(port->membase + MLB_USIO_REG_FCR);124 125 writew(fcr | MLB_USIO_FCR_FTIE, port->membase + MLB_USIO_REG_FCR);126 if (!(fcr & MLB_USIO_FCR_FDRQ))127 return;128 129 writeb(readb(port->membase + MLB_USIO_REG_SCR) | MLB_USIO_SCR_TBIE,130 port->membase + MLB_USIO_REG_SCR);131 132 if (readb(port->membase + MLB_USIO_REG_SSR) & MLB_USIO_SSR_TBI)133 mlb_usio_tx_chars(port);134}135 136static void mlb_usio_stop_rx(struct uart_port *port)137{138 writeb(readb(port->membase + MLB_USIO_REG_SCR) & ~MLB_USIO_SCR_RIE,139 port->membase + MLB_USIO_REG_SCR);140}141 142static void mlb_usio_enable_ms(struct uart_port *port)143{144 writeb(readb(port->membase + MLB_USIO_REG_SCR) |145 MLB_USIO_SCR_RIE | MLB_USIO_SCR_RXE,146 port->membase + MLB_USIO_REG_SCR);147}148 149static void mlb_usio_rx_chars(struct uart_port *port)150{151 struct tty_port *ttyport = &port->state->port;152 u8 flag = 0, ch = 0;153 u8 status;154 int max_count = 2;155 156 while (max_count--) {157 status = readb(port->membase + MLB_USIO_REG_SSR);158 159 if (!(status & MLB_USIO_SSR_RDRF))160 break;161 162 if (!(status & (MLB_USIO_SSR_ORE | MLB_USIO_SSR_FRE |163 MLB_USIO_SSR_PE))) {164 ch = readw(port->membase + MLB_USIO_REG_DR);165 flag = TTY_NORMAL;166 port->icount.rx++;167 if (uart_handle_sysrq_char(port, ch))168 continue;169 uart_insert_char(port, status, MLB_USIO_SSR_ORE,170 ch, flag);171 continue;172 }173 if (status & MLB_USIO_SSR_PE)174 port->icount.parity++;175 if (status & MLB_USIO_SSR_ORE)176 port->icount.overrun++;177 status &= port->read_status_mask;178 if (status & MLB_USIO_SSR_BRK) {179 flag = TTY_BREAK;180 ch = 0;181 } else182 if (status & MLB_USIO_SSR_PE) {183 flag = TTY_PARITY;184 ch = 0;185 } else186 if (status & MLB_USIO_SSR_FRE) {187 flag = TTY_FRAME;188 ch = 0;189 }190 if (flag)191 uart_insert_char(port, status, MLB_USIO_SSR_ORE,192 ch, flag);193 194 writeb(readb(port->membase + MLB_USIO_REG_SSR) |195 MLB_USIO_SSR_REC,196 port->membase + MLB_USIO_REG_SSR);197 198 max_count = readw(port->membase + MLB_USIO_REG_FBYTE) >> 8;199 writew(readw(port->membase + MLB_USIO_REG_FCR) |200 MLB_USIO_FCR_FE2 | MLB_USIO_FCR_FRIIE,201 port->membase + MLB_USIO_REG_FCR);202 }203 204 tty_flip_buffer_push(ttyport);205}206 207static irqreturn_t mlb_usio_rx_irq(int irq, void *dev_id)208{209 struct uart_port *port = dev_id;210 211 uart_port_lock(port);212 mlb_usio_rx_chars(port);213 uart_port_unlock(port);214 215 return IRQ_HANDLED;216}217 218static irqreturn_t mlb_usio_tx_irq(int irq, void *dev_id)219{220 struct uart_port *port = dev_id;221 222 uart_port_lock(port);223 if (readb(port->membase + MLB_USIO_REG_SSR) & MLB_USIO_SSR_TBI)224 mlb_usio_tx_chars(port);225 uart_port_unlock(port);226 227 return IRQ_HANDLED;228}229 230static unsigned int mlb_usio_tx_empty(struct uart_port *port)231{232 return (readb(port->membase + MLB_USIO_REG_SSR) & MLB_USIO_SSR_TBI) ?233 TIOCSER_TEMT : 0;234}235 236static void mlb_usio_set_mctrl(struct uart_port *port, unsigned int mctrl)237{238}239 240static unsigned int mlb_usio_get_mctrl(struct uart_port *port)241{242 return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;243 244}245 246static void mlb_usio_break_ctl(struct uart_port *port, int break_state)247{248}249 250static int mlb_usio_startup(struct uart_port *port)251{252 const char *portname = to_platform_device(port->dev)->name;253 unsigned long flags;254 int ret, index = port->line;255 unsigned char escr;256 257 ret = request_irq(mlb_usio_irq[index][RX], mlb_usio_rx_irq,258 0, portname, port);259 if (ret)260 return ret;261 ret = request_irq(mlb_usio_irq[index][TX], mlb_usio_tx_irq,262 0, portname, port);263 if (ret) {264 free_irq(mlb_usio_irq[index][RX], port);265 return ret;266 }267 268 escr = readb(port->membase + MLB_USIO_REG_ESCR);269 if (of_property_read_bool(port->dev->of_node, "auto-flow-control"))270 escr |= MLB_USIO_ESCR_FLWEN;271 uart_port_lock_irqsave(port, &flags);272 writeb(0, port->membase + MLB_USIO_REG_SCR);273 writeb(escr, port->membase + MLB_USIO_REG_ESCR);274 writeb(MLB_USIO_SCR_UPCL, port->membase + MLB_USIO_REG_SCR);275 writeb(MLB_USIO_SSR_REC, port->membase + MLB_USIO_REG_SSR);276 writew(0, port->membase + MLB_USIO_REG_FCR);277 writew(MLB_USIO_FCR_FCL1 | MLB_USIO_FCR_FCL2,278 port->membase + MLB_USIO_REG_FCR);279 writew(MLB_USIO_FCR_FE1 | MLB_USIO_FCR_FE2 | MLB_USIO_FCR_FRIIE,280 port->membase + MLB_USIO_REG_FCR);281 writew(0, port->membase + MLB_USIO_REG_FBYTE);282 writew(BIT(12), port->membase + MLB_USIO_REG_FBYTE);283 284 writeb(MLB_USIO_SCR_TXE | MLB_USIO_SCR_RIE | MLB_USIO_SCR_TBIE |285 MLB_USIO_SCR_RXE, port->membase + MLB_USIO_REG_SCR);286 uart_port_unlock_irqrestore(port, flags);287 288 return 0;289}290 291static void mlb_usio_shutdown(struct uart_port *port)292{293 int index = port->line;294 295 free_irq(mlb_usio_irq[index][RX], port);296 free_irq(mlb_usio_irq[index][TX], port);297}298 299static void mlb_usio_set_termios(struct uart_port *port,300 struct ktermios *termios,301 const struct ktermios *old)302{303 unsigned int escr, smr = MLB_USIO_SMR_SOE;304 unsigned long flags, baud, quot;305 306 switch (termios->c_cflag & CSIZE) {307 case CS5:308 escr = MLB_USIO_ESCR_L_5BIT;309 break;310 case CS6:311 escr = MLB_USIO_ESCR_L_6BIT;312 break;313 case CS7:314 escr = MLB_USIO_ESCR_L_7BIT;315 break;316 case CS8:317 default:318 escr = MLB_USIO_ESCR_L_8BIT;319 break;320 }321 322 if (termios->c_cflag & CSTOPB)323 smr |= MLB_USIO_SMR_SBL;324 325 if (termios->c_cflag & PARENB) {326 escr |= MLB_USIO_ESCR_PEN;327 if (termios->c_cflag & PARODD)328 escr |= MLB_USIO_ESCR_P;329 }330 /* Set hard flow control */331 if (of_property_read_bool(port->dev->of_node, "auto-flow-control") ||332 (termios->c_cflag & CRTSCTS))333 escr |= MLB_USIO_ESCR_FLWEN;334 335 baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk);336 if (baud > 1)337 quot = port->uartclk / baud - 1;338 else339 quot = 0;340 341 uart_port_lock_irqsave(port, &flags);342 uart_update_timeout(port, termios->c_cflag, baud);343 port->read_status_mask = MLB_USIO_SSR_ORE | MLB_USIO_SSR_RDRF |344 MLB_USIO_SSR_TDRE;345 if (termios->c_iflag & INPCK)346 port->read_status_mask |= MLB_USIO_SSR_FRE | MLB_USIO_SSR_PE;347 348 port->ignore_status_mask = 0;349 if (termios->c_iflag & IGNPAR)350 port->ignore_status_mask |= MLB_USIO_SSR_FRE | MLB_USIO_SSR_PE;351 if ((termios->c_iflag & IGNBRK) && (termios->c_iflag & IGNPAR))352 port->ignore_status_mask |= MLB_USIO_SSR_ORE;353 if ((termios->c_cflag & CREAD) == 0)354 port->ignore_status_mask |= MLB_USIO_SSR_RDRF;355 356 writeb(0, port->membase + MLB_USIO_REG_SCR);357 writeb(MLB_USIO_SCR_UPCL, port->membase + MLB_USIO_REG_SCR);358 writeb(MLB_USIO_SSR_REC, port->membase + MLB_USIO_REG_SSR);359 writew(0, port->membase + MLB_USIO_REG_FCR);360 writeb(smr, port->membase + MLB_USIO_REG_SMR);361 writeb(escr, port->membase + MLB_USIO_REG_ESCR);362 writew(quot, port->membase + MLB_USIO_REG_BGR);363 writew(0, port->membase + MLB_USIO_REG_FCR);364 writew(MLB_USIO_FCR_FCL1 | MLB_USIO_FCR_FCL2 | MLB_USIO_FCR_FE1 |365 MLB_USIO_FCR_FE2 | MLB_USIO_FCR_FRIIE,366 port->membase + MLB_USIO_REG_FCR);367 writew(0, port->membase + MLB_USIO_REG_FBYTE);368 writew(BIT(12), port->membase + MLB_USIO_REG_FBYTE);369 writeb(MLB_USIO_SCR_RIE | MLB_USIO_SCR_RXE | MLB_USIO_SCR_TBIE |370 MLB_USIO_SCR_TXE, port->membase + MLB_USIO_REG_SCR);371 uart_port_unlock_irqrestore(port, flags);372}373 374static const char *mlb_usio_type(struct uart_port *port)375{376 return ((port->type == PORT_MLB_USIO) ? USIO_NAME : NULL);377}378 379static void mlb_usio_config_port(struct uart_port *port, int flags)380{381 if (flags & UART_CONFIG_TYPE)382 port->type = PORT_MLB_USIO;383}384 385static const struct uart_ops mlb_usio_ops = {386 .tx_empty = mlb_usio_tx_empty,387 .set_mctrl = mlb_usio_set_mctrl,388 .get_mctrl = mlb_usio_get_mctrl,389 .stop_tx = mlb_usio_stop_tx,390 .start_tx = mlb_usio_start_tx,391 .stop_rx = mlb_usio_stop_rx,392 .enable_ms = mlb_usio_enable_ms,393 .break_ctl = mlb_usio_break_ctl,394 .startup = mlb_usio_startup,395 .shutdown = mlb_usio_shutdown,396 .set_termios = mlb_usio_set_termios,397 .type = mlb_usio_type,398 .config_port = mlb_usio_config_port,399};400 401#ifdef CONFIG_SERIAL_MILBEAUT_USIO_CONSOLE402 403static void mlb_usio_console_putchar(struct uart_port *port, unsigned char c)404{405 while (!(readb(port->membase + MLB_USIO_REG_SSR) & MLB_USIO_SSR_TDRE))406 cpu_relax();407 408 writew(c, port->membase + MLB_USIO_REG_DR);409}410 411static void mlb_usio_console_write(struct console *co, const char *s,412 unsigned int count)413{414 struct uart_port *port = &mlb_usio_ports[co->index];415 416 uart_console_write(port, s, count, mlb_usio_console_putchar);417}418 419static int __init mlb_usio_console_setup(struct console *co, char *options)420{421 struct uart_port *port;422 int baud = 115200;423 int parity = 'n';424 int flow = 'n';425 int bits = 8;426 427 if (co->index >= CONFIG_SERIAL_MILBEAUT_USIO_PORTS)428 return -ENODEV;429 430 port = &mlb_usio_ports[co->index];431 if (!port->membase)432 return -ENODEV;433 434 435 if (options)436 uart_parse_options(options, &baud, &parity, &bits, &flow);437 438 if (of_property_read_bool(port->dev->of_node, "auto-flow-control"))439 flow = 'r';440 441 return uart_set_options(port, co, baud, parity, bits, flow);442}443 444 445static struct uart_driver mlb_usio_uart_driver;446static struct console mlb_usio_console = {447 .name = USIO_UART_DEV_NAME,448 .write = mlb_usio_console_write,449 .device = uart_console_device,450 .setup = mlb_usio_console_setup,451 .flags = CON_PRINTBUFFER,452 .index = -1,453 .data = &mlb_usio_uart_driver,454};455 456static int __init mlb_usio_console_init(void)457{458 register_console(&mlb_usio_console);459 return 0;460}461console_initcall(mlb_usio_console_init);462 463 464static void mlb_usio_early_console_write(struct console *co, const char *s,465 u_int count)466{467 struct earlycon_device *dev = co->data;468 469 uart_console_write(&dev->port, s, count, mlb_usio_console_putchar);470}471 472static int __init mlb_usio_early_console_setup(struct earlycon_device *device,473 const char *opt)474{475 if (!device->port.membase)476 return -ENODEV;477 device->con->write = mlb_usio_early_console_write;478 return 0;479}480 481OF_EARLYCON_DECLARE(mlb_usio, "socionext,milbeaut-usio-uart",482 mlb_usio_early_console_setup);483 484#define USIO_CONSOLE (&mlb_usio_console)485#else486#define USIO_CONSOLE NULL487#endif488 489static struct uart_driver mlb_usio_uart_driver = {490 .owner = THIS_MODULE,491 .driver_name = USIO_NAME,492 .dev_name = USIO_UART_DEV_NAME,493 .cons = USIO_CONSOLE,494 .nr = CONFIG_SERIAL_MILBEAUT_USIO_PORTS,495};496 497static int mlb_usio_probe(struct platform_device *pdev)498{499 struct clk *clk = devm_clk_get(&pdev->dev, NULL);500 struct uart_port *port;501 struct resource *res;502 int index = 0;503 int ret;504 505 if (IS_ERR(clk)) {506 dev_err(&pdev->dev, "Missing clock\n");507 return PTR_ERR(clk);508 }509 ret = clk_prepare_enable(clk);510 if (ret) {511 dev_err(&pdev->dev, "Clock enable failed: %d\n", ret);512 return ret;513 }514 of_property_read_u32(pdev->dev.of_node, "index", &index);515 port = &mlb_usio_ports[index];516 517 port->private_data = (void *)clk;518 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);519 if (res == NULL) {520 dev_err(&pdev->dev, "Missing regs\n");521 ret = -ENODEV;522 goto failed;523 }524 port->membase = devm_ioremap(&pdev->dev, res->start,525 resource_size(res));526 527 ret = platform_get_irq_byname(pdev, "rx");528 mlb_usio_irq[index][RX] = ret;529 530 ret = platform_get_irq_byname(pdev, "tx");531 mlb_usio_irq[index][TX] = ret;532 533 port->irq = mlb_usio_irq[index][RX];534 port->uartclk = clk_get_rate(clk);535 port->fifosize = 128;536 port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_MILBEAUT_USIO_CONSOLE);537 port->iotype = UPIO_MEM32;538 port->flags = UPF_BOOT_AUTOCONF | UPF_SPD_VHI;539 port->line = index;540 port->ops = &mlb_usio_ops;541 port->dev = &pdev->dev;542 543 ret = uart_add_one_port(&mlb_usio_uart_driver, port);544 if (ret) {545 dev_err(&pdev->dev, "Adding port failed: %d\n", ret);546 goto failed;547 }548 return 0;549 550failed:551 clk_disable_unprepare(clk);552 553 return ret;554}555 556static void mlb_usio_remove(struct platform_device *pdev)557{558 struct uart_port *port = &mlb_usio_ports[pdev->id];559 struct clk *clk = port->private_data;560 561 uart_remove_one_port(&mlb_usio_uart_driver, port);562 clk_disable_unprepare(clk);563}564 565static const struct of_device_id mlb_usio_dt_ids[] = {566 { .compatible = "socionext,milbeaut-usio-uart" },567 { /* sentinel */ }568};569MODULE_DEVICE_TABLE(of, mlb_usio_dt_ids);570 571static struct platform_driver mlb_usio_driver = {572 .probe = mlb_usio_probe,573 .remove_new = mlb_usio_remove,574 .driver = {575 .name = USIO_NAME,576 .of_match_table = mlb_usio_dt_ids,577 },578};579 580static int __init mlb_usio_init(void)581{582 int ret = uart_register_driver(&mlb_usio_uart_driver);583 584 if (ret) {585 pr_err("%s: uart registration failed: %d\n", __func__, ret);586 return ret;587 }588 ret = platform_driver_register(&mlb_usio_driver);589 if (ret) {590 uart_unregister_driver(&mlb_usio_uart_driver);591 pr_err("%s: drv registration failed: %d\n", __func__, ret);592 return ret;593 }594 595 return 0;596}597 598static void __exit mlb_usio_exit(void)599{600 platform_driver_unregister(&mlb_usio_driver);601 uart_unregister_driver(&mlb_usio_uart_driver);602}603 604module_init(mlb_usio_init);605module_exit(mlb_usio_exit);606 607MODULE_AUTHOR("SOCIONEXT");608MODULE_DESCRIPTION("MILBEAUT_USIO/UART Driver");609MODULE_LICENSE("GPL");610