[PATCH 2/3] tty: serial: Add UART driver for Cortina-Access platform

Jason Li jason.lee651024 at gmail.com
Wed Jun 10 04:28:20 PDT 2026


This driver supports Cortina Access UART IP integrated
in most CAXXXX line of SoCs. Earlycon is also supported.

Signed-off-by: Jason Li <jason.li at cortina-access.com>
Assisted-by: Claude:claude-opus-4-8
---
 MAINTAINERS                                |   6 +
 drivers/tty/serial/Kconfig                 |  21 +
 drivers/tty/serial/Makefile                |   1 +
 drivers/tty/serial/serial_cortina-access.c | 755 +++++++++++++++++++++
 4 files changed, 783 insertions(+)
 create mode 100644 drivers/tty/serial/serial_cortina-access.c

diff --git a/MAINTAINERS b/MAINTAINERS
index cc261888fae0..515d89d96472 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6687,6 +6687,12 @@ S:	Maintained
 F:	Documentation/hwmon/corsair-psu.rst
 F:	drivers/hwmon/corsair-psu.c
 
+CORTINA-ACCESS SERIAL CONSOLE DRIVER
+M:	Jason Li <jason.li at cortina-access.com>
+L:	linux-serial at vger.kernel.org
+S:	Supported
+F:	drivers/tty/serial/serial_cortina-access.c
+
 COUNTER SUBSYSTEM
 M:	William Breathitt Gray <wbg at kernel.org>
 L:	linux-iio at vger.kernel.org
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index cf7dba473b20..99a1c9308395 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1592,6 +1592,27 @@ config SERIAL_NUVOTON_MA35D1_CONSOLE
 	  but you can alter that using a kernel command line option such as
 	  "console=ttyNVTx".
 
+config SERIAL_CORTINA_ACCESS
+	tristate "Cortina-Access serial port support"
+	depends on OF
+	select SERIAL_CORE
+	help
+	  This driver is for the Cortina-Access SoC UART, present in the
+	  CA8289 (Venus) and related CAXXXX family of SoCs. 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 the Cortina-Access UART as the system
+	  console (the device which receives all kernel messages and warnings
+	  and which allows logins in single user mode).
+	  /dev/ttyS* is the default device node.
+
 endmenu
 
 config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index bba7b21a4a1d..54866c419714 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -98,3 +98,4 @@ obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
 
 obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o
 obj-$(CONFIG_SERIAL_NUVOTON_MA35D1) += ma35d1_serial.o
+obj-$(CONFIG_SERIAL_CORTINA_ACCESS) += serial_cortina-access.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..f25eae987ccd
--- /dev/null
+++ b/drivers/tty/serial/serial_cortina-access.c
@@ -0,0 +1,755 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  UART driver for Cortina-Access SoC platform
+ *  Copyright (C) 2026 Cortina-Access Inc.
+ */
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/serial.h>
+#include <linux/serial_core.h>
+#include <linux/sysrq.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.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);
+static inline void cortina_uart_interrupt_tx_chars(struct uart_port *port);
+
+/* Return uart_port pointer based on index */
+static struct cortina_uart_port *cortina_uart_get_port(unsigned int index)
+{
+	struct cortina_uart_port *pca_port = cortina_uart_ports;
+
+	if (index >= UART_NR)
+		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 FIFO full 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 provided to the serial core.
+	 * port->ops->set_mctrl() is 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 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 TX FIFO is already empty the TX_FIFO_EMPTY interrupt may be
+	 * edge-triggered and won't fire again.  Kick-start the transmission
+	 * explicitly so the first character is not lost.
+	 */
+	if (readl(port->membase + INFO) & INFO_TX_EMPTY)
+		cortina_uart_interrupt_tx_chars(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 interrupts */
+	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,
+				     const 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;
+
+	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)
+{
+	if (readl(port->membase + INFO) & INFO_RX_EMPTY)
+		return NO_POLL_CHAR;
+
+	return readl(port->membase + RX_DAT);
+}
+
+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 /* CONFIG_CONSOLE_POLL */
+
+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 characters 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 (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)
+{
+	u8 ch;
+
+	/*
+	 * uart_port_tx() drains the kfifo xmit buffer, handles x_char,
+	 * calls uart_write_wakeup() and stop_tx() when the buffer empties.
+	 */
+	uart_port_tx(port, ch,
+		     !(readl(port->membase + INFO) & INFO_TX_FULL),
+		     writel(ch, port->membase + TX_DAT));
+}
+
+static 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
+static 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 for the TX buffer to be empty */
+		while (!(readl(port->membase + INFO) & INFO_TX_EMPTY))
+			cpu_relax();
+
+		writel(*s, port->membase + TX_DAT);
+
+		/* CR/LF handling */
+		if (*s++ == '\n') {
+			while (!(readl(port->membase + INFO) & INFO_TX_EMPTY))
+				cpu_relax();
+			writel('\r', port->membase + TX_DAT);
+		}
+	}
+
+	writel(previous, port->membase + IE);	/* Restore interrupt state */
+
+	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;
+
+	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,
+	.data	= &cortina_uart_driver,
+};
+
+#define CORTINA_CONSOLE (&cortina_console)
+
+/* Support EARLYCON */
+static void cortina_putc(struct uart_port *port, unsigned char c)
+{
+	unsigned int tmout = TX_TIMEOUT;
+
+	/* No jiffies at early boot stage; wait up to TX_TIMEOUT us */
+	while (--tmout) {
+		if (!(readl(port->membase + INFO) & INFO_TX_FULL))
+			break;
+		udelay(1);
+	}
+
+	/* Give up if FIFO stuck */
+	if (readl(port->membase + INFO) & INFO_TX_FULL)
+		return;
+
+	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)
+{
+	u32 reg_v;
+
+	if (!device->port.membase)
+		return -ENODEV;
+
+	device->con->write = cortina_early_write;
+
+	/*
+	 * If the bootloader did not enable the UART, initialise it here
+	 * at 115200 baud so that early boot messages are not lost.
+	 * The magic constant mirrors the reference BSP setting:
+	 *   CFG[31:8] = baud divisor for 115200, CFG[7] = UART_EN,
+	 *   CFG[6:5]  = RX_EN|TX_EN, CFG[1:0] = 8-bit data.
+	 */
+	reg_v = readl(device->port.membase + CFG);
+	if (!(reg_v & CFG_UART_EN)) {
+		writel(0x00043de3, device->port.membase + CFG);
+		writel(0x00043d / 2, device->port.membase + RX_SAMPLE);
+	}
+
+	return 0;
+}
+
+EARLYCON_DECLARE(ca_serial, cortina_early_console_setup);
+OF_EARLYCON_DECLARE(ca_serial, "cortina-access,serial",
+		    cortina_early_console_setup);
+#else
+#define CORTINA_CONSOLE	NULL
+#endif /* CONFIG_SERIAL_CORTINA_ACCESS_CONSOLE */
+
+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)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct cortina_uart_port *port;
+	struct resource *res;
+	struct clk *pclk_info;
+	int uart_idx;
+	int irq;
+	int ret;
+
+	if (!cortina_uart_ports) {
+		cortina_uart_ports = kcalloc(UART_NR, sizeof(*cortina_uart_ports),
+					     GFP_KERNEL);
+		if (!cortina_uart_ports)
+			return -ENOMEM;
+	}
+
+	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 -ENODEV;
+
+	snprintf(port->name, sizeof(port->name),
+		 "Cortina-Access UART%d", uart_idx);
+
+	/* Retrieve HW base address and reserve the I/O region */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -EINVAL;
+
+	port->uart.mapbase = res->start;
+	port->uart.membase = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(port->uart.membase))
+		return PTR_ERR(port->uart.membase);
+
+	/* Retrieve IRQ */
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	port->uart.irq = irq;
+
+	pclk_info = devm_clk_get_enabled(&pdev->dev, NULL);
+	if (IS_ERR(pclk_info)) {
+		dev_err(&pdev->dev, "failed to get clock\n");
+		return PTR_ERR(pclk_info);
+	}
+
+	port->uart.uartclk	= clk_get_rate(pclk_info);
+	port->uart.ops		= &cortina_uart_ops;
+	port->uart.dev		= &pdev->dev;
+	port->uart.line		= uart_idx;
+	port->uart.iotype	= UPIO_MEM32;
+	port->uart.type		= 1;
+	port->uart.has_sysrq	= IS_ENABLED(CONFIG_SERIAL_CORTINA_ACCESS_CONSOLE);
+
+	if (of_property_read_bool(np, "wakeup-source"))
+		port->may_wakeup = true;
+	if (of_property_read_bool(np, "break-indicator"))
+		port->has_bi = true;
+
+	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 void serial_cortina_remove(struct platform_device *pdev)
+{
+	struct cortina_uart_port *pca_port = platform_get_drvdata(pdev);
+
+	if (pca_port) {
+		memset(pca_port->name, 0, CA_UART_NAME_LEN);
+		uart_remove_one_port(&cortina_uart_driver, &pca_port->uart);
+	}
+
+	platform_set_drvdata(pdev, NULL);
+
+	/*
+	 * Free the port array when the last port is removed, i.e. when
+	 * all slots are empty again.
+	 */
+	if (cortina_uart_ports) {
+		int i;
+
+		for (i = 0; i < UART_NR; i++) {
+			if (strlen(cortina_uart_ports[i].name) != 0)
+				return;
+		}
+		kfree(cortina_uart_ports);
+		cortina_uart_ports = NULL;
+	}
+}
+
+#ifdef CONFIG_PM
+static int serial_cortina_suspend(struct platform_device *pdev,
+				  pm_message_t state)
+{
+	struct cortina_uart_port *p = platform_get_drvdata(pdev);
+
+	uart_suspend_port(&cortina_uart_driver, &p->uart);
+
+	return 0;
+}
+
+static int serial_cortina_resume(struct platform_device *pdev)
+{
+	struct cortina_uart_port *p = platform_get_drvdata(pdev);
+
+	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",
+		.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");
-- 
2.39.5




More information about the linux-arm-kernel mailing list