drivers: add serial spinal driver
Signed-off-by: Dolu1990 <charles.papon.90@gmail.com> Signed-off-by: Mohamad Noor Alim Hussin <mnalim@efinixinc.com>
This commit is contained in:
@@ -1583,6 +1583,29 @@ config SERIAL_MILBEAUT_USIO_CONSOLE
|
||||
receives all kernel messages and warnings and which allows logins in
|
||||
single user mode).
|
||||
|
||||
config SERIAL_SPINAL_LIB_UART
|
||||
tristate "Spinal lib UART support"
|
||||
select SERIAL_CORE
|
||||
help
|
||||
This driver supports the spinal lib UART port.
|
||||
|
||||
config SERIAL_SPINAL_LIB_UART_BAUDRATE
|
||||
int "Default baudrate for spinal lib UART ports"
|
||||
depends on SERIAL_SPINAL_LIB_UART
|
||||
default 115200
|
||||
help
|
||||
This setting lets you define what the default baudrate is for the
|
||||
spinal lib UART ports. The usual default varies from board to board,
|
||||
and this setting is a way of catering for that.
|
||||
|
||||
config SERIAL_SPINAL_LIB_UART_CONSOLE
|
||||
bool "Spinal lib UART console support"
|
||||
depends on SERIAL_SPINAL_LIB_UART=y
|
||||
select SERIAL_CORE_CONSOLE
|
||||
select SERIAL_EARLYCON
|
||||
help
|
||||
Enable a spinal lib UART port to be the system console.
|
||||
|
||||
endmenu
|
||||
|
||||
config SERIAL_MCTRL_GPIO
|
||||
|
||||
@@ -90,6 +90,7 @@ obj-$(CONFIG_SERIAL_OWL) += owl-uart.o
|
||||
obj-$(CONFIG_SERIAL_RDA) += rda-uart.o
|
||||
obj-$(CONFIG_SERIAL_MILBEAUT_USIO) += milbeaut_usio.o
|
||||
obj-$(CONFIG_SERIAL_SIFIVE) += sifive.o
|
||||
obj-$(CONFIG_SERIAL_SPINAL_LIB_UART) += serial_spinal_lib.o
|
||||
|
||||
# GPIOLIB helpers for modem control lines
|
||||
obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
|
||||
|
||||
729
drivers/tty/serial/serial_spinal_lib.c
Normal file
729
drivers/tty/serial/serial_spinal_lib.c
Normal file
@@ -0,0 +1,729 @@
|
||||
/*
|
||||
* 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);
|
||||
@@ -279,4 +279,7 @@
|
||||
/* Freescale LINFlexD UART */
|
||||
#define PORT_LINFLEXUART 122
|
||||
|
||||
/* Spinal lib UART */
|
||||
#define PORT_SPINAL_LIB 123
|
||||
|
||||
#endif /* _UAPILINUX_SERIAL_CORE_H */
|
||||
|
||||
Reference in New Issue
Block a user