From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7D7AA4A2A41; Tue, 6 Oct 2026 18:17:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791310662; cv=none; b=X2aqL/xiX++rloiJaD7MqNT/57tCIUcIYsDKOgdeLP415CiBx7xtPCM4jhV8PBjrWCkpNPaPboNnqTkaBUHyq0qAX9cTubyqRSMpVt3gzrJtyboKJ2MeK2b3eT1z5BR5b3ldH6PRbHaIa2lgmTEJlkItzUkL0PAo12z4+fb+0oI= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791310662; c=relaxed/simple; bh=O0HqCyza5hTn4VK5/yhOJWzner4WbLHrKDlEAlhb2BM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=uSgrtLSm/uVZg+XkuoJZ45joQT3/02iBhPvJY2QlxPztC06TQVWsG1AWzsvioaktNGuyBL/lh8Jjw7d2OkaoEHrUDzqiFa5WxGxvMikNJNKLkms5pWaI8FhqOKji9YuOdK2wU5XqQ7c7JFtJbDpWtAdJprKxOMrNGlynL6GEmJE= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=PQnY+t7D; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="PQnY+t7D" Received: by smtp.kernel.org (Postfix) with ESMTPS id 4E437C4DDFE; Tue, 6 Oct 2026 18:17:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1791310662; bh=O0HqCyza5hTn4VK5/yhOJWzner4WbLHrKDlEAlhb2BM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=PQnY+t7DxbBUdCyMTUHirZ23ifqpdfZEWPEuA77UHLbFohnCqxUuVYYa6xsmvI7gS SX8r7Pj4yoW2/tdR3vOfGjqMtMVnQpWt2XMRe3gAL0nPe/Gcx1QgmtBW7OHV2kHJKS Qluqr1z8+JJv1um95374hgBolep4zfsg+N7TyFDu4IjDP9m4LOL1mPGKO0XVQWkIri N7kzULEbCevZ6Y795uGg8qXUqTw6v3ssuAxSaZTNTYxLAnW+Z4xxInqs5tGiWiqH1y L5xz8xsHs9vcffc1YfsXog3vlXkt1RZdi9lO4SJnq6GETkQfbZRU0htSdQA2FKGWms /yaan+XKKYNsg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 38A98CA6004; Tue, 6 Oct 2026 18:17:42 +0000 (UTC) From: Fil Dunsky via B4 Relay Date: Tue, 06 Oct 2026 21:17:45 +0300 Subject: [PATCH RFC v2 07/10] tty: serial: Add Cortina UART driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20261006-b4-cs75xx-v2-7-b7d6e1e24d44@gmail.com> References: <20261006-b4-cs75xx-v2-0-b7d6e1e24d44@gmail.com> In-Reply-To: <20261006-b4-cs75xx-v2-0-b7d6e1e24d44@gmail.com> To: Arnd Bergmann , Linus Walleij , Krzysztof Kozlowski Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Greg Kroah-Hartman , Jiri Slaby , Russell King , Alexandre Belloni , Drew Fustini , Lorenzo Pieralisi , linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-serial@vger.kernel.org, Jason Li , Fil Dunsky X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1791310660; l=20551; i=filipp.dunsky@gmail.com; s=20260930; h=from:subject:message-id; bh=DCcluIIDENSeYrtFBMQ/jVNtmjZxl4kl7IIhn1TFHc8=; b=fOGOZOc7esEvsKBUNoxfr190UYsxDjZ0i281QtgEWGuZ9kyaBqlUBI5N5Vw79dEPsG5VhQ2tO rJzz597nKY4CmGVRm1MnhIB2i+MhTwKOTX5zB6YAYB1HKpR4vEJISLJ X-Developer-Key: i=filipp.dunsky@gmail.com; a=ed25519; pk=JAYP8e2rJ54DHOmhNtXud8rl1igAaAXoOHxtJmVXI1g= X-Endpoint-Received: by B4 Relay for filipp.dunsky@gmail.com/20260930 with auth_id=1085 X-Original-From: Fil Dunsky Reply-To: filipp.dunsky@gmail.com From: Fil Dunsky Add a driver for the UART found in Cortina SoCs, starting with the Cortina Systems CS75xx (Goldengate G2) family such as the CS7542. The block has transmit and receive FIFOs with full/empty status, a 24-bit baud rate divider clocked from the peripheral (APB) clock, a receive sampling point register and one interrupt line per port with per-cause enable and write-one-to-clear status registers. The same block, with an additional flow control register and a few more interrupt causes, is used in the Cortina Access CAxxxx SoCs, which were briefly supported by commit b61c8bf4694b ("tty: serial: Add UART driver for Cortina-Access platform") before it was removed again. The driver is therefore named after the vendor rather than one SoC, so that those parts can be added with their own compatible strings. The driver is a rewrite of the vendor driver shipped with the Linux 2.6.36 and 4.9 based vendor kernels, using the current serial core API (uart_port_tx(), port lock helpers, deferred sysrq handling) and devm resource management. It supports the system console and earlycon. Hardware flow control is not supported. The ports are named ttyCS so that the driver can coexist with the 8250 driver in multiplatform kernels, where the 8250 core registers ttyS0 and up even without any 8250 hardware, and PORT_GENERIC is used rather than allocating a new port type. Signed-off-by: Fil Dunsky --- drivers/tty/serial/Kconfig | 24 ++ drivers/tty/serial/Makefile | 1 + drivers/tty/serial/cortina-uart.c | 583 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 608 insertions(+) diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index bf680d22199..92443a0c7a7 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1359,6 +1359,30 @@ config SERIAL_CONEXANT_DIGICOLOR_CONSOLE If you have enabled the USART serial port on Conexant Digicolor SoCs, you can make it the console by answering Y to this option. +config SERIAL_CORTINA + tristate "Cortina UART support" + depends on ARCH_CORTINA_ACCESS || COMPILE_TEST + depends on OF + select SERIAL_CORE + help + Support for the on-chip UART of Cortina Systems CS75xx + (Goldengate G2) SoCs such as the CS7542. Cortina Access SoCs use + the same UART. The ports are named ttyCS0 to ttyCS3. + + To compile this driver as a module, choose M here: the module + will be called cortina-uart. + +config SERIAL_CORTINA_CONSOLE + bool "Console on Cortina UART" + depends on SERIAL_CORTINA=y + select SERIAL_CORE_CONSOLE + select SERIAL_EARLYCON + help + Say Y here to use a Cortina UART as the system + console. This also enables the early console, which can be + selected with "earlycon" on the kernel command line when the + device tree chosen node has a stdout-path pointing at the UART. + config SERIAL_ST_ASC tristate "ST ASC serial port support" select SERIAL_CORE diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index bba7b21a4a1..7c7d749a2ce 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_SERIAL_BCM63XX) += bcm63xx_uart.o obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o obj-$(CONFIG_SERIAL_CPM) += cpm_uart.o obj-$(CONFIG_SERIAL_CONEXANT_DIGICOLOR) += digicolor-usart.o +obj-$(CONFIG_SERIAL_CORTINA) += cortina-uart.o obj-$(CONFIG_SERIAL_DZ) += dz.o obj-$(CONFIG_SERIAL_FSL_LINFLEXUART) += fsl_linflexuart.o obj-$(CONFIG_SERIAL_FSL_LPUART) += fsl_lpuart.o diff --git a/drivers/tty/serial/cortina-uart.c b/drivers/tty/serial/cortina-uart.c new file mode 100644 index 00000000000..1952629de05 --- /dev/null +++ b/drivers/tty/serial/cortina-uart.c @@ -0,0 +1,583 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UART driver for Cortina SoCs (Cortina Systems CS75xx "Goldengate G2") + * + * Copyright (C) 2026 Fil Dunsky + * + * Based on serial_cortina.c from the Cortina Systems vendor kernel. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CORTINA_UART_CFG 0x00 +#define CORTINA_UART_CFG_BAUD GENMASK(31, 8) +#define CORTINA_UART_CFG_EN BIT(7) +#define CORTINA_UART_CFG_RX_EN BIT(6) +#define CORTINA_UART_CFG_TX_EN BIT(5) +#define CORTINA_UART_CFG_PARITY_EN BIT(4) +#define CORTINA_UART_CFG_PARITY_EVEN BIT(3) +#define CORTINA_UART_CFG_STOP_2BIT BIT(2) +#define CORTINA_UART_CFG_CHAR GENMASK(1, 0) +#define CORTINA_UART_RX_SAMPLE 0x08 +#define CORTINA_UART_TXDAT 0x10 +#define CORTINA_UART_RXDAT 0x14 +#define CORTINA_UART_RXDAT_VALID BIT(8) +#define CORTINA_UART_RXDAT_DATA GENMASK(7, 0) +#define CORTINA_UART_INFO 0x18 +#define CORTINA_UART_INFO_TX_EMPTY BIT(3) +#define CORTINA_UART_INFO_TX_FULL BIT(2) +#define CORTINA_UART_INFO_RX_EMPTY BIT(1) +#define CORTINA_UART_IE 0x1c +#define CORTINA_UART_INT 0x24 + +/* Interrupt bits, shared by the IE and INT registers */ +#define CORTINA_UART_IRQ_RX_NONEMPTY BIT(6) +#define CORTINA_UART_IRQ_TX_EMPTY BIT(5) +#define CORTINA_UART_IRQ_RX_OVERRUN BIT(3) +#define CORTINA_UART_IRQ_RX_PARITY BIT(2) +#define CORTINA_UART_IRQ_RX_STOP BIT(1) +#define CORTINA_UART_IRQ_ALL GENMASK(6, 0) + +#define CORTINA_UART_DIV_MIN 16 +#define CORTINA_UART_DIV_MAX FIELD_MAX(CORTINA_UART_CFG_BAUD) + +#define CORTINA_UART_NR_PORTS 4 +#define CORTINA_UART_FIFO_SIZE 16 +#define CORTINA_UART_POLL_TIMEOUT_US 10000 +#define CORTINA_UART_RX_BUDGET 256 + +struct cortina_uart_port { + struct uart_port port; + struct clk *clk; +}; + +static struct cortina_uart_port *cortina_uart_ports[CORTINA_UART_NR_PORTS]; +static DEFINE_MUTEX(cortina_uart_ports_lock); + +static void cortina_uart_ie_update(struct uart_port *port, u32 clear, u32 set) +{ + u32 ie = readl(port->membase + CORTINA_UART_IE); + + writel((ie & ~clear) | set, port->membase + CORTINA_UART_IE); +} + +static void __maybe_unused cortina_uart_putchar(struct uart_port *port, + unsigned char ch) +{ + u32 info; + + readl_poll_timeout_atomic(port->membase + CORTINA_UART_INFO, info, + !(info & CORTINA_UART_INFO_TX_FULL), 1, + CORTINA_UART_POLL_TIMEOUT_US); + writel(ch, port->membase + CORTINA_UART_TXDAT); +} + +static unsigned int cortina_uart_tx_empty(struct uart_port *port) +{ + u32 info = readl(port->membase + CORTINA_UART_INFO); + + return (info & CORTINA_UART_INFO_TX_EMPTY) ? TIOCSER_TEMT : 0; +} + +/* + * There are no modem control lines, but serial_core calls ->set_mctrl() + * unconditionally. + */ +static void cortina_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +} + +static unsigned int cortina_uart_get_mctrl(struct uart_port *port) +{ + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; +} + +static void cortina_uart_stop_tx(struct uart_port *port) +{ + cortina_uart_ie_update(port, CORTINA_UART_IRQ_TX_EMPTY, 0); +} + +static void cortina_uart_tx_chars(struct uart_port *port) +{ + u8 ch; + + uart_port_tx(port, ch, + !(readl(port->membase + CORTINA_UART_INFO) & + CORTINA_UART_INFO_TX_FULL), + writel(ch, port->membase + CORTINA_UART_TXDAT)); +} + +static void cortina_uart_start_tx(struct uart_port *port) +{ + cortina_uart_ie_update(port, 0, CORTINA_UART_IRQ_TX_EMPTY); + + /* + * The TX empty interrupt only fires when the FIFO drains, so prime + * the FIFO here. + */ + cortina_uart_tx_chars(port); +} + +static void cortina_uart_stop_rx(struct uart_port *port) +{ + cortina_uart_ie_update(port, CORTINA_UART_IRQ_RX_NONEMPTY, 0); +} + +static void cortina_uart_rx_chars(struct uart_port *port, u32 status) +{ + struct tty_port *tport = &port->state->port; + unsigned int budget = CORTINA_UART_RX_BUDGET; + u32 data; + + if (status & CORTINA_UART_IRQ_RX_OVERRUN) { + port->icount.overrun++; + tty_insert_flip_char(tport, 0, TTY_OVERRUN); + } + if (status & CORTINA_UART_IRQ_RX_PARITY) + port->icount.parity++; + if (status & CORTINA_UART_IRQ_RX_STOP) + port->icount.frame++; + + while (budget-- && !(readl(port->membase + CORTINA_UART_INFO) & + CORTINA_UART_INFO_RX_EMPTY)) { + u8 ch; + + data = readl(port->membase + CORTINA_UART_RXDAT); + if (!(data & CORTINA_UART_RXDAT_VALID)) + continue; + + port->icount.rx++; + ch = FIELD_GET(CORTINA_UART_RXDAT_DATA, data); + + if (port->ignore_status_mask & CORTINA_UART_RXDAT_VALID) + continue; + if (uart_prepare_sysrq_char(port, ch)) + continue; + + tty_insert_flip_char(tport, ch, TTY_NORMAL); + } + + tty_flip_buffer_push(tport); +} + +static irqreturn_t cortina_uart_isr(int irq, void *dev_id) +{ + struct uart_port *port = dev_id; + u32 status; + + uart_port_lock(port); + + status = readl(port->membase + CORTINA_UART_INT); + writel(status, port->membase + CORTINA_UART_INT); + + cortina_uart_rx_chars(port, status); + cortina_uart_tx_chars(port); + + uart_unlock_and_check_sysrq(port); + + return IRQ_HANDLED; +} + +static void cortina_uart_rx_flush(struct uart_port *port) +{ + unsigned int n = CORTINA_UART_RX_BUDGET; + + while (n-- && !(readl(port->membase + CORTINA_UART_INFO) & + CORTINA_UART_INFO_RX_EMPTY)) + readl(port->membase + CORTINA_UART_RXDAT); +} + +static int cortina_uart_startup(struct uart_port *port) +{ + unsigned long flags; + u32 cfg; + int ret; + + uart_port_lock_irqsave(port, &flags); + writel(0, port->membase + CORTINA_UART_IE); + writel(CORTINA_UART_IRQ_ALL, port->membase + CORTINA_UART_INT); + uart_port_unlock_irqrestore(port, flags); + + ret = request_irq(port->irq, cortina_uart_isr, 0, dev_name(port->dev), + port); + if (ret) + return ret; + + uart_port_lock_irqsave(port, &flags); + + cfg = readl(port->membase + CORTINA_UART_CFG); + cfg |= CORTINA_UART_CFG_EN | CORTINA_UART_CFG_RX_EN | CORTINA_UART_CFG_TX_EN; + writel(cfg, port->membase + CORTINA_UART_CFG); + + /* Drop whatever was received while the port was closed */ + cortina_uart_rx_flush(port); + writel(CORTINA_UART_IRQ_ALL, port->membase + CORTINA_UART_INT); + + writel(CORTINA_UART_IRQ_RX_NONEMPTY, port->membase + CORTINA_UART_IE); + + uart_port_unlock_irqrestore(port, flags); + + return 0; +} + +static void cortina_uart_shutdown(struct uart_port *port) +{ + unsigned long flags; + + uart_port_lock_irqsave(port, &flags); + writel(0, port->membase + CORTINA_UART_IE); + /* The console only needs the transmitter */ + writel(readl(port->membase + CORTINA_UART_CFG) & ~CORTINA_UART_CFG_RX_EN, + port->membase + CORTINA_UART_CFG); + uart_port_unlock_irqrestore(port, flags); + + free_irq(port->irq, port); +} + +static void cortina_uart_set_termios(struct uart_port *port, + struct ktermios *termios, + const struct ktermios *old) +{ + unsigned int baud, div; + unsigned long flags; + u32 cfg; + + /* No hardware flow control or mark/space parity support */ + termios->c_cflag &= ~(CRTSCTS | CMSPAR); + + baud = uart_get_baud_rate(port, termios, old, + DIV_ROUND_UP(port->uartclk, CORTINA_UART_DIV_MAX), + port->uartclk / CORTINA_UART_DIV_MIN); + div = DIV_ROUND_CLOSEST(port->uartclk, baud); + + cfg = FIELD_PREP(CORTINA_UART_CFG_BAUD, div) | CORTINA_UART_CFG_EN | + CORTINA_UART_CFG_RX_EN | CORTINA_UART_CFG_TX_EN; + cfg |= FIELD_PREP(CORTINA_UART_CFG_CHAR, + tty_get_char_size(termios->c_cflag) - 5); + if (termios->c_cflag & CSTOPB) + cfg |= CORTINA_UART_CFG_STOP_2BIT; + if (termios->c_cflag & PARENB) { + cfg |= CORTINA_UART_CFG_PARITY_EN; + if (!(termios->c_cflag & PARODD)) + cfg |= CORTINA_UART_CFG_PARITY_EVEN; + } + + uart_port_lock_irqsave(port, &flags); + + port->ignore_status_mask = 0; + if (!(termios->c_cflag & CREAD)) + port->ignore_status_mask |= CORTINA_UART_RXDAT_VALID; + + uart_update_timeout(port, termios->c_cflag, baud); + + writel(cfg, port->membase + CORTINA_UART_CFG); + /* Sample the received bits in the middle of the bit time */ + writel(div / 2, port->membase + CORTINA_UART_RX_SAMPLE); + + uart_port_unlock_irqrestore(port, flags); + + if (tty_termios_baud_rate(termios)) + tty_termios_encode_baud_rate(termios, baud, baud); +} + +static const char *cortina_uart_type(struct uart_port *port) +{ + return port->type == PORT_GENERIC ? "CORTINA" : NULL; +} + +static void cortina_uart_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) + port->type = PORT_GENERIC; +} + +static int cortina_uart_verify_port(struct uart_port *port, + struct serial_struct *serinfo) +{ + if (serinfo->type != PORT_UNKNOWN && serinfo->type != PORT_GENERIC) + return -EINVAL; + if (serinfo->irq != port->irq) + return -EINVAL; + + return 0; +} + +#ifdef CONFIG_CONSOLE_POLL +static int cortina_uart_poll_get_char(struct uart_port *port) +{ + u32 data; + + if (readl(port->membase + CORTINA_UART_INFO) & CORTINA_UART_INFO_RX_EMPTY) + return NO_POLL_CHAR; + + data = readl(port->membase + CORTINA_UART_RXDAT); + if (!(data & CORTINA_UART_RXDAT_VALID)) + return NO_POLL_CHAR; + + return FIELD_GET(CORTINA_UART_RXDAT_DATA, data); +} + +static void cortina_uart_poll_put_char(struct uart_port *port, unsigned char c) +{ + cortina_uart_putchar(port, c); +} +#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, + .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, +#ifdef CONFIG_CONSOLE_POLL + .poll_get_char = cortina_uart_poll_get_char, + .poll_put_char = cortina_uart_poll_put_char, +#endif +}; + +#ifdef CONFIG_SERIAL_CORTINA_CONSOLE +static void cortina_uart_console_write(struct console *co, const char *s, + unsigned int count) +{ + struct cortina_uart_port *up = cortina_uart_ports[co->index]; + struct uart_port *port; + unsigned long flags; + bool locked = true; + u64 timeout_us; + u32 ie, info; + + if (!up) + return; + port = &up->port; + + if (oops_in_progress) + locked = uart_port_trylock_irqsave(port, &flags); + else + uart_port_lock_irqsave(port, &flags); + + ie = readl(port->membase + CORTINA_UART_IE); + writel(ie & ~CORTINA_UART_IRQ_TX_EMPTY, port->membase + CORTINA_UART_IE); + + uart_console_write(port, s, count, cortina_uart_putchar); + + /* Wait for the whole FIFO to drain, which is slow at low rates */ + timeout_us = max_t(u64, CORTINA_UART_POLL_TIMEOUT_US, + div_u64((u64)port->frame_time * (port->fifosize + 1), + NSEC_PER_USEC)); + readl_poll_timeout_atomic(port->membase + CORTINA_UART_INFO, info, + info & CORTINA_UART_INFO_TX_EMPTY, 1, timeout_us); + writel(ie, port->membase + CORTINA_UART_IE); + + if (locked) + uart_port_unlock_irqrestore(port, flags); +} + +static int __init cortina_uart_console_setup(struct console *co, char *options) +{ + struct cortina_uart_port *up; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + + if (co->index < 0 || co->index >= CORTINA_UART_NR_PORTS) + return -ENODEV; + + up = cortina_uart_ports[co->index]; + if (!up) + return -ENODEV; + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + + return uart_set_options(&up->port, co, baud, parity, bits, flow); +} + +static struct uart_driver cortina_uart_driver; + +static struct console cortina_uart_console = { + .name = "ttyCS", + .write = cortina_uart_console_write, + .device = uart_console_device, + .setup = cortina_uart_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &cortina_uart_driver, +}; + +static void cortina_uart_earlycon_write(struct console *co, const char *s, + unsigned int count) +{ + struct earlycon_device *dev = co->data; + + uart_console_write(&dev->port, s, count, cortina_uart_putchar); +} + +static int __init cortina_uart_earlycon_setup(struct earlycon_device *dev, + const char *options) +{ + if (!dev->port.membase) + return -ENODEV; + + dev->con->write = cortina_uart_earlycon_write; + + return 0; +} + +OF_EARLYCON_DECLARE(cortina, "cortina,cs7542-uart", cortina_uart_earlycon_setup); + +#define CORTINA_UART_CONSOLE (&cortina_uart_console) +#else +#define CORTINA_UART_CONSOLE NULL +#endif + +static struct uart_driver cortina_uart_driver = { + .owner = THIS_MODULE, + .driver_name = "cortina-uart", + .dev_name = "ttyCS", + .nr = CORTINA_UART_NR_PORTS, + .cons = CORTINA_UART_CONSOLE, +}; + +static int cortina_uart_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cortina_uart_port *up; + struct uart_port *port; + struct resource *res; + int id, ret; + + up = devm_kzalloc(dev, sizeof(*up), GFP_KERNEL); + if (!up) + return -ENOMEM; + + port = &up->port; + + port->membase = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(port->membase)) + return PTR_ERR(port->membase); + + port->irq = platform_get_irq(pdev, 0); + if (port->irq < 0) + return port->irq; + + up->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(up->clk)) + return dev_err_probe(dev, PTR_ERR(up->clk), "failed to get clock\n"); + + port->uartclk = clk_get_rate(up->clk); + if (port->uartclk < CORTINA_UART_DIV_MIN * 9600) + return dev_err_probe(dev, -EINVAL, "clock rate %u Hz is too low\n", + port->uartclk); + + port->dev = dev; + port->mapbase = res->start; + port->mapsize = resource_size(res); + port->iotype = UPIO_MEM32; + port->ops = &cortina_uart_ops; + port->fifosize = CORTINA_UART_FIFO_SIZE; + port->flags = UPF_BOOT_AUTOCONF; + port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_CORTINA_CONSOLE); + + platform_set_drvdata(pdev, up); + + mutex_lock(&cortina_uart_ports_lock); + + id = of_alias_get_id(dev->of_node, "serial"); + if (id < 0) { + for (id = 0; id < CORTINA_UART_NR_PORTS; id++) + if (!cortina_uart_ports[id]) + break; + } + if (id >= CORTINA_UART_NR_PORTS) { + ret = dev_err_probe(dev, -EINVAL, "invalid port index %d\n", id); + goto out_unlock; + } + if (cortina_uart_ports[id]) { + ret = dev_err_probe(dev, -EBUSY, "port %d already in use\n", id); + goto out_unlock; + } + + port->line = id; + cortina_uart_ports[id] = up; + + ret = uart_add_one_port(&cortina_uart_driver, port); + if (ret) + cortina_uart_ports[id] = NULL; + +out_unlock: + mutex_unlock(&cortina_uart_ports_lock); + return ret; +} + +static void cortina_uart_remove(struct platform_device *pdev) +{ + struct cortina_uart_port *up = platform_get_drvdata(pdev); + + uart_remove_one_port(&cortina_uart_driver, &up->port); + + mutex_lock(&cortina_uart_ports_lock); + cortina_uart_ports[up->port.line] = NULL; + mutex_unlock(&cortina_uart_ports_lock); +} + +static const struct of_device_id cortina_uart_of_match[] = { + { .compatible = "cortina,cs7542-uart" }, + { } +}; +MODULE_DEVICE_TABLE(of, cortina_uart_of_match); + +static struct platform_driver cortina_uart_platform_driver = { + .probe = cortina_uart_probe, + .remove = cortina_uart_remove, + .driver = { + .name = "cortina-uart", + .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(&cortina_uart_platform_driver); + if (ret) + uart_unregister_driver(&cortina_uart_driver); + + return ret; +} +module_init(cortina_uart_init); + +static void __exit cortina_uart_exit(void) +{ + platform_driver_unregister(&cortina_uart_platform_driver); + uart_unregister_driver(&cortina_uart_driver); +} +module_exit(cortina_uart_exit); + +MODULE_DESCRIPTION("Cortina UART driver"); +MODULE_LICENSE("GPL"); -- 2.53.0