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:
Greg Kroah-Hartman 2021-06-24 14:46:32 +02:00
parent b61c8bf469
commit cddd53e8aa
4 changed files with 0 additions and 823 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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");