562 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Driver for CLPS711x serial ports4 *5 * Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o.6 *7 * Copyright 1999 ARM Limited8 * Copyright (C) 2000 Deep Blue Solutions Ltd.9 */10 11#include <linux/module.h>12#include <linux/device.h>13#include <linux/console.h>14#include <linux/serial_core.h>15#include <linux/serial.h>16#include <linux/clk.h>17#include <linux/io.h>18#include <linux/tty.h>19#include <linux/tty_flip.h>20#include <linux/ioport.h>21#include <linux/of.h>22#include <linux/platform_device.h>23#include <linux/regmap.h>24 25#include <linux/mfd/syscon.h>26#include <linux/mfd/syscon/clps711x.h>27 28#include "serial_mctrl_gpio.h"29 30#define UART_CLPS711X_DEVNAME "ttyCL"31#define UART_CLPS711X_NR 232#define UART_CLPS711X_MAJOR 20433#define UART_CLPS711X_MINOR 4034 35#define UARTDR_OFFSET (0x00)36#define UBRLCR_OFFSET (0x40)37 38#define UARTDR_FRMERR (1 << 8)39#define UARTDR_PARERR (1 << 9)40#define UARTDR_OVERR (1 << 10)41 42#define UBRLCR_BAUD_MASK ((1 << 12) - 1)43#define UBRLCR_BREAK (1 << 12)44#define UBRLCR_PRTEN (1 << 13)45#define UBRLCR_EVENPRT (1 << 14)46#define UBRLCR_XSTOP (1 << 15)47#define UBRLCR_FIFOEN (1 << 16)48#define UBRLCR_WRDLEN5 (0 << 17)49#define UBRLCR_WRDLEN6 (1 << 17)50#define UBRLCR_WRDLEN7 (2 << 17)51#define UBRLCR_WRDLEN8 (3 << 17)52#define UBRLCR_WRDLEN_MASK (3 << 17)53 54struct clps711x_port {55 struct uart_port port;56 unsigned int tx_enabled;57 int rx_irq;58 struct regmap *syscon;59 struct mctrl_gpios *gpios;60};61 62static struct uart_driver clps711x_uart = {63 .owner = THIS_MODULE,64 .driver_name = UART_CLPS711X_DEVNAME,65 .dev_name = UART_CLPS711X_DEVNAME,66 .major = UART_CLPS711X_MAJOR,67 .minor = UART_CLPS711X_MINOR,68 .nr = UART_CLPS711X_NR,69};70 71static void uart_clps711x_stop_tx(struct uart_port *port)72{73 struct clps711x_port *s = dev_get_drvdata(port->dev);74 75 if (s->tx_enabled) {76 disable_irq(port->irq);77 s->tx_enabled = 0;78 }79}80 81static void uart_clps711x_start_tx(struct uart_port *port)82{83 struct clps711x_port *s = dev_get_drvdata(port->dev);84 85 if (!s->tx_enabled) {86 s->tx_enabled = 1;87 enable_irq(port->irq);88 }89}90 91static irqreturn_t uart_clps711x_int_rx(int irq, void *dev_id)92{93 struct uart_port *port = dev_id;94 struct clps711x_port *s = dev_get_drvdata(port->dev);95 unsigned int status;96 u16 ch;97 u8 flg;98 99 for (;;) {100 u32 sysflg = 0;101 102 regmap_read(s->syscon, SYSFLG_OFFSET, &sysflg);103 if (sysflg & SYSFLG_URXFE)104 break;105 106 ch = readw(port->membase + UARTDR_OFFSET);107 status = ch & (UARTDR_FRMERR | UARTDR_PARERR | UARTDR_OVERR);108 ch &= 0xff;109 110 port->icount.rx++;111 flg = TTY_NORMAL;112 113 if (unlikely(status)) {114 if (status & UARTDR_PARERR)115 port->icount.parity++;116 else if (status & UARTDR_FRMERR)117 port->icount.frame++;118 else if (status & UARTDR_OVERR)119 port->icount.overrun++;120 121 status &= port->read_status_mask;122 123 if (status & UARTDR_PARERR)124 flg = TTY_PARITY;125 else if (status & UARTDR_FRMERR)126 flg = TTY_FRAME;127 else if (status & UARTDR_OVERR)128 flg = TTY_OVERRUN;129 }130 131 if (uart_handle_sysrq_char(port, ch))132 continue;133 134 if (status & port->ignore_status_mask)135 continue;136 137 uart_insert_char(port, status, UARTDR_OVERR, ch, flg);138 }139 140 tty_flip_buffer_push(&port->state->port);141 142 return IRQ_HANDLED;143}144 145static irqreturn_t uart_clps711x_int_tx(int irq, void *dev_id)146{147 struct uart_port *port = dev_id;148 struct clps711x_port *s = dev_get_drvdata(port->dev);149 struct tty_port *tport = &port->state->port;150 unsigned char c;151 152 if (port->x_char) {153 writew(port->x_char, port->membase + UARTDR_OFFSET);154 port->icount.tx++;155 port->x_char = 0;156 return IRQ_HANDLED;157 }158 159 if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) {160 if (s->tx_enabled) {161 disable_irq_nosync(port->irq);162 s->tx_enabled = 0;163 }164 return IRQ_HANDLED;165 }166 167 while (uart_fifo_get(port, &c)) {168 u32 sysflg = 0;169 170 writew(c, port->membase + UARTDR_OFFSET);171 172 regmap_read(s->syscon, SYSFLG_OFFSET, &sysflg);173 if (sysflg & SYSFLG_UTXFF)174 break;175 }176 177 if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)178 uart_write_wakeup(port);179 180 return IRQ_HANDLED;181}182 183static unsigned int uart_clps711x_tx_empty(struct uart_port *port)184{185 struct clps711x_port *s = dev_get_drvdata(port->dev);186 u32 sysflg = 0;187 188 regmap_read(s->syscon, SYSFLG_OFFSET, &sysflg);189 190 return (sysflg & SYSFLG_UBUSY) ? 0 : TIOCSER_TEMT;191}192 193static unsigned int uart_clps711x_get_mctrl(struct uart_port *port)194{195 unsigned int result = TIOCM_DSR | TIOCM_CTS | TIOCM_CAR;196 struct clps711x_port *s = dev_get_drvdata(port->dev);197 198 return mctrl_gpio_get(s->gpios, &result);199}200 201static void uart_clps711x_set_mctrl(struct uart_port *port, unsigned int mctrl)202{203 struct clps711x_port *s = dev_get_drvdata(port->dev);204 205 mctrl_gpio_set(s->gpios, mctrl);206}207 208static void uart_clps711x_break_ctl(struct uart_port *port, int break_state)209{210 unsigned int ubrlcr;211 212 ubrlcr = readl(port->membase + UBRLCR_OFFSET);213 if (break_state)214 ubrlcr |= UBRLCR_BREAK;215 else216 ubrlcr &= ~UBRLCR_BREAK;217 writel(ubrlcr, port->membase + UBRLCR_OFFSET);218}219 220static void uart_clps711x_set_ldisc(struct uart_port *port,221 struct ktermios *termios)222{223 if (!port->line) {224 struct clps711x_port *s = dev_get_drvdata(port->dev);225 226 regmap_update_bits(s->syscon, SYSCON_OFFSET, SYSCON1_SIREN,227 (termios->c_line == N_IRDA) ? SYSCON1_SIREN : 0);228 }229}230 231static int uart_clps711x_startup(struct uart_port *port)232{233 struct clps711x_port *s = dev_get_drvdata(port->dev);234 235 /* Disable break */236 writel(readl(port->membase + UBRLCR_OFFSET) & ~UBRLCR_BREAK,237 port->membase + UBRLCR_OFFSET);238 239 /* Enable the port */240 return regmap_update_bits(s->syscon, SYSCON_OFFSET,241 SYSCON_UARTEN, SYSCON_UARTEN);242}243 244static void uart_clps711x_shutdown(struct uart_port *port)245{246 struct clps711x_port *s = dev_get_drvdata(port->dev);247 248 /* Disable the port */249 regmap_update_bits(s->syscon, SYSCON_OFFSET, SYSCON_UARTEN, 0);250}251 252static void uart_clps711x_set_termios(struct uart_port *port,253 struct ktermios *termios,254 const struct ktermios *old)255{256 u32 ubrlcr;257 unsigned int baud, quot;258 259 /* Mask termios capabilities we don't support */260 termios->c_cflag &= ~CMSPAR;261 termios->c_iflag &= ~(BRKINT | IGNBRK);262 263 /* Ask the core to calculate the divisor for us */264 baud = uart_get_baud_rate(port, termios, old, port->uartclk / 4096,265 port->uartclk / 16);266 quot = uart_get_divisor(port, baud);267 268 switch (termios->c_cflag & CSIZE) {269 case CS5:270 ubrlcr = UBRLCR_WRDLEN5;271 break;272 case CS6:273 ubrlcr = UBRLCR_WRDLEN6;274 break;275 case CS7:276 ubrlcr = UBRLCR_WRDLEN7;277 break;278 case CS8:279 default:280 ubrlcr = UBRLCR_WRDLEN8;281 break;282 }283 284 if (termios->c_cflag & CSTOPB)285 ubrlcr |= UBRLCR_XSTOP;286 287 if (termios->c_cflag & PARENB) {288 ubrlcr |= UBRLCR_PRTEN;289 if (!(termios->c_cflag & PARODD))290 ubrlcr |= UBRLCR_EVENPRT;291 }292 293 /* Enable FIFO */294 ubrlcr |= UBRLCR_FIFOEN;295 296 /* Set read status mask */297 port->read_status_mask = UARTDR_OVERR;298 if (termios->c_iflag & INPCK)299 port->read_status_mask |= UARTDR_PARERR | UARTDR_FRMERR;300 301 /* Set status ignore mask */302 port->ignore_status_mask = 0;303 if (!(termios->c_cflag & CREAD))304 port->ignore_status_mask |= UARTDR_OVERR | UARTDR_PARERR |305 UARTDR_FRMERR;306 307 uart_update_timeout(port, termios->c_cflag, baud);308 309 writel(ubrlcr | (quot - 1), port->membase + UBRLCR_OFFSET);310}311 312static const char *uart_clps711x_type(struct uart_port *port)313{314 return (port->type == PORT_CLPS711X) ? "CLPS711X" : NULL;315}316 317static void uart_clps711x_config_port(struct uart_port *port, int flags)318{319 if (flags & UART_CONFIG_TYPE)320 port->type = PORT_CLPS711X;321}322 323static void uart_clps711x_nop_void(struct uart_port *port)324{325}326 327static int uart_clps711x_nop_int(struct uart_port *port)328{329 return 0;330}331 332static const struct uart_ops uart_clps711x_ops = {333 .tx_empty = uart_clps711x_tx_empty,334 .set_mctrl = uart_clps711x_set_mctrl,335 .get_mctrl = uart_clps711x_get_mctrl,336 .stop_tx = uart_clps711x_stop_tx,337 .start_tx = uart_clps711x_start_tx,338 .stop_rx = uart_clps711x_nop_void,339 .break_ctl = uart_clps711x_break_ctl,340 .set_ldisc = uart_clps711x_set_ldisc,341 .startup = uart_clps711x_startup,342 .shutdown = uart_clps711x_shutdown,343 .set_termios = uart_clps711x_set_termios,344 .type = uart_clps711x_type,345 .config_port = uart_clps711x_config_port,346 .release_port = uart_clps711x_nop_void,347 .request_port = uart_clps711x_nop_int,348};349 350#ifdef CONFIG_SERIAL_CLPS711X_CONSOLE351static void uart_clps711x_console_putchar(struct uart_port *port, unsigned char ch)352{353 struct clps711x_port *s = dev_get_drvdata(port->dev);354 u32 sysflg = 0;355 356 /* Wait for FIFO is not full */357 do {358 regmap_read(s->syscon, SYSFLG_OFFSET, &sysflg);359 } while (sysflg & SYSFLG_UTXFF);360 361 writew(ch, port->membase + UARTDR_OFFSET);362}363 364static void uart_clps711x_console_write(struct console *co, const char *c,365 unsigned n)366{367 struct uart_port *port = clps711x_uart.state[co->index].uart_port;368 struct clps711x_port *s = dev_get_drvdata(port->dev);369 u32 sysflg = 0;370 371 uart_console_write(port, c, n, uart_clps711x_console_putchar);372 373 /* Wait for transmitter to become empty */374 do {375 regmap_read(s->syscon, SYSFLG_OFFSET, &sysflg);376 } while (sysflg & SYSFLG_UBUSY);377}378 379static int uart_clps711x_console_setup(struct console *co, char *options)380{381 int baud = 38400, bits = 8, parity = 'n', flow = 'n';382 int ret, index = co->index;383 struct clps711x_port *s;384 struct uart_port *port;385 unsigned int quot;386 u32 ubrlcr;387 388 if (index < 0 || index >= UART_CLPS711X_NR)389 return -EINVAL;390 391 port = clps711x_uart.state[index].uart_port;392 if (!port)393 return -ENODEV;394 395 s = dev_get_drvdata(port->dev);396 397 if (!options) {398 u32 syscon = 0;399 400 regmap_read(s->syscon, SYSCON_OFFSET, &syscon);401 if (syscon & SYSCON_UARTEN) {402 ubrlcr = readl(port->membase + UBRLCR_OFFSET);403 404 if (ubrlcr & UBRLCR_PRTEN) {405 if (ubrlcr & UBRLCR_EVENPRT)406 parity = 'e';407 else408 parity = 'o';409 }410 411 if ((ubrlcr & UBRLCR_WRDLEN_MASK) == UBRLCR_WRDLEN7)412 bits = 7;413 414 quot = ubrlcr & UBRLCR_BAUD_MASK;415 baud = port->uartclk / (16 * (quot + 1));416 }417 } else418 uart_parse_options(options, &baud, &parity, &bits, &flow);419 420 ret = uart_set_options(port, co, baud, parity, bits, flow);421 if (ret)422 return ret;423 424 return regmap_update_bits(s->syscon, SYSCON_OFFSET,425 SYSCON_UARTEN, SYSCON_UARTEN);426}427 428static struct console clps711x_console = {429 .name = UART_CLPS711X_DEVNAME,430 .device = uart_console_device,431 .write = uart_clps711x_console_write,432 .setup = uart_clps711x_console_setup,433 .flags = CON_PRINTBUFFER,434 .index = -1,435};436#endif437 438static int uart_clps711x_probe(struct platform_device *pdev)439{440 struct device_node *np = pdev->dev.of_node;441 struct clps711x_port *s;442 struct resource *res;443 struct clk *uart_clk;444 int irq, ret;445 446 s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL);447 if (!s)448 return -ENOMEM;449 450 uart_clk = devm_clk_get(&pdev->dev, NULL);451 if (IS_ERR(uart_clk))452 return PTR_ERR(uart_clk);453 454 s->port.membase = devm_platform_get_and_ioremap_resource(pdev, 0, &res);455 if (IS_ERR(s->port.membase))456 return PTR_ERR(s->port.membase);457 458 irq = platform_get_irq(pdev, 0);459 if (irq < 0)460 return irq;461 s->port.irq = irq;462 463 s->rx_irq = platform_get_irq(pdev, 1);464 if (s->rx_irq < 0)465 return s->rx_irq;466 467 s->syscon = syscon_regmap_lookup_by_phandle(np, "syscon");468 if (IS_ERR(s->syscon))469 return PTR_ERR(s->syscon);470 471 s->port.line = of_alias_get_id(np, "serial");472 s->port.dev = &pdev->dev;473 s->port.iotype = UPIO_MEM32;474 s->port.mapbase = res->start;475 s->port.type = PORT_CLPS711X;476 s->port.fifosize = 16;477 s->port.has_sysrq = IS_ENABLED(CONFIG_SERIAL_CLPS711X_CONSOLE);478 s->port.flags = UPF_SKIP_TEST | UPF_FIXED_TYPE;479 s->port.uartclk = clk_get_rate(uart_clk);480 s->port.ops = &uart_clps711x_ops;481 482 platform_set_drvdata(pdev, s);483 484 s->gpios = mctrl_gpio_init_noauto(&pdev->dev, 0);485 if (IS_ERR(s->gpios))486 return PTR_ERR(s->gpios);487 488 ret = uart_add_one_port(&clps711x_uart, &s->port);489 if (ret)490 return ret;491 492 /* Disable port */493 if (!uart_console(&s->port))494 regmap_update_bits(s->syscon, SYSCON_OFFSET, SYSCON_UARTEN, 0);495 496 s->tx_enabled = 1;497 498 ret = devm_request_irq(&pdev->dev, s->port.irq, uart_clps711x_int_tx, 0,499 dev_name(&pdev->dev), &s->port);500 if (ret) {501 uart_remove_one_port(&clps711x_uart, &s->port);502 return ret;503 }504 505 ret = devm_request_irq(&pdev->dev, s->rx_irq, uart_clps711x_int_rx, 0,506 dev_name(&pdev->dev), &s->port);507 if (ret)508 uart_remove_one_port(&clps711x_uart, &s->port);509 510 return ret;511}512 513static void uart_clps711x_remove(struct platform_device *pdev)514{515 struct clps711x_port *s = platform_get_drvdata(pdev);516 517 uart_remove_one_port(&clps711x_uart, &s->port);518}519 520static const struct of_device_id __maybe_unused clps711x_uart_dt_ids[] = {521 { .compatible = "cirrus,ep7209-uart", },522 { }523};524MODULE_DEVICE_TABLE(of, clps711x_uart_dt_ids);525 526static struct platform_driver clps711x_uart_platform = {527 .driver = {528 .name = "clps711x-uart",529 .of_match_table = of_match_ptr(clps711x_uart_dt_ids),530 },531 .probe = uart_clps711x_probe,532 .remove_new = uart_clps711x_remove,533};534 535static int __init uart_clps711x_init(void)536{537 int ret;538 539#ifdef CONFIG_SERIAL_CLPS711X_CONSOLE540 clps711x_uart.cons = &clps711x_console;541 clps711x_console.data = &clps711x_uart;542#endif543 544 ret = uart_register_driver(&clps711x_uart);545 if (ret)546 return ret;547 548 return platform_driver_register(&clps711x_uart_platform);549}550module_init(uart_clps711x_init);551 552static void __exit uart_clps711x_exit(void)553{554 platform_driver_unregister(&clps711x_uart_platform);555 uart_unregister_driver(&clps711x_uart);556}557module_exit(uart_clps711x_exit);558 559MODULE_AUTHOR("Deep Blue Solutions Ltd");560MODULE_DESCRIPTION("CLPS711X serial driver");561MODULE_LICENSE("GPL");562