Files
efinix-linux/drivers/tty/serial/serial_spinal_lib.c
Mohamad Noor Alim Hussin 270244937f drivers: add serial spinal driver
Signed-off-by: Dolu1990 <charles.papon.90@gmail.com>
Signed-off-by: Mohamad Noor Alim Hussin <mnalim@efinixinc.com>
2026-08-09 22:38:51 -07:00

730 lines
18 KiB
C

/*
* Copyright (C) 2023 Efinix Inc. All rights reserved.
*
* SPDX-License-Identifier: GPL-3.0
*
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/console.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/clk.h>
#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 <charles.papon.90@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);