summaryrefslogtreecommitdiff
path: root/drivers/tty
diff options
context:
space:
mode:
authorJason Li <jason.li@cortina-access.com>2021-06-15 09:57:48 -0700
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2021-06-18 13:12:12 +0200
commitb61c8bf4694b5115766849378dcb8787ff54e65e (patch)
tree863e3428f77e2756f708b7b267fe2f90cd29e6ba /drivers/tty
parent18aaa97eee7ddb770963d84aeaf5e95a8f8af088 (diff)
tty: serial: Add UART driver for Cortina-Access platform
This driver supports Cortina Access UART IP integrated in most all CAXXXX line of SoCs. Earlycom is also supported Signed-off-by: Jason Li <jason.li@cortina-access.com> Link: https://lore.kernel.org/r/20210615165750.31261-1-alex.nemirovsky@cortina-access.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty')
-rw-r--r--drivers/tty/serial/Kconfig19
-rw-r--r--drivers/tty/serial/Makefile1
-rw-r--r--drivers/tty/serial/serial_cortina-access.c798
3 files changed, 818 insertions, 0 deletions
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 24282ad99d85..2fa39cd78c56 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1561,6 +1561,25 @@ 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
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 7da0856cd198..f3f531a48854 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -87,6 +87,7 @@ 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
diff --git a/drivers/tty/serial/serial_cortina-access.c b/drivers/tty/serial/serial_cortina-access.c
new file mode 100644
index 000000000000..b4b9362bb4e6
--- /dev/null
+++ b/drivers/tty/serial/serial_cortina-access.c
@@ -0,0 +1,798 @@
+// 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");