tty: serial: Add UART driver for Cortina-Access platform
authorJason Li <jason.li@cortina-access.com>
Tue, 15 Jun 2021 16:57:48 +0000 (09:57 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 18 Jun 2021 11:12:12 +0000 (13:12 +0200)
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>
MAINTAINERS
drivers/tty/serial/Kconfig
drivers/tty/serial/Makefile
drivers/tty/serial/serial_cortina-access.c [new file with mode: 0644]

index 25d7d86..f20502d 100644 (file)
@@ -4680,6 +4680,11 @@ 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
index 24282ad..2fa39cd 100644 (file)
@@ -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
index 7da0856..f3f531a 100644 (file)
@@ -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 (file)
index 0000000..b4b9362
--- /dev/null
@@ -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");