mirror of https://gitee.com/openkylin/linux.git
Revert "tty: serial: Add UART driver for Cortina-Access platform"
This reverts commit b61c8bf469
. It never
made it to a public mailing list and still needs some work based on the
review comments. So revert it for now.
Reported-by: Geert Uytterhoeven <geert@linux-m68k.org>
Link: https://lore.kernel.org/r/CAMuHMdXA9-ajoAza2JAW5879ECieMm1dbBbKHgJhDa7=3kWu3w@mail.gmail.com
Cc: Jason Li <jason.li@cortina-access.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
b61c8bf469
commit
cddd53e8aa
|
@ -4680,11 +4680,6 @@ S: Maintained
|
|||
W: http://www.fi.muni.cz/~kas/cosa/
|
||||
F: drivers/net/wan/cosa*
|
||||
|
||||
CORTINA-ACCESS SERIAL CONSOLE DRIVER
|
||||
M: Jason Li <jason.li@cortina-access.com>
|
||||
S: Supported
|
||||
F: drivers/tty/serial/serial_cortina-access.c
|
||||
|
||||
COUNTER SUBSYSTEM
|
||||
M: William Breathitt Gray <vilhelm.gray@gmail.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
|
|
|
@ -1561,25 +1561,6 @@ config SERIAL_LITEUART_CONSOLE
|
|||
and warnings and which allows logins in single user mode).
|
||||
Otherwise, say 'N'.
|
||||
|
||||
config SERIAL_CORTINA_ACCESS
|
||||
tristate "Cortina-Access serial port support"
|
||||
select SERIAL_CORE
|
||||
help
|
||||
This driver is for Cortina-Access SoC's UART. If you have a machine
|
||||
based on the Cortina-Access SoC and wish to use the serial port,
|
||||
say 'Y' here. Otherwise, say 'N'.
|
||||
|
||||
config SERIAL_CORTINA_ACCESS_CONSOLE
|
||||
bool "Console on Cortina-ACCESS serial port"
|
||||
depends on SERIAL_CORTINA_ACCESS=y
|
||||
select SERIAL_CORE_CONSOLE
|
||||
select SERIAL_EARLYCON
|
||||
help
|
||||
Say 'Y' here if you wish to use Cortina-Access UART as the system
|
||||
console. (the system console is the device which receives all kernel
|
||||
messages and warnings and which allows logins in single user mode)
|
||||
/dev/ttyS* is default device node.
|
||||
|
||||
endmenu
|
||||
|
||||
config SERIAL_MCTRL_GPIO
|
||||
|
|
|
@ -87,7 +87,6 @@ obj-$(CONFIG_SERIAL_RDA) += rda-uart.o
|
|||
obj-$(CONFIG_SERIAL_MILBEAUT_USIO) += milbeaut_usio.o
|
||||
obj-$(CONFIG_SERIAL_SIFIVE) += sifive.o
|
||||
obj-$(CONFIG_SERIAL_LITEUART) += liteuart.o
|
||||
obj-$(CONFIG_SERIAL_CORTINA_ACCESS) += serial_cortina-access.o
|
||||
|
||||
# GPIOLIB helpers for modem control lines
|
||||
obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
|
||||
|
|
|
@ -1,798 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* UART driver for Cortina-Access Soc platform
|
||||
* Copyright (C) 2021 Cortina-Access Inc.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/tty_flip.h>
|
||||
#include <linux/serial.h>
|
||||
#include <linux/sysrq.h>
|
||||
#include <linux/console.h>
|
||||
#include <linux/serial_core.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
|
||||
/***************************************
|
||||
* UART Related registers
|
||||
****************************************/
|
||||
/* register definitions */
|
||||
#define CFG 0x00
|
||||
#define FC 0x04
|
||||
#define RX_SAMPLE 0x08
|
||||
#define RT_TUNE 0x0C
|
||||
#define TX_DAT 0x10
|
||||
#define RX_DAT 0x14
|
||||
#define INFO 0x18
|
||||
#define IE 0x1C
|
||||
#define INT 0x24
|
||||
#define STATUS 0x2C
|
||||
|
||||
/* CFG */
|
||||
#define CFG_STOP_2BIT BIT(2)
|
||||
#define CFG_PARITY_EVEN BIT(3)
|
||||
#define CFG_PARITY_EN BIT(4)
|
||||
#define CFG_TX_EN BIT(5)
|
||||
#define CFG_RX_EN BIT(6)
|
||||
#define CFG_UART_EN BIT(7)
|
||||
#define CFG_BAUD_SART_SHIFT 8
|
||||
|
||||
/* INFO */
|
||||
#define INFO_TX_EMPTY BIT(3)
|
||||
#define INFO_TX_FULL BIT(2)
|
||||
#define INFO_RX_EMPTY BIT(1)
|
||||
#define INFO_RX_FULL BIT(0)
|
||||
|
||||
/* Interrupt */
|
||||
#define RX_BREAK BIT(7)
|
||||
#define RX_FIFO_NONEMPTYE BIT(6)
|
||||
#define TX_FIFO_EMPTYE BIT(5)
|
||||
#define RX_FIFO_UNDERRUNE BIT(4)
|
||||
#define RX_FIFO_OVERRUNE BIT(3)
|
||||
#define RX_PARITY_ERRE BIT(2)
|
||||
#define RX_STOP_ERRE BIT(1)
|
||||
#define TX_FIFO_OVERRUNE BIT(0)
|
||||
|
||||
#define TX_TIMEOUT 5000
|
||||
#define UART_NR 4
|
||||
#define CA_UART_NAME_LEN 32
|
||||
struct cortina_uart_port {
|
||||
struct uart_port uart;
|
||||
char name[CA_UART_NAME_LEN];
|
||||
char has_bi;
|
||||
unsigned int may_wakeup;
|
||||
};
|
||||
|
||||
static struct cortina_uart_port *cortina_uart_ports;
|
||||
|
||||
static irqreturn_t cortina_uart_interrupt(int irq, void *dev_id);
|
||||
|
||||
/* Return uart_port pointer base on index */
|
||||
struct cortina_uart_port *cortina_uart_get_port(unsigned int index)
|
||||
{
|
||||
struct cortina_uart_port *pca_port = cortina_uart_ports;
|
||||
|
||||
if (index >= UART_NR) {
|
||||
/* return 1st element if invalid index */
|
||||
index = 0;
|
||||
}
|
||||
|
||||
pca_port += index;
|
||||
|
||||
return pca_port;
|
||||
}
|
||||
|
||||
/* uart_ops functions */
|
||||
static unsigned int cortina_uart_tx_empty(struct uart_port *port)
|
||||
{
|
||||
/* Return 0 on FIXO condition, TIOCSER_TEMT otherwise */
|
||||
return (readl(port->membase + INFO) & INFO_TX_EMPTY) ? TIOCSER_TEMT : 0;
|
||||
}
|
||||
|
||||
static void cortina_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
||||
{
|
||||
/*
|
||||
* Even if we do not support configuring the modem control lines, this
|
||||
* function must be proided to the serial core.
|
||||
* port->ops->set_mctrl() be called in uart_configure_port()
|
||||
*/
|
||||
}
|
||||
|
||||
static unsigned int cortina_uart_get_mctrl(struct uart_port *port)
|
||||
{
|
||||
/* Unimplemented signals asserted, per Documentation/serial/driver */
|
||||
return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR;
|
||||
}
|
||||
|
||||
static void cortina_uart_stop_tx(struct uart_port *port)
|
||||
{
|
||||
/* Turn off Tx interrupts. The port lock is held at this point */
|
||||
unsigned int reg_v;
|
||||
|
||||
reg_v = readl(port->membase + IE);
|
||||
writel(reg_v & ~TX_FIFO_EMPTYE, port->membase + IE);
|
||||
}
|
||||
|
||||
static inline void cortina_transmit_buffer(struct uart_port *port)
|
||||
{
|
||||
struct circ_buf *xmit = &port->state->xmit;
|
||||
|
||||
if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
|
||||
cortina_uart_stop_tx(port);
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
/* send xmit->buf[xmit->tail] out the port here */
|
||||
writel(xmit->buf[xmit->tail], port->membase + TX_DAT);
|
||||
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
|
||||
port->icount.tx++;
|
||||
if ((readl(port->membase + INFO) & INFO_TX_FULL))
|
||||
break;
|
||||
} while (!uart_circ_empty(xmit));
|
||||
|
||||
if (uart_circ_empty(xmit))
|
||||
cortina_uart_stop_tx(port);
|
||||
}
|
||||
|
||||
static void cortina_uart_start_tx(struct uart_port *port)
|
||||
{
|
||||
/* Turn on Tx interrupts. The port lock is held at this point */
|
||||
unsigned int reg_v;
|
||||
|
||||
reg_v = readl(port->membase + IE);
|
||||
writel((reg_v | TX_FIFO_EMPTYE), port->membase + IE);
|
||||
|
||||
reg_v = readl(port->membase + CFG);
|
||||
writel(reg_v | CFG_TX_EN, port->membase + CFG);
|
||||
|
||||
if (readl(port->membase + INFO) & INFO_TX_EMPTY)
|
||||
cortina_transmit_buffer(port);
|
||||
}
|
||||
|
||||
static void cortina_uart_stop_rx(struct uart_port *port)
|
||||
{
|
||||
/* Turn off Rx interrupts. The port lock is held at this point */
|
||||
unsigned int reg_v;
|
||||
|
||||
reg_v = readl(port->membase + IE);
|
||||
writel(reg_v & ~RX_FIFO_NONEMPTYE, port->membase + IE);
|
||||
}
|
||||
|
||||
static void cortina_uart_enable_ms(struct uart_port *port)
|
||||
{
|
||||
/* Nope, you really can't hope to attach a modem to this */
|
||||
}
|
||||
|
||||
static int cortina_uart_startup(struct uart_port *port)
|
||||
{
|
||||
unsigned int reg_v;
|
||||
int retval;
|
||||
unsigned long flags;
|
||||
|
||||
/* Disable interrupt */
|
||||
writel(0, port->membase + IE);
|
||||
|
||||
retval =
|
||||
request_irq(port->irq, cortina_uart_interrupt, 0, "cortina_uart",
|
||||
port);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
|
||||
reg_v = readl(port->membase + CFG);
|
||||
reg_v |= (CFG_UART_EN | CFG_TX_EN | CFG_RX_EN | 0x3 /* 8-bits data */);
|
||||
writel(reg_v, port->membase + CFG);
|
||||
reg_v = readl(port->membase + IE);
|
||||
writel(reg_v | RX_FIFO_NONEMPTYE | TX_FIFO_EMPTYE, port->membase + IE);
|
||||
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cortina_uart_shutdown(struct uart_port *port)
|
||||
{
|
||||
cortina_uart_stop_tx(port);
|
||||
cortina_uart_stop_rx(port);
|
||||
free_irq(port->irq, port);
|
||||
}
|
||||
|
||||
static void cortina_uart_set_termios(struct uart_port *port,
|
||||
struct ktermios *termios,
|
||||
struct ktermios *old)
|
||||
{
|
||||
unsigned long flags;
|
||||
int baud;
|
||||
unsigned int reg_v, sample_freq = 0;
|
||||
|
||||
baud = uart_get_baud_rate(port, termios, old, 0, 230400);
|
||||
reg_v = readl(port->membase + CFG);
|
||||
/* mask off the baud settings */
|
||||
reg_v &= 0xff;
|
||||
reg_v |= (port->uartclk / baud) << CFG_BAUD_SART_SHIFT;
|
||||
|
||||
/* Sampling rate should be half of baud count */
|
||||
sample_freq = (reg_v >> CFG_BAUD_SART_SHIFT) / 2;
|
||||
|
||||
/* See include/uapi/asm-generic/termbits.h for CSIZE definition */
|
||||
/* mask off the data width */
|
||||
reg_v &= 0xfffffffc;
|
||||
switch (termios->c_cflag & CSIZE) {
|
||||
case CS5:
|
||||
reg_v |= 0x0;
|
||||
break;
|
||||
case CS6:
|
||||
reg_v |= 0x1;
|
||||
break;
|
||||
case CS7:
|
||||
reg_v |= 0x2;
|
||||
break;
|
||||
case CS8:
|
||||
default:
|
||||
reg_v |= 0x3;
|
||||
break;
|
||||
}
|
||||
|
||||
/* mask off Stop bits */
|
||||
reg_v &= ~(CFG_STOP_2BIT);
|
||||
if (termios->c_cflag & CSTOPB)
|
||||
reg_v |= CFG_STOP_2BIT;
|
||||
|
||||
/* Parity */
|
||||
reg_v &= ~(CFG_PARITY_EN);
|
||||
reg_v |= CFG_PARITY_EVEN;
|
||||
if (termios->c_cflag & PARENB) {
|
||||
reg_v |= CFG_PARITY_EN;
|
||||
if (termios->c_cflag & PARODD)
|
||||
reg_v &= ~(CFG_PARITY_EVEN);
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
writel(reg_v, port->membase + CFG);
|
||||
writel(sample_freq, port->membase + RX_SAMPLE);
|
||||
spin_unlock_irqrestore(&port->lock, flags);
|
||||
}
|
||||
|
||||
static const char *cortina_uart_type(struct uart_port *port)
|
||||
{
|
||||
return container_of(port, struct cortina_uart_port, uart)->name;
|
||||
}
|
||||
|
||||
static void cortina_uart_config_port(struct uart_port *port, int flags)
|
||||
{
|
||||
/*
|
||||
* Driver core for serial ports forces a non-zero value for port type.
|
||||
* Write an arbitrary value here to accommodate the serial core driver,
|
||||
* as ID part of UAPI is redundant.
|
||||
*/
|
||||
port->type = 1;
|
||||
}
|
||||
|
||||
static int cortina_uart_verify_port(struct uart_port *port,
|
||||
struct serial_struct *ser)
|
||||
{
|
||||
if (ser->type != PORT_UNKNOWN && ser->type != 1)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cortina_access_power(struct uart_port *port, unsigned int state,
|
||||
unsigned int oldstate)
|
||||
{
|
||||
unsigned int reg_v;
|
||||
|
||||
/* Read Config register */
|
||||
reg_v = readl(port->membase + CFG);
|
||||
switch (state) {
|
||||
case UART_PM_STATE_ON:
|
||||
reg_v |= CFG_UART_EN;
|
||||
break;
|
||||
case UART_PM_STATE_OFF:
|
||||
reg_v &= ~CFG_UART_EN;
|
||||
break;
|
||||
default:
|
||||
pr_err("cortina-access serial: Unknown PM state %d\n", state);
|
||||
}
|
||||
writel(reg_v, port->membase + CFG);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CONSOLE_POLL
|
||||
static int cortina_poll_get_char(struct uart_port *port)
|
||||
{
|
||||
unsigned int rx;
|
||||
|
||||
if (readl(port->membase + INFO) & INFO_RX_EMPTY)
|
||||
return NO_POLL_CHAR;
|
||||
|
||||
rx = readl(port->membase + RX_DAT);
|
||||
|
||||
return rx;
|
||||
}
|
||||
|
||||
static void cortina_poll_put_char(struct uart_port *port, unsigned char c)
|
||||
{
|
||||
unsigned long time_out;
|
||||
|
||||
time_out = jiffies + usecs_to_jiffies(TX_TIMEOUT);
|
||||
|
||||
while (time_before(jiffies, time_out) &&
|
||||
(readl(port->membase + INFO) & INFO_TX_FULL))
|
||||
cpu_relax();
|
||||
|
||||
/* Give up if FIFO stuck! */
|
||||
if ((readl(port->membase + INFO) & INFO_TX_FULL))
|
||||
return;
|
||||
|
||||
writel(c, port->membase + TX_DAT);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static const struct uart_ops cortina_uart_ops = {
|
||||
.tx_empty = cortina_uart_tx_empty,
|
||||
.set_mctrl = cortina_uart_set_mctrl,
|
||||
.get_mctrl = cortina_uart_get_mctrl,
|
||||
.stop_tx = cortina_uart_stop_tx,
|
||||
.start_tx = cortina_uart_start_tx,
|
||||
.stop_rx = cortina_uart_stop_rx,
|
||||
.enable_ms = cortina_uart_enable_ms,
|
||||
.startup = cortina_uart_startup,
|
||||
.shutdown = cortina_uart_shutdown,
|
||||
.set_termios = cortina_uart_set_termios,
|
||||
.type = cortina_uart_type,
|
||||
.config_port = cortina_uart_config_port,
|
||||
.verify_port = cortina_uart_verify_port,
|
||||
.pm = cortina_access_power,
|
||||
#ifdef CONFIG_CONSOLE_POLL
|
||||
.poll_get_char = cortina_poll_get_char,
|
||||
.poll_put_char = cortina_poll_put_char,
|
||||
#endif
|
||||
};
|
||||
|
||||
static inline void cortina_uart_interrupt_rx_chars(struct uart_port *port,
|
||||
unsigned long status)
|
||||
{
|
||||
struct tty_port *ttyport = &port->state->port;
|
||||
unsigned int ch;
|
||||
unsigned int rx, flg;
|
||||
struct cortina_uart_port *pca_port;
|
||||
|
||||
rx = readl(port->membase + INFO);
|
||||
if (INFO_RX_EMPTY & rx)
|
||||
return;
|
||||
|
||||
if (status & RX_FIFO_OVERRUNE)
|
||||
port->icount.overrun++;
|
||||
|
||||
pca_port = cortina_uart_get_port(port->line);
|
||||
|
||||
/* Read the character while FIFO is not empty */
|
||||
do {
|
||||
flg = TTY_NORMAL;
|
||||
port->icount.rx++;
|
||||
ch = readl(port->membase + RX_DAT);
|
||||
if (status & RX_PARITY_ERRE) {
|
||||
port->icount.parity++;
|
||||
flg = TTY_PARITY;
|
||||
}
|
||||
|
||||
if (pca_port->has_bi) {
|
||||
/* If BI supported ? */
|
||||
if (status & RX_BREAK) {
|
||||
port->icount.brk++;
|
||||
if (uart_handle_break(port))
|
||||
goto ignore;
|
||||
}
|
||||
} else {
|
||||
/* Treat stop err as BI */
|
||||
if (status & RX_STOP_ERRE) {
|
||||
port->icount.brk++;
|
||||
if (uart_handle_break(port))
|
||||
goto ignore;
|
||||
}
|
||||
}
|
||||
if (!(ch & 0x100)) /* RX char is not valid */
|
||||
goto ignore;
|
||||
|
||||
if (uart_handle_sysrq_char(port, (unsigned char)ch))
|
||||
goto ignore;
|
||||
|
||||
tty_insert_flip_char(ttyport, ch, flg);
|
||||
ignore:
|
||||
rx = readl(port->membase + INFO);
|
||||
} while (!(INFO_RX_EMPTY & rx));
|
||||
|
||||
spin_unlock(&port->lock);
|
||||
tty_flip_buffer_push(ttyport);
|
||||
spin_lock(&port->lock);
|
||||
}
|
||||
|
||||
static inline void cortina_uart_interrupt_tx_chars(struct uart_port *port)
|
||||
{
|
||||
struct circ_buf *xmit = &port->state->xmit;
|
||||
|
||||
/* Process out of band chars */
|
||||
if (port->x_char) {
|
||||
/* Send next char */
|
||||
writel(port->x_char, port->membase + TX_DAT);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Nothing to do ? */
|
||||
if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
|
||||
cortina_uart_stop_tx(port);
|
||||
goto done;
|
||||
}
|
||||
|
||||
cortina_transmit_buffer(port);
|
||||
|
||||
/* Wake up */
|
||||
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
|
||||
uart_write_wakeup(port);
|
||||
|
||||
/* Maybe we're done after all */
|
||||
if (uart_circ_empty(xmit))
|
||||
cortina_uart_stop_tx(port);
|
||||
|
||||
done:
|
||||
return;
|
||||
}
|
||||
|
||||
irqreturn_t cortina_uart_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct uart_port *port = (struct uart_port *)dev_id;
|
||||
unsigned int irq_status;
|
||||
|
||||
spin_lock(&port->lock);
|
||||
|
||||
/* Clear interrupt! */
|
||||
irq_status = readl(port->membase + INT);
|
||||
writel(irq_status, port->membase + INT);
|
||||
|
||||
/* Process any Rx chars first */
|
||||
cortina_uart_interrupt_rx_chars(port, irq_status);
|
||||
/* Then use any Tx space */
|
||||
cortina_uart_interrupt_tx_chars(port);
|
||||
|
||||
spin_unlock(&port->lock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SERIAL_CORTINA_ACCESS_CONSOLE
|
||||
void cortina_console_write(struct console *co, const char *s,
|
||||
unsigned int count)
|
||||
{
|
||||
struct uart_port *port;
|
||||
struct cortina_uart_port *pca_port;
|
||||
unsigned int i, previous;
|
||||
unsigned long flags;
|
||||
int locked;
|
||||
|
||||
pca_port = cortina_uart_get_port(co->index);
|
||||
port = &pca_port->uart;
|
||||
|
||||
local_irq_save(flags);
|
||||
if (port->sysrq) {
|
||||
locked = 0;
|
||||
} else if (oops_in_progress) {
|
||||
locked = spin_trylock(&port->lock);
|
||||
} else {
|
||||
spin_lock(&port->lock);
|
||||
locked = 1;
|
||||
}
|
||||
|
||||
/* Save current state */
|
||||
previous = readl(port->membase + IE);
|
||||
/* Disable Tx interrupts so this all goes out in one go */
|
||||
cortina_uart_stop_tx(port);
|
||||
|
||||
/* Write all the chars */
|
||||
for (i = 0; i < count; i++) {
|
||||
/* Wait the TX buffer to be empty, which can't take forever */
|
||||
while (!(readl(port->membase + INFO) & INFO_TX_EMPTY))
|
||||
cpu_relax();
|
||||
|
||||
/* Send the char */
|
||||
writel(*s, port->membase + TX_DAT);
|
||||
|
||||
/* CR/LF stuff */
|
||||
if (*s++ == '\n') {
|
||||
/* Wait the TX buffer to be empty */
|
||||
while (!(readl(port->membase + INFO) & INFO_TX_EMPTY))
|
||||
cpu_relax();
|
||||
writel('\r', port->membase + TX_DAT);
|
||||
}
|
||||
}
|
||||
|
||||
writel(previous, port->membase + IE); /* Put it all back */
|
||||
|
||||
if (locked)
|
||||
spin_unlock(&port->lock);
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static int __init cortina_console_setup(struct console *co, char *options)
|
||||
{
|
||||
struct uart_port *port;
|
||||
struct cortina_uart_port *pca_port;
|
||||
int baud = 115200;
|
||||
int bits = 8;
|
||||
int parity = 'n';
|
||||
int flow = 'n';
|
||||
|
||||
if (co->index < 0 || co->index >= UART_NR)
|
||||
return -ENODEV;
|
||||
|
||||
pca_port = cortina_uart_get_port(co->index);
|
||||
port = &pca_port->uart;
|
||||
|
||||
/* This isn't going to do much, but it might change the baud rate. */
|
||||
if (options)
|
||||
uart_parse_options(options, &baud, &parity, &bits, &flow);
|
||||
|
||||
return uart_set_options(port, co, baud, parity, bits, flow);
|
||||
}
|
||||
|
||||
static struct uart_driver cortina_uart_driver;
|
||||
|
||||
static struct console cortina_console = {
|
||||
.name = "ttyS",
|
||||
.write = cortina_console_write,
|
||||
.device = uart_console_device,
|
||||
.setup = cortina_console_setup,
|
||||
.flags = CON_PRINTBUFFER,
|
||||
.index = -1, /* Only possible option. */
|
||||
.data = &cortina_uart_driver,
|
||||
};
|
||||
#define CORTINA_CONSOLE (&cortina_console)
|
||||
|
||||
/* Support EARLYCON */
|
||||
static void cortina_putc(struct uart_port *port, int c)
|
||||
{
|
||||
unsigned int tmout;
|
||||
|
||||
/* No jiffie at early boot stage!
|
||||
* Wait up to 5ms for the character to be sent.
|
||||
*/
|
||||
tmout = TX_TIMEOUT;
|
||||
while (--tmout) {
|
||||
if (!(readl(port->membase + INFO) & INFO_TX_FULL))
|
||||
break;
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
/* Give up if FIFO stuck! */
|
||||
while ((readl(port->membase + INFO) & INFO_TX_FULL))
|
||||
return;
|
||||
|
||||
/* Send the char */
|
||||
writel(c, port->membase + TX_DAT);
|
||||
}
|
||||
|
||||
static void cortina_early_write(struct console *con, const char *s,
|
||||
unsigned int n)
|
||||
{
|
||||
struct earlycon_device *dev = con->data;
|
||||
|
||||
uart_console_write(&dev->port, s, n, cortina_putc);
|
||||
}
|
||||
|
||||
static int __init cortina_early_console_setup(struct earlycon_device *device,
|
||||
const char *opt)
|
||||
{
|
||||
if (!device->port.membase)
|
||||
return -ENODEV;
|
||||
|
||||
device->con->write = cortina_early_write;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EARLYCON_DECLARE(serial, cortina_early_console_setup);
|
||||
OF_EARLYCON_DECLARE(serial, "cortina-access,serial", cortina_early_console_setup);
|
||||
#else
|
||||
#define CORTINA_CONSOLE NULL
|
||||
#endif
|
||||
|
||||
static struct uart_driver cortina_uart_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.driver_name = "cortina-access_uart",
|
||||
.dev_name = "ttyS",
|
||||
.major = TTY_MAJOR,
|
||||
.minor = 64,
|
||||
.nr = UART_NR,
|
||||
.cons = CORTINA_CONSOLE,
|
||||
};
|
||||
|
||||
/* Match table for of_platform binding */
|
||||
static const struct of_device_id cortina_uart_of_match[] = {
|
||||
{.compatible = "cortina-access,serial",},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, cortina_uart_of_match);
|
||||
|
||||
static int serial_cortina_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
void __iomem *base;
|
||||
struct cortina_uart_port *port;
|
||||
const struct of_device_id *match;
|
||||
|
||||
/* assign DT node pointer */
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct resource mem_resource;
|
||||
u32 of_clock_frequency;
|
||||
struct clk *pclk_info;
|
||||
int uart_idx;
|
||||
|
||||
/* search DT for a match */
|
||||
match = of_match_device(cortina_uart_of_match, &pdev->dev);
|
||||
if (!match)
|
||||
return -EINVAL;
|
||||
|
||||
if (cortina_uart_ports == NULL)
|
||||
cortina_uart_ports = kzalloc(UART_NR * sizeof(struct cortina_uart_port),
|
||||
GFP_KERNEL);
|
||||
|
||||
port = cortina_uart_ports;
|
||||
for (uart_idx = 0; uart_idx < UART_NR; ++uart_idx) {
|
||||
/* Find first empty slot */
|
||||
if (strlen(port->name) == 0)
|
||||
break;
|
||||
port++;
|
||||
}
|
||||
|
||||
if (uart_idx >= UART_NR)
|
||||
return -EINVAL;
|
||||
|
||||
snprintf(port->name, sizeof(port->name), "Cortina-Access UART%d", uart_idx);
|
||||
|
||||
/* Retrieve HW base address */
|
||||
ret = of_address_to_resource(np, 0, &mem_resource);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev, "invalid address %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
base = devm_ioremap(&pdev->dev, mem_resource.start,
|
||||
resource_size(&mem_resource));
|
||||
if (!base) {
|
||||
devm_kfree(&pdev->dev, port);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* assign reg base and irq from DT */
|
||||
port->uart.irq = irq_of_parse_and_map(np, 0);
|
||||
port->uart.membase = base;
|
||||
port->uart.mapbase = mem_resource.start;
|
||||
port->uart.ops = &cortina_uart_ops;
|
||||
port->uart.dev = &pdev->dev;
|
||||
port->uart.line = uart_idx;
|
||||
port->uart.has_sysrq = IS_ENABLED(CONFIG_SERIAL_CORTINA_ACCESS_CONSOLE);
|
||||
|
||||
/* get clock-freqency tuple from DT and store value */
|
||||
if (of_property_read_u32(np, "clock-frequency", &of_clock_frequency)) {
|
||||
/* If we are here, it means DT node did not contain
|
||||
* clock-frequency tuple. Therefore, instead try to get
|
||||
* clk rate through the clk driver that DT has stated
|
||||
* we are consuming.
|
||||
*/
|
||||
pclk_info = clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(pclk_info)) {
|
||||
dev_warn(&pdev->dev,
|
||||
"clk or clock-frequency not defined\n");
|
||||
return PTR_ERR(pclk_info);
|
||||
}
|
||||
|
||||
clk_prepare_enable(pclk_info);
|
||||
of_clock_frequency = clk_get_rate(pclk_info);
|
||||
}
|
||||
port->uart.uartclk = of_clock_frequency;
|
||||
|
||||
if (of_property_read_bool(np, "wakeup-source"))
|
||||
port->may_wakeup = true;
|
||||
if (of_property_read_bool(np, "break-indicator"))
|
||||
port->has_bi = true;
|
||||
|
||||
port->uart.type = PORT_UNKNOWN;
|
||||
|
||||
if (port->may_wakeup)
|
||||
device_init_wakeup(&pdev->dev, true);
|
||||
|
||||
ret = uart_add_one_port(&cortina_uart_driver, &port->uart);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
platform_set_drvdata(pdev, port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int serial_cortina_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct uart_port *port = platform_get_drvdata(pdev);
|
||||
struct cortina_uart_port *pca_port;
|
||||
|
||||
if (port) {
|
||||
pca_port = cortina_uart_get_port(port->line);
|
||||
memset(pca_port->name, 0, CA_UART_NAME_LEN);
|
||||
uart_remove_one_port(&cortina_uart_driver, port);
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int serial_cortina_suspend(struct platform_device *pdev,
|
||||
pm_message_t state)
|
||||
{
|
||||
struct cortina_uart_port *p =
|
||||
(struct cortina_uart_port *)pdev->dev.driver_data;
|
||||
|
||||
uart_suspend_port(&cortina_uart_driver, &p->uart);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int serial_cortina_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct cortina_uart_port *p =
|
||||
(struct cortina_uart_port *)pdev->dev.driver_data;
|
||||
|
||||
uart_resume_port(&cortina_uart_driver, &p->uart);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define serial_cortina_suspend NULL
|
||||
#define serial_cortina_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver serial_cortina_driver = {
|
||||
.probe = serial_cortina_probe,
|
||||
.remove = serial_cortina_remove,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = serial_cortina_suspend,
|
||||
.resume = serial_cortina_resume,
|
||||
#endif
|
||||
.driver = {
|
||||
.name = "cortina-access_serial",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = cortina_uart_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init cortina_uart_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = uart_register_driver(&cortina_uart_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = platform_driver_register(&serial_cortina_driver);
|
||||
if (ret)
|
||||
uart_unregister_driver(&cortina_uart_driver);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit cortina_uart_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&serial_cortina_driver);
|
||||
uart_unregister_driver(&cortina_uart_driver);
|
||||
}
|
||||
|
||||
module_init(cortina_uart_init);
|
||||
module_exit(cortina_uart_exit);
|
||||
|
||||
MODULE_AUTHOR("Cortina-Access Inc.");
|
||||
MODULE_DESCRIPTION(" Cortina-Access UART driver");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Reference in New Issue