/* * Copyright (C) 2023 Efinix Inc. All rights reserved. * * SPDX-License-Identifier: GPL-3.0 * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DRV_NAME "spinal_lib_uart" #define SERIAL_SPINAL_LIB_UART_MAXPORTS 4 /* Register Offset */ #define SPINAL_LIB_UART_DATA 0x00 #define SPINAL_LIB_UART_STATUS 0x04 #define SPINAL_LIB_UART_CLOCK_DIVIDER 0x08 #define SPINAL_LIB_UART_FRAME_CONFIG 0x0C #define SPINAL_LIB_UART_FRAME_FLAGS 0x10 #define SPINAL_LIB_UART_STATUS_TX_BUSY 0x8000 #define SPINAL_LIB_UART_FRAME_CONFIG_STOP1 (0 << 16) #define SPINAL_LIB_UART_FRAME_CONFIG_STOP2 (1 << 16) #define SPINAL_LIB_UART_FRAME_CONFIG_ODD (1 << 9) #define SPINAL_LIB_UART_FRAME_CONFIG_EVEN (1 << 8) #define SPINAL_LIB_UART_MISC_READ_OVERFLOW (1 << 1) #define SPINAL_LIB_UART_MISC_BREAK_ACTIVE (1 << 8) #define SPINAL_LIB_UART_MISC_BREAK_DETECTED (1 << 9) #define SPINAL_LIB_UART_MISC_BREAK_ENABLE (1 << 10) #define SPINAL_LIB_UART_MISC_BREAK_DISABLE (1 << 11) #define SPINAL_LIB_UART_CONTROL_TRDY_MSK (1 << 0) #define SPINAL_LIB_UART_CONTROL_RRDY_MSK (1 << 1) #define SPINAL_LIB_UART_STATUS_TRDY_MSK (1 << 8) #define SPINAL_LIB_UART_STATUS_RRDY_MSK (1 << 9) /* Depth of TX/RX FIFO (in bytes) */ #define SPINAL_LIB_UART_FIFO_DEPTH 128 #undef pr_fmt #define pr_fmt(fmt) "%s : " fmt, __func__ /* * Local per-uart structure. */ struct spinal_lib_uart { struct device *dev; struct uart_port port; struct timer_list tmr; u32 break_on_rx; u32 imr; }; static struct spinal_lib_uart *spinal_lib_uart_ports[SERIAL_SPINAL_LIB_UART_MAXPORTS]; static u32 spinal_lib_uart_tx_availability(struct uart_port *port) { return (readl(port->membase + SPINAL_LIB_UART_STATUS) >> 16) & 0xFF; } static u32 spinal_lib_uart_rx_occupancy(struct uart_port *port) { return (readl(port->membase + SPINAL_LIB_UART_STATUS) >> 24) & 0xFF; } static void spinal_lib_uart_tx(struct uart_port *port, char ch) { writel(ch, port->membase + SPINAL_LIB_UART_DATA); } static inline void spinal_lib_uart_writel(struct uart_port *port, u32 data, u32 reg) { writel(data, port->membase + reg); } static inline u32 spinal_lib_uart_readl(struct uart_port *port, u32 reg) { return readl(port->membase + reg); } static void spinal_lib_uart_update_ctrl_reg(struct spinal_lib_uart *pp) { unsigned short imr = pp->imr; /* * If the device doesn't have an irq, ensure that the irq bits are * masked out to keep the irq line inactive. */ spinal_lib_uart_writel(&pp->port, imr, SPINAL_LIB_UART_STATUS); } static inline void spinal_lib_uart_disable_tx_interrupt(struct spinal_lib_uart *pp) { pp->imr &= ~SPINAL_LIB_UART_CONTROL_TRDY_MSK; spinal_lib_uart_update_ctrl_reg(pp); } static inline void spinal_lib_uart_disable_rx_interrupt(struct spinal_lib_uart *pp) { pp->imr &= ~SPINAL_LIB_UART_CONTROL_RRDY_MSK; spinal_lib_uart_update_ctrl_reg(pp); } static inline void spinal_lib_uart_enable_tx_interrupt(struct spinal_lib_uart *pp) { pp->imr |= SPINAL_LIB_UART_CONTROL_TRDY_MSK; spinal_lib_uart_update_ctrl_reg(pp); } static inline void spinal_lib_uart_enable_rx_interrupt(struct spinal_lib_uart *pp) { pp->imr |= SPINAL_LIB_UART_CONTROL_RRDY_MSK; spinal_lib_uart_update_ctrl_reg(pp); } static unsigned int spinal_lib_uart_tx_empty(struct uart_port *port) { return (spinal_lib_uart_tx_availability(port) & SPINAL_LIB_UART_STATUS_TX_BUSY) ? 0 : TIOCSER_TEMT; } static unsigned int spinal_lib_uart_get_mctrl(struct uart_port *port) { return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS; } static void spinal_lib_uart_set_mctrl(struct uart_port *port, unsigned int sigs) { } static void spinal_lib_uart_stop_tx(struct uart_port *port) { struct spinal_lib_uart *pp = container_of(port, struct spinal_lib_uart, port); spinal_lib_uart_disable_tx_interrupt(pp); } static void spinal_lib_uart_stop_rx(struct uart_port *port) { struct spinal_lib_uart *pp = container_of(port, struct spinal_lib_uart, port); spinal_lib_uart_disable_rx_interrupt(pp); } static void spinal_lib_uart_break_ctl(struct uart_port *port, int break_state) { unsigned long flags; spin_lock_irqsave(&port->lock, flags); writel(break_state != 0 ? SPINAL_LIB_UART_MISC_BREAK_ENABLE : SPINAL_LIB_UART_MISC_BREAK_DISABLE, port->membase + SPINAL_LIB_UART_FRAME_FLAGS); spin_unlock_irqrestore(&port->lock, flags); } static void spinal_lib_uart_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) { unsigned long flags; unsigned int baud, baudclk; unsigned int config = 0; baud = uart_get_baud_rate(port, termios, old, 0, 4000000); switch(termios->c_cflag & CSIZE) { case CS5: config |= 4; break; case CS6: config |= 5; break; case CS7: config |= 6; break; case CS8: config |= 7; break; } baudclk = port->uartclk / (baud * (config + 1)) - 1; if (termios->c_cflag & CSTOPB) config |= SPINAL_LIB_UART_FRAME_CONFIG_STOP2; else config |= SPINAL_LIB_UART_FRAME_CONFIG_STOP1; if (termios->c_cflag & PARODD) config |= SPINAL_LIB_UART_FRAME_CONFIG_ODD; if (termios->c_cflag & PARENB) config |= SPINAL_LIB_UART_FRAME_CONFIG_EVEN; spinal_lib_uart_writel(port, baudclk, SPINAL_LIB_UART_CLOCK_DIVIDER); spinal_lib_uart_writel(port, config, SPINAL_LIB_UART_FRAME_CONFIG); spin_lock_irqsave(&port->lock, flags); uart_update_timeout(port, termios->c_cflag, baud); spin_unlock_irqrestore(&port->lock, flags); } static void spinal_lib_uart_rx_chars(struct spinal_lib_uart *pp) { struct uart_port *port = &pp->port; unsigned char ch, flag; u32 count; u32 flags; flags = spinal_lib_uart_readl(port, SPINAL_LIB_UART_FRAME_FLAGS); if(!pp->break_on_rx){ if(flags & SPINAL_LIB_UART_MISC_BREAK_DETECTED){ spinal_lib_uart_writel(port, SPINAL_LIB_UART_MISC_BREAK_DETECTED, SPINAL_LIB_UART_FRAME_FLAGS); pp->break_on_rx = 1; port->icount.brk++; uart_handle_break(port); } } else { if((flags & SPINAL_LIB_UART_MISC_BREAK_ACTIVE) == 0){ pp->break_on_rx = 0; } } if(!pp->break_on_rx) while((count = spinal_lib_uart_rx_occupancy(port))){ while(count--){ ch = spinal_lib_uart_readl(port, SPINAL_LIB_UART_DATA); flag = TTY_NORMAL; port->icount.rx++; if (uart_handle_sysrq_char(port, ch)) continue; uart_insert_char(port, 0, 0, ch, flag); } } spin_unlock(&port->lock); tty_flip_buffer_push(&port->state->port); spin_lock(&port->lock); } static void spinal_lib_uart_tx_chars(struct spinal_lib_uart *pp) { struct uart_port *port = &pp->port; struct circ_buf *xmit = &port->state->xmit; unsigned int numbytes; numbytes = SPINAL_LIB_UART_FIFO_DEPTH; if (port->x_char && spinal_lib_uart_tx_availability(port)) { /* Send special char - probably flow control */ spinal_lib_uart_tx(port, port->x_char); port->x_char = 0; port->icount.tx++; return; } if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { spinal_lib_uart_stop_tx(port); return; } while (numbytes && !uart_circ_empty(xmit) && spinal_lib_uart_tx_availability(port)) { spinal_lib_uart_tx(port, xmit->buf[xmit->tail]); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); port->icount.tx++; numbytes--; } if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(port); if (uart_circ_empty(xmit)) spinal_lib_uart_stop_tx(port); else spinal_lib_uart_enable_tx_interrupt(pp); } static void spinal_lib_uart_start_tx(struct uart_port *port) { struct spinal_lib_uart *pp = container_of(port, struct spinal_lib_uart, port); spinal_lib_uart_enable_tx_interrupt(pp); } static irqreturn_t spinal_lib_uart_interrupt(int irq, void *data) { struct uart_port *port = data; struct spinal_lib_uart *pp = container_of(port, struct spinal_lib_uart, port); unsigned int isr; isr = spinal_lib_uart_readl(port, SPINAL_LIB_UART_STATUS); /* Disable Tx/Rx interrupt */ spinal_lib_uart_disable_tx_interrupt(pp); spinal_lib_uart_disable_rx_interrupt(pp); spin_lock(&port->lock); if (isr & SPINAL_LIB_UART_STATUS_RRDY_MSK) spinal_lib_uart_rx_chars(pp); if (isr & SPINAL_LIB_UART_STATUS_TRDY_MSK) spinal_lib_uart_tx_chars(pp); spin_unlock(&port->lock); /* Enable Rx interrupt */ spinal_lib_uart_enable_rx_interrupt(pp); return IRQ_RETVAL(isr); } static void spinal_lib_uart_timer(struct timer_list *t) { struct spinal_lib_uart *pp = from_timer(pp, t, tmr); struct uart_port *port = &pp->port; spinal_lib_uart_interrupt(0, port); mod_timer(&pp->tmr, jiffies + uart_poll_timeout(port)); } static void spinal_lib_uart_config_port(struct uart_port *port, int flags) { port->type = PORT_SPINAL_LIB; if (flags & UART_CONFIG_TYPE) port->type = PORT_SPINAL_LIB; } static int spinal_lib_uart_startup(struct uart_port *port) { struct spinal_lib_uart *pp = container_of(port, struct spinal_lib_uart, port); unsigned long flags; if (!port->irq) { timer_setup(&pp->tmr, spinal_lib_uart_timer, 0); mod_timer(&pp->tmr, jiffies + uart_poll_timeout(port)); } spin_lock_irqsave(&port->lock, flags); /* Enable RX interrupts now */ spinal_lib_uart_enable_rx_interrupt(pp); spin_unlock_irqrestore(&port->lock, flags); return 0; } static void spinal_lib_uart_shutdown(struct uart_port *port) { struct spinal_lib_uart *pp = container_of(port, struct spinal_lib_uart, port); unsigned long flags; spin_lock_irqsave(&port->lock, flags); /* Disable all interrupts now */ spinal_lib_uart_disable_tx_interrupt(pp); spinal_lib_uart_disable_rx_interrupt(pp); spin_unlock_irqrestore(&port->lock, flags); if (!port->irq) del_timer_sync(&pp->tmr); } static const char *spinal_lib_uart_type(struct uart_port *port) { return (port->type == PORT_SPINAL_LIB) ? "Spinal lib UART" : NULL; } static int spinal_lib_uart_request_port(struct uart_port *port) { /* UARTs always present */ return 0; } static void spinal_lib_uart_release_port(struct uart_port *port) { /* Nothing to release... */ } static int spinal_lib_uart_verify_port(struct uart_port *port, struct serial_struct *ser) { if ((ser->type != PORT_UNKNOWN) && (ser->type != PORT_SPINAL_LIB)) return -EINVAL; return 0; } #ifdef CONFIG_CONSOLE_POLL static int spinal_lib_uart_poll_get_char(struct uart_port *port) { while (!spinal_lib_uart_rx_occupancy(port)) return NO_POLL_CHAR; return spinal_lib_uart_readl(port, SPINAL_LIB_UART_DATA); } static void spinal_lib_uart_poll_put_char(struct uart_port *port, unsigned char c) { while (!spinal_lib_uart_tx_availability(port)) cpu_relax(); spinal_lib_uart_writel(port, c, SPINAL_LIB_UART_DATA); } #endif /* * Define the basic serial functions we support. */ static const struct uart_ops spinal_lib_uart_ops = { .tx_empty = spinal_lib_uart_tx_empty, .get_mctrl = spinal_lib_uart_get_mctrl, .set_mctrl = spinal_lib_uart_set_mctrl, .start_tx = spinal_lib_uart_start_tx, .stop_tx = spinal_lib_uart_stop_tx, .stop_rx = spinal_lib_uart_stop_rx, .break_ctl = spinal_lib_uart_break_ctl, .startup = spinal_lib_uart_startup, .shutdown = spinal_lib_uart_shutdown, .set_termios = spinal_lib_uart_set_termios, .type = spinal_lib_uart_type, .request_port = spinal_lib_uart_request_port, .release_port = spinal_lib_uart_release_port, .config_port = spinal_lib_uart_config_port, .verify_port = spinal_lib_uart_verify_port, #ifdef CONFIG_CONSOLE_POLL .poll_get_char = spinal_lib_uart_poll_get_char, .poll_put_char = spinal_lib_uart_poll_put_char, #endif }; #ifdef CONFIG_SERIAL_SPINAL_LIB_UART_CONSOLE static void spinal_lib_uart_console_putc(struct uart_port *port, int c) { while (!spinal_lib_uart_tx_availability(port)) cpu_relax(); spinal_lib_uart_writel(port, c, SPINAL_LIB_UART_DATA); } static void spinal_lib_uart_console_write(struct console *co, const char *s, unsigned int count) { struct spinal_lib_uart *pp = spinal_lib_uart_ports[co->index]; unsigned long flags; int locked = 1; if (!pp) return; local_irq_save(flags); if (pp->port.sysrq) locked = 0; else if (oops_in_progress) locked = spin_trylock(&pp->port.lock); else spin_lock(&pp->port.lock); uart_console_write(&pp->port, s, count, spinal_lib_uart_console_putc); if (locked) spin_unlock(&pp->port.lock); local_irq_restore(flags); } static int __init spinal_lib_uart_console_setup(struct console *co, char *options) { struct spinal_lib_uart *pp; int baud = CONFIG_SERIAL_SPINAL_LIB_UART_BAUDRATE; int bits = 8; int parity = 'n'; int flow = 'n'; if (co->index < 0 || co->index >= SERIAL_SPINAL_LIB_UART_MAXPORTS) return -ENODEV; pp = spinal_lib_uart_ports[co->index]; if (!pp) return -ENODEV; if (options) uart_parse_options(options, &baud, &parity, &bits, &flow); return uart_set_options(&pp->port, co, baud, parity, bits, flow); } static struct uart_driver spinal_lib_uart_driver; static struct console spinal_lib_uart_console = { .name = "ttySL", .write = spinal_lib_uart_console_write, .device = uart_console_device, .setup = spinal_lib_uart_console_setup, .flags = CON_PRINTBUFFER, .index = -1, .data = &spinal_lib_uart_driver, }; static int __init spinal_lib_uart_console_init(void) { register_console(&spinal_lib_uart_console); return 0; } console_initcall(spinal_lib_uart_console_init); #define SPINAL_LIB_UART_CONSOLE (&spinal_lib_uart_console) static void spinal_lib_uart_earlycon_write(struct console *co, const char *s, unsigned int count) { struct earlycon_device *dev = co->data; uart_console_write(&dev->port, s, count, spinal_lib_uart_console_putc); } static int __init spinal_lib_uart_earlycon_setup(struct earlycon_device *dev, const char *options) { struct uart_port *port = &dev->port; unsigned int baudclk; int bits = 8; int parity = 'n'; int flow = 'n'; unsigned int config = 0; if (!port->membase) return -ENODEV; if (options) uart_parse_options(options, &dev->baud, &parity, &bits, &flow); switch (parity) { case 'e': config |= SPINAL_LIB_UART_FRAME_CONFIG_EVEN; break; case 'o': config |= SPINAL_LIB_UART_FRAME_CONFIG_ODD; break; default: break; } config |= (bits - 1); if (dev->baud) { baudclk = port->uartclk / (dev->baud * bits) - 1; spinal_lib_uart_writel(port, baudclk, SPINAL_LIB_UART_CLOCK_DIVIDER); spinal_lib_uart_writel(port, config, SPINAL_LIB_UART_FRAME_CONFIG); } dev->con->write = spinal_lib_uart_earlycon_write; return 0; } OF_EARLYCON_DECLARE(sluart, "spinal-lib,uart-2.0", spinal_lib_uart_earlycon_setup); #else #define SPINAL_LIB_UART_CONSOLE NULL #endif /* CONFIG_SERIAL_SPINAL_LIB_UART_CONSOLE */ /* * Define the spinal_lib_uart UART driver structure. */ static struct uart_driver spinal_lib_uart_driver = { .owner = THIS_MODULE, .driver_name = DRV_NAME, .dev_name = "ttySL", .nr = SERIAL_SPINAL_LIB_UART_MAXPORTS, .cons = SPINAL_LIB_UART_CONSOLE, }; static int spinal_lib_uart_probe(struct platform_device *pdev) { struct resource *res_mem; struct uart_port *port; struct device *dev = &pdev->dev; int ret; struct spinal_lib_uart *pp; struct clk *clk; int irq; u32 hz; if (pdev->dev.of_node) { pdev->id = of_alias_get_id(pdev->dev.of_node, "serial"); if (pdev->id < 0) pdev->id = of_alias_get_id(pdev->dev.of_node, "uart"); } if (pdev->id < 0 || pdev->id >= SERIAL_SPINAL_LIB_UART_MAXPORTS) { return -ENODEV; } pp = devm_kzalloc(&pdev->dev, sizeof(*pp), GFP_KERNEL); if (!pp) { dev_err(dev, "out of memory\n"); return -ENOMEM; } spinal_lib_uart_ports[pdev->id] = pp; pp->dev = &pdev->dev; pp->break_on_rx = 0; port = &pp->port; res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res_mem) port->mapbase = res_mem->start; else return -ENODEV; port->membase = devm_ioremap_resource(&pdev->dev, res_mem); if (IS_ERR(port->membase)) return PTR_ERR(port->membase); irq = platform_get_irq(pdev, 0); port->irq = irq; ret = devm_request_irq(pp->dev, port->irq, spinal_lib_uart_interrupt, port->irqflags, DRV_NAME, port); if (ret) { port->irq = 0; pr_err(DRV_NAME ": unable to attach Spinal Lib UART %d " "interrupt vector=%d\n", port->line, port->irq); } clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(clk)) { dev_info(&pdev->dev, "No peripheral clock\n"); goto exit; } hz = clk_get_rate(clk); if(!hz){ dev_info(&pdev->dev, "Bad frequancy\n"); goto exit; } port->uartclk = hz; port->fifosize = SPINAL_LIB_UART_FIFO_DEPTH; port->regshift = 0; port->line = pdev->id; port->type = PORT_SPINAL_LIB; port->iotype = SERIAL_IO_MEM; port->ops = &spinal_lib_uart_ops; port->flags = UPF_BOOT_AUTOCONF; port->dev = &pdev->dev; port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_SPINAL_LIB_UART_CONSOLE); platform_set_drvdata(pdev, port); ret = uart_add_one_port(&spinal_lib_uart_driver, port); if (ret) { spinal_lib_uart_ports[pdev->id]->port.membase = NULL; return ret; } return 0; exit: return -EINVAL; } static int spinal_lib_uart_remove(struct platform_device *pdev) { struct uart_port *port = platform_get_drvdata(pdev); if (port) { uart_remove_one_port(&spinal_lib_uart_driver, port); spinal_lib_uart_ports[pdev->id]->port.membase = NULL; } return 0; } #ifdef CONFIG_OF static const struct of_device_id spinal_lib_uart_match[] = { { .compatible = "spinal-lib,uart-2.0", }, {}, }; MODULE_DEVICE_TABLE(of, spinal_lib_uart_match); #endif /* CONFIG_OF */ static struct platform_driver spinal_lib_uart_platform_driver = { .probe = spinal_lib_uart_probe, .remove = spinal_lib_uart_remove, .driver = { .name = DRV_NAME, .of_match_table = of_match_ptr(spinal_lib_uart_match), }, }; static int __init spinal_lib_uart_init(void) { int ret; ret = uart_register_driver(&spinal_lib_uart_driver); if (ret < 0) { pr_err("Could not register %s driver\n", spinal_lib_uart_driver.driver_name); return ret; } ret = platform_driver_register(&spinal_lib_uart_platform_driver); if (ret < 0) { pr_err("Uart platform driver register failed, e = %d\n", ret); uart_unregister_driver(&spinal_lib_uart_driver); return ret; } return 0; } static void __exit spinal_lib_uart_exit(void) { platform_driver_unregister(&spinal_lib_uart_platform_driver); uart_unregister_driver(&spinal_lib_uart_driver); } module_init(spinal_lib_uart_init); module_exit(spinal_lib_uart_exit); MODULE_DESCRIPTION("Spinal lib UART driver"); MODULE_AUTHOR("Charles Papon "); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:" DRV_NAME);