From: Jacky Huang <ychuang570808@gmail.com>
To: robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
lee@kernel.org, mturquette@baylibre.com, sboyd@kernel.org,
p.zabel@pengutronix.de, gregkh@linuxfoundation.org,
jirislaby@kernel.org
Cc: devicetree@vger.kernel.org, linux-clk@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-serial@vger.kernel.org,
arnd@arndb.de, schung@nuvoton.com, mjchen@nuvoton.com,
Jacky Huang <ychuang3@nuvoton.com>
Subject: [PATCH v6 11/12] tty: serial: Add Nuvoton ma35d1 serial driver support
Date: Tue, 28 Mar 2023 02:19:11 +0000 [thread overview]
Message-ID: <20230328021912.177301-12-ychuang570808@gmail.com> (raw)
In-Reply-To: <20230328021912.177301-1-ychuang570808@gmail.com>
From: Jacky Huang <ychuang3@nuvoton.com>
This adds UART and console driver for Nuvoton ma35d1 Soc.
It supports full-duplex communication, FIFO control, and
hardware flow control.
Signed-off-by: Jacky Huang <ychuang3@nuvoton.com>
---
drivers/tty/serial/Kconfig | 18 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/ma35d1_serial.c | 802 +++++++++++++++++++++++++++++
3 files changed, 821 insertions(+)
create mode 100644 drivers/tty/serial/ma35d1_serial.c
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 0072892ca7fc..daa4ae6bc08e 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1562,6 +1562,24 @@ config SERIAL_SUNPLUS_CONSOLE
you can alter that using a kernel command line option such as
"console=ttySUPx".
+config SERIAL_NUVOTON_MA35D1
+ tristate "Nuvoton MA35D1 family UART support"
+ depends on ARCH_NUVOTON || COMPILE_TEST
+ select SERIAL_CORE
+ help
+ This driver supports Nuvoton MA35D1 family UART ports. If you would
+ like to use them, you must answer Y or M to this option. Note that
+ for use as console, it must be included in kernel and not as a
+ module
+
+config SERIAL_NUVOTON_MA35D1_CONSOLE
+ bool "Console on a Nuvotn MA35D1 family UART port"
+ depends on SERIAL_NUVOTON_MA35D1=y
+ select SERIAL_CORE_CONSOLE
+ help
+ Select this options if you'd like to use the UART port0 of the
+ Nuvoton MA35D1 family as a console.
+
endmenu
config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index cd9afd9e3018..71ebeba06ff2 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -93,3 +93,4 @@ obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
obj-$(CONFIG_SERIAL_KGDB_NMI) += kgdb_nmi.o
obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o
+obj-$(CONFIG_SERIAL_NUVOTON_MA35D1) += ma35d1_serial.o
diff --git a/drivers/tty/serial/ma35d1_serial.c b/drivers/tty/serial/ma35d1_serial.c
new file mode 100644
index 000000000000..75618ab0b2ca
--- /dev/null
+++ b/drivers/tty/serial/ma35d1_serial.c
@@ -0,0 +1,802 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * MA35D1 serial driver
+ * Copyright (C) 2023 Nuvoton Technology Corp.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/serial_core.h>
+#include <linux/slab.h>
+#include <linux/tty_flip.h>
+
+#define UART_NR 17
+
+#define UART_REG_RBR 0x00
+#define UART_REG_THR 0x00
+#define UART_REG_IER 0x04
+#define UART_REG_FCR 0x08
+#define UART_REG_LCR 0x0C
+#define UART_REG_MCR 0x10
+#define UART_REG_MSR 0x14
+#define UART_REG_FSR 0x18
+#define UART_REG_ISR 0x1C
+#define UART_REG_TOR 0x20
+#define UART_REG_BAUD 0x24
+#define UART_REG_ALTCTL 0x2C
+#define UART_FUN_SEL 0x30
+#define UART_REG_WKCTL 0x40
+#define UART_REG_WKSTS 0x44
+
+/* UART_REG_IER - Interrupt Enable Register */
+#define IER_RDA_IEN BIT(0) /* RBR Available Interrupt Enable */
+#define IER_THRE_IEN BIT(1) /* THR Empty Interrupt Enable */
+#define IER_RLS_IEN BIT(2) /* RX Line Status Interrupt Enable */
+#define IER_RTO_IEN BIT(4) /* RX Time-out Interrupt Enable */
+#define IER_BUFERR_IEN BIT(5) /* Buffer Error Interrupt Enable */
+#define IER_TIME_OUT_EN BIT(11) /* RX Buffer Time-out Counter Enable */
+#define IER_AUTO_RTS BIT(12) /* nRTS Auto-flow Control Enable */
+#define IER_AUTO_CTS BIT(13) /* nCTS Auto-flow Control Enable */
+
+/* UART_REG_FCR - FIFO Control Register */
+#define FCR_RFR BIT(1) /* RX Field Software Reset */
+#define FCR_TFR BIT(2) /* TX Field Software Reset */
+#define FCR_RFITL_MASK GENMASK(7, 4) /* RX FIFO Interrupt Trigger Level */
+#define FCR_RFITL_1BYTE FIELD_PREP(FCR_RFITL_MASK, 0)
+#define FCR_RFITL_4BYTES FIELD_PREP(FCR_RFITL_MASK, 1)
+#define FCR_RFITL_8BYTES FIELD_PREP(FCR_RFITL_MASK, 2)
+#define FCR_RFITL_14BYTES FIELD_PREP(FCR_RFITL_MASK, 3)
+#define FCR_RFITL_30BYTES FIELD_PREP(FCR_RFITL_MASK, 4)
+#define FCR_RTSTRGLV_MASK GENMASK(19, 16) /* nRTS Trigger Level */
+#define FCR_RTSTRGLV_1BYTE FIELD_PREP(FCR_RTSTRGLV_MASK, 0)
+#define FCR_RTSTRGLV_4BYTES FIELD_PREP(FCR_RTSTRGLV_MASK, 1)
+#define FCR_RTSTRGLV_8BYTES FIELD_PREP(FCR_RTSTRGLV_MASK, 2)
+#define FCR_RTSTRGLV_14BYTES FIELD_PREP(FCR_RTSTRGLV_MASK, 3)
+#define FCR_RTSTRGLVL_30BYTES FIELD_PREP(FCR_RTSTRGLV_MASK, 4)
+
+/* UART_REG_LCR - Line Control Register */
+#define LCR_NSB BIT(2) /* Number of “STOP Bit” */
+#define LCR_PBE BIT(3) /* Parity Bit Enable */
+#define LCR_EPE BIT(4) /* Even Parity Enable */
+#define LCR_SPE BIT(5) /* Stick Parity Enable */
+#define LCR_BREAK BIT(6) /* Break Control */
+#define LCR_WLS_MASK GENMASK(1, 0) /* Word Length Selection */
+#define LCR_WLS_5BITS FIELD_PREP(LCR_WLS_MASK, 0)
+#define LCR_WLS_6BITS FIELD_PREP(LCR_WLS_MASK, 1)
+#define LCR_WLS_7BITS FIELD_PREP(LCR_WLS_MASK, 2)
+#define LCR_WLS_8BITS FIELD_PREP(LCR_WLS_MASK, 3)
+
+/* UART_REG_MCR - Modem Control Register */
+#define MCR_RTS_CTRL BIT(1) /* nRTS Signal Control */
+#define MCR_RTSACTLV BIT(9) /* nRTS Pin Active Level */
+#define MCR_RTSSTS BIT(13) /* nRTS Pin Status (Read Only) */
+
+/* UART_REG_MSR - Modem Status Register */
+#define MSR_CTSDETF BIT(0) /* Detect nCTS State Change Flag */
+#define MSR_CTSSTS BIT(4) /* nCTS Pin Status (Read Only) */
+#define MSR_CTSACTLV BIT(8) /* nCTS Pin Active Level */
+
+/* UART_REG_FSR - FIFO Status Register */
+#define FSR_RX_OVER_IF BIT(0) /* RX Overflow Error Interrupt Flag */
+#define FSR_PEF BIT(4) /* Parity Error Flag*/
+#define FSR_FEF BIT(5) /* Framing Error Flag */
+#define FSR_BIF BIT(6) /* Break Interrupt Flag */
+#define FSR_RX_EMPTY BIT(14) /* Receiver FIFO Empty (Read Only) */
+#define FSR_RX_FULL BIT(15) /* Receiver FIFO Full (Read Only) */
+#define FSR_TX_EMPTY BIT(22) /* Transmitter FIFO Empty (Read Only) */
+#define FSR_TX_FULL BIT(23) /* Transmitter FIFO Full (Read Only) */
+#define FSR_TX_OVER_IF BIT(24) /* TX Overflow Error Interrupt Flag */
+#define FSR_TE_FLAG BIT(28) /* Transmitter Empty Flag (Read Only) */
+#define FSR_RXPTR_MSK GENMASK(13, 8) /* TX FIFO Pointer mask */
+#define FSR_TXPTR_MSK GENMASK(21, 16) /* RX FIFO Pointer mask */
+
+/* UART_REG_ISR - Interrupt Status Register */
+#define ISR_RDA_IF BIT(0) /* RBR Available Interrupt Flag */
+#define ISR_THRE_IF BIT(1) /* THR Empty Interrupt Flag */
+#define ISR_RLSIF BIT(2) /* Receive Line Interrupt Flag */
+#define ISR_MODEMIF BIT(3) /* MODEM Interrupt Flag */
+#define ISR_RXTO_IF BIT(4) /* RX Time-out Interrupt Flag */
+#define ISR_BUFEIF BIT(5) /* Buffer Error Interrupt Flag */
+#define UART_ISR_WK_IF BIT(6) /* UART Wake-up Interrupt Flag */
+#define UART_ISR_RDAINT BIT(8) /* RBR Available Interrupt Indicator */
+#define ISR_THRE_INT BIT(9) /* THR Empty Interrupt Indicator */
+#define ISR_ALL 0xFFFFFFFF
+
+/* UART_REG_BAUD - Baud Rate Divider Register */
+#define BAUD_MODE_MASK GENMASK(29, 28)
+#define BAUD_MODE0 FIELD_PREP(BAUD_MODE_MASK, 0)
+#define BAUD_MODE1 FIELD_PREP(BAUD_MODE_MASK, 2)
+#define BAUD_MODE2 FIELD_PREP(BAUD_MODE_MASK, 3)
+
+/* UART_REG_ALTCTL - Alternate Control/Status Register */
+#define ALTCTL_RS485AUD BIT(10) /* RS-485 Auto Direction Function */
+
+/* UART_FUN_SEL - Function Select Register */
+#define FUN_SEL_MASK GENMASK(2, 0)
+#define FUN_SEL_UART FIELD_PREP(FUN_SEL_MASK, 0)
+#define FUN_SEL_RS485 FIELD_PREP(FUN_SEL_MASK, 3)
+
+/* UART FIFO depth */
+#define UART_FIFO_DEPTH 32
+/* UART console clock */
+#define UART_CONSOLE_CLK 24000000
+/* UART register ioremap size */
+#define UART_REG_SIZE 0x100
+/* Rx Timeout */
+#define UART_RX_TOUT 0x40
+
+#define UART_ISR_IF_CHECK (ISR_RDA_IF | ISR_RXTO_IF | ISR_THRE_INT | ISR_BUFEIF)
+
+#define LOOP_TIMEOUT 1000
+
+static struct uart_driver ma35d1serial_reg;
+struct clk *clk;
+
+struct uart_ma35d1_port {
+ struct uart_port port;
+ u16 capabilities; /* port capabilities */
+ u8 ier;
+ u8 lcr;
+ u8 mcr;
+ u32 baud_rate;
+ u32 console_baud_rate;
+ u32 console_line;
+ u32 console_int;
+};
+
+static struct device_node *ma35d1serial_uart_nodes[UART_NR];
+static struct uart_ma35d1_port ma35d1serial_ports[UART_NR] = { 0 };
+
+static struct uart_ma35d1_port *to_ma35d1_uart_port(struct uart_port *uart)
+{
+ return container_of(uart, struct uart_ma35d1_port, port);
+}
+
+static u32 serial_in(struct uart_ma35d1_port *p, u32 offset)
+{
+ return readl_relaxed(p->port.membase + offset);
+}
+
+static void serial_out(struct uart_ma35d1_port *p, u32 offset, u32 value)
+{
+ writel_relaxed(value, p->port.membase + offset);
+}
+
+static void __stop_tx(struct uart_ma35d1_port *p)
+{
+ u32 ier;
+
+ ier = serial_in(p, UART_REG_IER);
+ if (ier & IER_THRE_IEN)
+ serial_out(p, UART_REG_IER, ier & ~IER_THRE_IEN);
+}
+
+static void ma35d1serial_stop_tx(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = to_ma35d1_uart_port(port);
+
+ __stop_tx(up);
+}
+
+static void transmit_chars(struct uart_ma35d1_port *up)
+{
+ struct circ_buf *xmit = &up->port.state->xmit;
+ int count;
+ u8 ch;
+
+ if (uart_tx_stopped(&up->port)) {
+ ma35d1serial_stop_tx(&up->port);
+ return;
+ }
+ if (uart_circ_empty(xmit)) {
+ __stop_tx(up);
+ return;
+ }
+
+ count = UART_FIFO_DEPTH - ((serial_in(up, UART_REG_FSR) & FSR_TXPTR_MSK) >> 16);
+
+ uart_port_tx_limited(&up->port, ch, count,
+ !(serial_in(up, UART_REG_FSR) & FSR_TX_FULL),
+ serial_out(up, UART_REG_THR, ch),
+ ({}));
+
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(&up->port);
+
+ if (uart_circ_empty(xmit))
+ __stop_tx(up);
+}
+
+static void ma35d1serial_start_tx(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = to_ma35d1_uart_port(port);
+ u32 ier;
+
+ ier = serial_in(up, UART_REG_IER);
+ serial_out(up, UART_REG_IER, ier & ~IER_THRE_IEN);
+ transmit_chars(up);
+ serial_out(up, UART_REG_IER, ier | IER_THRE_IEN);
+}
+
+static void ma35d1serial_stop_rx(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = to_ma35d1_uart_port(port);
+
+ serial_out(up, UART_REG_IER, serial_in(up, UART_REG_IER) & ~IER_RDA_IEN);
+}
+
+static void receive_chars(struct uart_ma35d1_port *up)
+{
+ u8 flag;
+ u32 fsr;
+ unsigned int ch;
+ int max_count = 256;
+
+ fsr = serial_in(up, UART_REG_FSR);
+ do {
+ flag = TTY_NORMAL;
+ up->port.icount.rx++;
+
+ if (unlikely(fsr & (FSR_BIF | FSR_FEF | FSR_PEF | FSR_RX_OVER_IF))) {
+ if (fsr & FSR_BIF) {
+ up->port.icount.brk++;
+ if (uart_handle_break(&up->port))
+ continue;
+ }
+ if (fsr & FSR_FEF)
+ up->port.icount.frame++;
+ if (fsr & FSR_PEF)
+ up->port.icount.parity++;
+ if (fsr & FSR_RX_OVER_IF)
+ up->port.icount.overrun++;
+
+ serial_out(up, UART_REG_FSR, fsr &
+ (FSR_BIF | FSR_FEF | FSR_PEF | FSR_RX_OVER_IF));
+
+ if (fsr & FSR_BIF)
+ flag = TTY_BREAK;
+ else if (fsr & FSR_PEF)
+ flag = TTY_PARITY;
+ else if (fsr & FSR_FEF)
+ flag = TTY_FRAME;
+ }
+
+ ch = serial_in(up, UART_REG_RBR);
+ if (uart_handle_sysrq_char(&up->port, ch))
+ continue;
+
+ spin_lock(&up->port.lock);
+ uart_insert_char(&up->port, fsr, FSR_RX_OVER_IF, ch, flag);
+ spin_unlock(&up->port.lock);
+
+ fsr = serial_in(up, UART_REG_FSR);
+ } while (!(fsr & FSR_RX_EMPTY) && (max_count-- > 0));
+
+ spin_lock(&up->port.lock);
+ tty_flip_buffer_push(&up->port.state->port);
+ spin_unlock(&up->port.lock);
+}
+
+static irqreturn_t ma35d1serial_interrupt(int irq, void *dev_id)
+{
+ struct uart_port *port = dev_id;
+ struct uart_ma35d1_port *up = to_ma35d1_uart_port(port);
+ u32 isr, fsr;
+
+ isr = serial_in(up, UART_REG_ISR);
+ fsr = serial_in(up, UART_REG_FSR);
+
+ if (!(isr & UART_ISR_IF_CHECK))
+ return IRQ_NONE;
+
+ if (isr & (ISR_RDA_IF | ISR_RXTO_IF))
+ receive_chars(up);
+ if (isr & ISR_THRE_INT)
+ transmit_chars(up);
+ if (fsr & FSR_TX_OVER_IF)
+ serial_out(up, UART_REG_FSR, FSR_TX_OVER_IF);
+
+ return IRQ_HANDLED;
+}
+
+static u32 ma35d1serial_tx_empty(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = to_ma35d1_uart_port(port);
+ u32 fsr;
+
+ fsr = serial_in(up, UART_REG_FSR);
+ return (fsr & (FSR_TE_FLAG | FSR_TX_EMPTY)) ==
+ (FSR_TE_FLAG | FSR_TX_EMPTY) ? TIOCSER_TEMT : 0;
+}
+
+static u32 ma35d1serial_get_mctrl(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = to_ma35d1_uart_port(port);
+ u32 status;
+ u32 ret = 0;
+
+ status = serial_in(up, UART_REG_MSR);
+ if (!(status & MSR_CTSSTS))
+ ret |= TIOCM_CTS;
+ return ret;
+}
+
+static void ma35d1serial_set_mctrl(struct uart_port *port, u32 mctrl)
+{
+ struct uart_ma35d1_port *up = to_ma35d1_uart_port(port);
+ u32 mcr = 0;
+ u32 ier = 0;
+
+ if (mctrl & TIOCM_RTS) {
+ mcr = serial_in(up, UART_REG_MCR);
+ mcr |= MCR_RTSACTLV;
+ mcr &= ~MCR_RTS_CTRL;
+ }
+ if (up->mcr & UART_MCR_AFE) {
+ mcr = serial_in(up, UART_REG_MCR);
+ mcr |= MCR_RTSACTLV;
+ mcr &= ~MCR_RTS_CTRL;
+
+ serial_out(up, UART_REG_IER,
+ (serial_in(up, UART_REG_IER) | IER_AUTO_RTS | IER_AUTO_CTS));
+
+ up->port.flags |= UPF_HARD_FLOW;
+ } else {
+ ier = serial_in(up, UART_REG_IER);
+ ier &= ~(IER_AUTO_RTS | IER_AUTO_CTS);
+ serial_out(up, UART_REG_IER, ier);
+
+ up->port.flags &= ~UPF_HARD_FLOW;
+ }
+ serial_out(up, UART_REG_MSR, (serial_in(up, UART_REG_MSR) | MSR_CTSACTLV));
+ serial_out(up, UART_REG_MCR, mcr);
+}
+
+static void ma35d1serial_break_ctl(struct uart_port *port, int break_state)
+{
+ struct uart_ma35d1_port *up = to_ma35d1_uart_port(port);
+ unsigned long flags;
+ u32 lcr;
+
+ spin_lock_irqsave(&up->port.lock, flags);
+ lcr = serial_in(up, UART_REG_LCR);
+ if (break_state != 0)
+ lcr |= LCR_BREAK;
+ else
+ lcr &= ~LCR_BREAK;
+ serial_out(up, UART_REG_LCR, lcr);
+ spin_unlock_irqrestore(&up->port.lock, flags);
+}
+
+static int ma35d1serial_startup(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = to_ma35d1_uart_port(port);
+ int retval;
+
+ /* Reset FIFO */
+ serial_out(up, UART_REG_FCR, FCR_TFR | FCR_RFR);
+
+ /* Clear pending interrupts */
+ serial_out(up, UART_REG_ISR, ISR_ALL);
+
+ retval = request_irq(port->irq, ma35d1serial_interrupt, 0,
+ dev_name(port->dev), port);
+ if (retval) {
+ dev_err(up->port.dev, "request irq failed.\n");
+ return retval;
+ }
+
+ serial_out(up, UART_REG_FCR, serial_in(up, UART_REG_FCR) |
+ FCR_RFITL_4BYTES | FCR_RTSTRGLV_8BYTES);
+ serial_out(up, UART_REG_LCR, LCR_WLS_8BITS);
+ serial_out(up, UART_REG_TOR, UART_RX_TOUT);
+ serial_out(up, UART_REG_IER, IER_RTO_IEN | IER_RDA_IEN |
+ IER_TIME_OUT_EN | IER_BUFERR_IEN);
+ return 0;
+}
+
+static void ma35d1serial_shutdown(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = to_ma35d1_uart_port(port);
+
+ serial_out(up, UART_REG_IER, 0);
+ free_irq(port->irq, port);
+}
+
+static u32 ma35d1serial_get_divisor(struct uart_port *port, u32 baud)
+{
+ return (port->uartclk / baud) - 2;
+}
+
+static void ma35d1serial_set_termios(struct uart_port *port,
+ struct ktermios *termios,
+ const struct ktermios *old)
+{
+ struct uart_ma35d1_port *up = to_ma35d1_uart_port(port);
+ u32 lcr = 0;
+ unsigned long flags;
+ u32 baud, quot;
+
+ lcr = UART_LCR_WLEN(tty_get_char_size(termios->c_cflag));
+
+ if (termios->c_cflag & CSTOPB)
+ lcr |= LCR_NSB;
+ if (termios->c_cflag & PARENB)
+ lcr |= LCR_PBE;
+ if (!(termios->c_cflag & PARODD))
+ lcr |= LCR_EPE;
+ if (termios->c_cflag & CMSPAR)
+ lcr |= LCR_SPE;
+
+ baud = uart_get_baud_rate(port, termios, old, port->uartclk / 0xffff,
+ port->uartclk / 11);
+
+ quot = ma35d1serial_get_divisor(port, baud);
+
+ /*
+ * Ok, we're now changing the port state. Do it with
+ * interrupts disabled.
+ */
+ spin_lock_irqsave(&up->port.lock, flags);
+
+ up->port.read_status_mask = FSR_RX_OVER_IF;
+ if (termios->c_iflag & INPCK)
+ up->port.read_status_mask |= FSR_FEF | FSR_PEF;
+ if (termios->c_iflag & (BRKINT | PARMRK))
+ up->port.read_status_mask |= FSR_BIF;
+
+ /*
+ * Characteres to ignore
+ */
+ up->port.ignore_status_mask = 0;
+ if (termios->c_iflag & IGNPAR)
+ up->port.ignore_status_mask |= FSR_FEF | FSR_PEF;
+ if (termios->c_iflag & IGNBRK) {
+ up->port.ignore_status_mask |= FSR_BIF;
+ /*
+ * If we're ignoring parity and break indicators,
+ * ignore overruns too (for real raw support).
+ */
+ if (termios->c_iflag & IGNPAR)
+ up->port.ignore_status_mask |= FSR_RX_OVER_IF;
+ }
+ if (termios->c_cflag & CRTSCTS)
+ up->mcr |= UART_MCR_AFE;
+ else
+ up->mcr &= ~UART_MCR_AFE;
+
+ uart_update_timeout(port, termios->c_cflag, baud);
+ ma35d1serial_set_mctrl(&up->port, up->port.mctrl);
+ serial_out(up, UART_REG_BAUD, quot | BAUD_MODE2);
+ serial_out(up, UART_REG_LCR, lcr);
+ spin_unlock_irqrestore(&up->port.lock, flags);
+}
+
+static const char *ma35d1serial_type(struct uart_port *port)
+{
+ return "ma35d1-uart";
+}
+
+static void ma35d1serial_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 ma35d1serial_verify_port(struct uart_port *port,
+ struct serial_struct *ser)
+{
+ if (port->type != PORT_UNKNOWN && ser->type != 1)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct uart_ops ma35d1serial_ops = {
+ .tx_empty = ma35d1serial_tx_empty,
+ .set_mctrl = ma35d1serial_set_mctrl,
+ .get_mctrl = ma35d1serial_get_mctrl,
+ .stop_tx = ma35d1serial_stop_tx,
+ .start_tx = ma35d1serial_start_tx,
+ .stop_rx = ma35d1serial_stop_rx,
+ .break_ctl = ma35d1serial_break_ctl,
+ .startup = ma35d1serial_startup,
+ .shutdown = ma35d1serial_shutdown,
+ .set_termios = ma35d1serial_set_termios,
+ .type = ma35d1serial_type,
+ .config_port = ma35d1serial_config_port,
+ .verify_port = ma35d1serial_verify_port,
+};
+
+static const struct of_device_id ma35d1_serial_of_match[] = {
+ { .compatible = "nuvoton,ma35d1-uart" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ma35d1_serial_of_match);
+
+#ifdef CONFIG_SERIAL_NUVOTON_MA35D1_CONSOLE
+
+static void wait_for_xmitr(struct uart_ma35d1_port *up)
+{
+ unsigned int tmout;
+
+ /* Wait up to 10ms for the character(s) to be sent. */
+ tmout = 10000;
+ while (--tmout) {
+ if (serial_in(up, UART_REG_FSR) & FSR_TX_EMPTY)
+ break;
+ udelay(1);
+ }
+}
+
+static void ma35d1serial_console_putchar(struct uart_port *port, unsigned char ch)
+{
+ struct uart_ma35d1_port *up = to_ma35d1_uart_port(port);
+
+ wait_for_xmitr(up);
+ serial_out(up, UART_REG_THR, ch);
+}
+
+/*
+ * Print a string to the serial port trying not to disturb
+ * any possible real use of the port...
+ *
+ * The console_lock must be held when we get here.
+ */
+static void ma35d1serial_console_write(struct console *co,
+ const char *s, u32 count)
+{
+ struct uart_ma35d1_port *up = &ma35d1serial_ports[co->index];
+ unsigned long flags;
+ u32 ier;
+
+ local_irq_save(flags);
+
+ /*
+ * First save the IER then disable the interrupts
+ */
+ ier = serial_in(up, UART_REG_IER);
+ serial_out(up, UART_REG_IER, 0);
+
+ uart_console_write(&up->port, s, count, ma35d1serial_console_putchar);
+
+ wait_for_xmitr(up);
+
+ serial_out(up, UART_REG_IER, ier);
+ local_irq_restore(flags);
+}
+
+static int __init ma35d1serial_console_setup(struct console *co,
+ char *options)
+{
+ struct device_node *np = ma35d1serial_uart_nodes[co->index];
+ struct uart_ma35d1_port *p = &ma35d1serial_ports[co->index];
+ u32 val32[4];
+ struct uart_port *port;
+ int baud = 115200;
+ int bits = 8;
+ int parity = 'n';
+ int flow = 'n';
+
+ /*
+ * Check whether an invalid uart number has been specified, and
+ * if so, search for the first available port that does have
+ * console support.
+ */
+ if ((co->index < 0) || (co->index >= UART_NR)) {
+ pr_debug("Console Port%x out of range\n", co->index);
+ return -EINVAL;
+ }
+
+ if (of_property_read_u32_array(np, "reg", val32, 4) != 0)
+ return -EINVAL;
+ p->port.iobase = val32[1];
+ p->port.membase = ioremap(p->port.iobase, UART_REG_SIZE);
+ p->port.ops = &ma35d1serial_ops;
+ p->port.line = 0;
+ p->port.uartclk = UART_CONSOLE_CLK;
+
+ port = &ma35d1serial_ports[co->index].port;
+ return uart_set_options(port, co, baud, parity, bits, flow);
+}
+
+static struct console ma35d1serial_console = {
+ .name = "ttyS",
+ .write = ma35d1serial_console_write,
+ .device = uart_console_device,
+ .setup = ma35d1serial_console_setup,
+ .flags = CON_PRINTBUFFER | CON_ENABLED,
+ .index = -1,
+ .data = &ma35d1serial_reg,
+};
+
+static void ma35d1serial_console_init_port(void)
+{
+ int i = 0;
+ struct device_node *np;
+
+ for_each_matching_node(np, ma35d1_serial_of_match) {
+ if (ma35d1serial_uart_nodes[i] == NULL) {
+ of_node_get(np);
+ ma35d1serial_uart_nodes[i] = np;
+ i++;
+ if (i == UART_NR)
+ break;
+ }
+ }
+}
+
+static int __init ma35d1serial_console_init(void)
+{
+ ma35d1serial_console_init_port();
+ register_console(&ma35d1serial_console);
+ return 0;
+}
+console_initcall(ma35d1serial_console_init);
+
+#define MA35D1SERIAL_CONSOLE (&ma35d1serial_console)
+#else
+#define MA35D1SERIAL_CONSOLE NULL
+#endif
+
+static struct uart_driver ma35d1serial_reg = {
+ .owner = THIS_MODULE,
+ .driver_name = "serial",
+ .dev_name = "ttyS",
+ .major = TTY_MAJOR,
+ .minor = 64,
+ .cons = MA35D1SERIAL_CONSOLE,
+ .nr = UART_NR,
+};
+
+/*
+ * Register a set of serial devices attached to a platform device.
+ * The list is terminated with a zero flags entry, which means we expect
+ * all entries to have at least UPF_BOOT_AUTOCONF set.
+ */
+static int ma35d1serial_probe(struct platform_device *pdev)
+{
+ struct resource *res_mem;
+ struct uart_ma35d1_port *up;
+ int ret;
+ struct clk *clk;
+ int err;
+
+ if (pdev->dev.of_node) {
+ ret = of_alias_get_id(pdev->dev.of_node, "serial");
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to get alias/pdev id, errno %d\n", ret);
+ return ret;
+ }
+ }
+ up = &ma35d1serial_ports[ret];
+ up->port.line = ret;
+ res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res_mem)
+ return -ENODEV;
+
+ up->port.iobase = res_mem->start;
+ up->port.membase = ioremap(up->port.iobase, UART_REG_SIZE);
+ up->port.ops = &ma35d1serial_ops;
+
+ spin_lock_init(&up->port.lock);
+
+ clk = of_clk_get(pdev->dev.of_node, 0);
+ if (IS_ERR(clk)) {
+ err = PTR_ERR(clk);
+ dev_err(&pdev->dev, "failed to get core clk: %d\n", err);
+ return -ENOENT;
+ }
+ err = clk_prepare_enable(clk);
+ if (err)
+ return -ENOENT;
+
+ if (up->port.line != 0)
+ up->port.uartclk = clk_get_rate(clk);
+ up->port.irq = platform_get_irq(pdev, 0);
+ up->port.dev = &pdev->dev;
+ up->port.flags = UPF_BOOT_AUTOCONF;
+ ret = uart_add_one_port(&ma35d1serial_reg, &up->port);
+ platform_set_drvdata(pdev, up);
+ return 0;
+}
+
+/*
+ * Remove serial ports registered against a platform device.
+ */
+static int ma35d1serial_remove(struct platform_device *dev)
+{
+ struct uart_port *port = platform_get_drvdata(dev);
+
+ if (port) {
+ uart_remove_one_port(&ma35d1serial_reg, port);
+ free_irq(port->irq, port);
+ }
+ return 0;
+}
+
+static int ma35d1serial_suspend(struct platform_device *dev, pm_message_t state)
+{
+ int i;
+ struct uart_ma35d1_port *up;
+
+ if (dev->dev.of_node)
+ i = of_alias_get_id(dev->dev.of_node, "serial");
+ if (i < 0) {
+ dev_err(&dev->dev, "failed to get alias/pdev id, errno %d\n", i);
+ return i;
+ }
+ up = &ma35d1serial_ports[i];
+ if (i == 0) {
+ up->console_baud_rate = serial_in(up, UART_REG_BAUD);
+ up->console_line = serial_in(up, UART_REG_LCR);
+ up->console_int = serial_in(up, UART_REG_IER);
+ }
+ return 0;
+}
+
+static int ma35d1serial_resume(struct platform_device *dev)
+{
+ int i;
+ struct uart_ma35d1_port *up;
+
+ if (dev->dev.of_node)
+ i = of_alias_get_id(dev->dev.of_node, "serial");
+ if (i < 0) {
+ dev_err(&dev->dev, "failed to get alias/pdev id, errno %d\n", i);
+ return i;
+ }
+ up = &ma35d1serial_ports[i];
+ if (i == 0) {
+ serial_out(up, UART_REG_BAUD, up->console_baud_rate);
+ serial_out(up, UART_REG_LCR, up->console_line);
+ serial_out(up, UART_REG_IER, up->console_int);
+ }
+ return 0;
+}
+
+static struct platform_driver ma35d1serial_driver = {
+ .probe = ma35d1serial_probe,
+ .remove = ma35d1serial_remove,
+ .suspend = ma35d1serial_suspend,
+ .resume = ma35d1serial_resume,
+ .driver = {
+ .name = "ma35d1-uart",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(ma35d1_serial_of_match),
+ },
+};
+
+static int __init ma35d1serial_init(void)
+{
+ int ret;
+
+ ret = uart_register_driver(&ma35d1serial_reg);
+ if (ret)
+ return ret;
+ ret = platform_driver_register(&ma35d1serial_driver);
+ if (ret)
+ uart_unregister_driver(&ma35d1serial_reg);
+ return ret;
+}
+
+static void __exit ma35d1serial_exit(void)
+{
+ platform_driver_unregister(&ma35d1serial_driver);
+ uart_unregister_driver(&ma35d1serial_reg);
+}
+
+module_init(ma35d1serial_init);
+module_exit(ma35d1serial_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MA35D1 serial driver");
+MODULE_ALIAS_CHARDEV_MAJOR(TTY_MAJOR);
+
--
2.34.1
next prev parent reply other threads:[~2023-03-28 2:21 UTC|newest]
Thread overview: 56+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-03-28 2:19 [PATCH v6 00/12] Introduce Nuvoton ma35d1 SoC Jacky Huang
2023-03-28 2:19 ` [PATCH v6 01/12] arm64: Kconfig.platforms: Add config for Nuvoton MA35 platform Jacky Huang
2023-03-28 2:19 ` [PATCH v6 02/12] arm64: defconfig: Add support for Nuvoton MA35 family SoCs Jacky Huang
2023-03-28 2:19 ` [PATCH v6 03/12] dt-bindings: clock: nuvoton: add binding for ma35d1 clock controller Jacky Huang
2023-03-29 8:14 ` Krzysztof Kozlowski
2023-03-28 2:19 ` [PATCH v6 04/12] dt-bindings: reset: nuvoton: add binding for ma35d1 IP reset control Jacky Huang
2023-03-28 17:48 ` Stephen Boyd
2023-03-29 8:53 ` Jacky Huang
2023-03-29 8:17 ` Krzysztof Kozlowski
2023-03-29 9:12 ` Jacky Huang
2023-03-29 9:27 ` Krzysztof Kozlowski
2023-03-29 9:33 ` Jacky Huang
2023-03-28 2:19 ` [PATCH v6 05/12] dt-bindings: mfd: syscon: Add nuvoton,ma35d1-sys compatible Jacky Huang
2023-04-05 15:27 ` Lee Jones
2023-03-28 2:19 ` [PATCH v6 06/12] dt-bindings: arm: Add initial bindings for Nuvoton platform Jacky Huang
2023-03-28 15:41 ` kernel test robot
2023-03-29 8:19 ` Krzysztof Kozlowski
2023-03-29 8:32 ` Jacky Huang
2023-03-29 13:07 ` Rob Herring
2023-03-30 10:41 ` Jacky Huang
2023-03-30 13:25 ` Krzysztof Kozlowski
2023-03-31 2:15 ` Jacky Huang
2023-04-03 20:33 ` Rob Herring
2023-04-06 2:09 ` Jacky Huang
2023-03-28 2:19 ` [PATCH v6 07/12] dt-bindings: serial: Document ma35d1 uart controller Jacky Huang
2023-03-29 8:20 ` Krzysztof Kozlowski
2023-03-29 8:44 ` Jacky Huang
2023-03-30 7:33 ` Krzysztof Kozlowski
2023-03-30 10:52 ` Jacky Huang
2023-03-30 13:12 ` Rob Herring
2023-03-31 2:03 ` Jacky Huang
2023-03-28 2:19 ` [PATCH v6 08/12] arm64: dts: nuvoton: Add initial ma35d1 device tree Jacky Huang
2023-03-28 17:57 ` Stephen Boyd
2023-03-29 2:03 ` Jacky Huang
2023-03-29 2:19 ` Stephen Boyd
2023-03-29 2:39 ` Jacky Huang
2023-03-29 2:46 ` Stephen Boyd
2023-03-29 3:13 ` Jacky Huang
2023-03-29 3:25 ` Stephen Boyd
2023-03-29 3:43 ` Jacky Huang
2023-03-29 3:54 ` Stephen Boyd
2023-03-29 6:02 ` Jacky Huang
2023-03-29 17:52 ` Stephen Boyd
2023-03-29 8:21 ` Krzysztof Kozlowski
2023-03-29 8:36 ` Jacky Huang
2023-03-28 2:19 ` [PATCH v6 09/12] clk: nuvoton: Add clock driver for ma35d1 clock controller Jacky Huang
2023-03-28 18:18 ` Stephen Boyd
2023-03-30 10:36 ` Jacky Huang
2023-03-28 2:19 ` [PATCH v6 10/12] reset: Add Nuvoton ma35d1 reset driver support Jacky Huang
2023-04-24 20:02 ` Philipp Zabel
2023-04-25 1:22 ` Jacky Huang
2023-03-28 2:19 ` Jacky Huang [this message]
2023-03-28 9:23 ` [PATCH v6 11/12] tty: serial: Add Nuvoton ma35d1 serial " Jiri Slaby
2023-03-29 8:06 ` Jacky Huang
2023-03-31 0:29 ` kernel test robot
2023-03-28 2:19 ` [PATCH v6 12/12] MAINTAINERS: Add entry for NUVOTON MA35 Jacky Huang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230328021912.177301-12-ychuang570808@gmail.com \
--to=ychuang570808@gmail.com \
--cc=arnd@arndb.de \
--cc=devicetree@vger.kernel.org \
--cc=gregkh@linuxfoundation.org \
--cc=jirislaby@kernel.org \
--cc=krzysztof.kozlowski+dt@linaro.org \
--cc=lee@kernel.org \
--cc=linux-clk@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-serial@vger.kernel.org \
--cc=mjchen@nuvoton.com \
--cc=mturquette@baylibre.com \
--cc=p.zabel@pengutronix.de \
--cc=robh+dt@kernel.org \
--cc=sboyd@kernel.org \
--cc=schung@nuvoton.com \
--cc=ychuang3@nuvoton.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.