[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20240912-test-v1-17-458fa57c8ccf@analog.com>
Date: Thu, 12 Sep 2024 19:25:02 +0100
From: Arturs Artamonovs via B4 Relay <devnull+arturs.artamonovs.analog.com@...nel.org>
To: Catalin Marinas <catalin.marinas@....com>,
Will Deacon <will@...nel.org>, Greg Malysa <greg.malysa@...esys.com>,
Philipp Zabel <p.zabel@...gutronix.de>, Rob Herring <robh@...nel.org>,
Krzysztof Kozlowski <krzk+dt@...nel.org>,
Conor Dooley <conor+dt@...nel.org>,
Utsav Agarwal <Utsav.Agarwal@...log.com>,
Michael Turquette <mturquette@...libre.com>,
Stephen Boyd <sboyd@...nel.org>, Linus Walleij <linus.walleij@...aro.org>,
Bartosz Golaszewski <brgl@...ev.pl>, Thomas Gleixner <tglx@...utronix.de>,
Andi Shyti <andi.shyti@...nel.org>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Jiri Slaby <jirislaby@...nel.org>, Arnd Bergmann <arnd@...db.de>,
Olof Johansson <olof@...om.net>, soc@...nel.org
Cc: linux-arm-kernel@...ts.infradead.org, linux-kernel@...r.kernel.org,
devicetree@...r.kernel.org, linux-clk@...r.kernel.org,
linux-gpio@...r.kernel.org, linux-i2c@...r.kernel.org,
linux-serial@...r.kernel.org,
Arturs Artamonovs <arturs.artamonovs@...log.com>, adsp-linux@...log.com,
Arturs Artamonovs <Arturs.Artamonovs@...log.com>,
Nathan Barrett-Morrison <nathan.morrison@...esys.com>
Subject: [PATCH 17/21] serial: adi,uart: Add driver for ADI ADSP-SC5xx
From: Arturs Artamonovs <arturs.artamonovs@...log.com>
Adding UART driver,supports all ADSP-SC5xx SoC family
- Support FIFO mode
- Support earlyprintk
- Support Enable Divide By One support, for higher clock resolutions.
Signed-off-by: Arturs Artamonovs <Arturs.Artamonovs@...log.com>
Signed-off-by: Utsav Agarwal <Utsav.Agarwal@...log.com>
Co-developed-by: Nathan Barrett-Morrison <nathan.morrison@...esys.com>
Signed-off-by: Nathan Barrett-Morrison <nathan.morrison@...esys.com>
Co-developed-by: Greg Malysa <greg.malysa@...esys.com>
Signed-off-by: Greg Malysa <greg.malysa@...esys.com>
---
drivers/tty/serial/Kconfig | 19 +-
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/adi_uart.c | 1045 ++++++++++++++++++++++++++++++++++++++
include/uapi/linux/serial_core.h | 3 +
4 files changed, 1067 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 28e4beeabf8f373fc76e09ea7d1c9d55a66f4964..1d1d8fc808969c721d5931127d9294fb17d9c249 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -471,6 +471,23 @@ config SERIAL_SA1100_CONSOLE
your boot loader (lilo or loadlin) about how to pass options to the
kernel at boot time.)
+config SERIAL_ADI_UART
+ tristate "ADI uart serial port support"
+ depends on ARCH_SC59X = y || ARCH_SC59X_64 = y
+ select SERIAL_CORE
+ select SERIAL_CORE_CONSOLE
+ help
+ Add support for the built-in adi uart driver.
+
+config SERIAL_ADI_UART_CONSOLE
+ bool "Console on ADI uart serial port"
+ depends on SERIAL_ADI_UART
+ default y
+ select SERIAL_CORE_CONSOLE
+ help
+ If you have enabled the ADI UART serial port, you can
+ make it the console by answering Y to this option.
+
config SERIAL_IMX
tristate "IMX serial port support"
depends on ARCH_MXC || COMPILE_TEST
@@ -771,7 +788,7 @@ config SERIAL_CPM
depends on CPM2 || CPM1
select SERIAL_CORE
help
- This driver supports the SCC and SMC serial ports on Motorola
+ This driver supports the SCC and SMC serial ports on Motorola
embedded PowerPC that contain a CPM1 (8xx) or CPM2 (8xxx)
config SERIAL_CPM_CONSOLE
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 6ff74f0a9530c4f6e058a848f74084f3b63a730a..9d4920b51b55af70c285d9bcaef53cc01a69b898 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_SERIAL_JSM) += jsm/
obj-$(CONFIG_SERIAL_LANTIQ) += lantiq.o
obj-$(CONFIG_SERIAL_LITEUART) += liteuart.o
obj-$(CONFIG_SERIAL_HS_LPC32XX) += lpc32xx_hs.o
+obj-$(CONFIG_SERIAL_ADI_UART) += adi_uart.o
obj-$(CONFIG_SERIAL_MAX3100) += max3100.o
obj-$(CONFIG_SERIAL_MAX310X) += max310x.o
obj-$(CONFIG_SERIAL_MCF) += mcf.o
diff --git a/drivers/tty/serial/adi_uart.c b/drivers/tty/serial/adi_uart.c
new file mode 100644
index 0000000000000000000000000000000000000000..dfbc7dcfd6169a299d4b1580e56f5ba0a3d8cc12
--- /dev/null
+++ b/drivers/tty/serial/adi_uart.c
@@ -0,0 +1,1045 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ADI On-Chip Two Wire Interface Driver
+ *
+ * Copyright 2022-2024 - Analog Devices Inc.
+ */
+
+#include <linux/circ_buf.h>
+#include <linux/clk.h>
+#include <linux/dmaengine.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/serial_core.h>
+#include <linux/tty_flip.h>
+
+#if defined(CONFIG_SERIAL_ADI_UART_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
+#define SUPPORT_SYSRQ
+#endif
+
+#define DRIVER_NAME "adi-uart"
+
+struct adi_uart_serial_port {
+ struct uart_port port;
+ struct device *dev;
+ unsigned int old_status;
+ int tx_irq;
+ int rx_irq;
+ int status_irq;
+ unsigned int lsr;
+ unsigned int hwflow_mode;
+ struct gpio_desc *hwflow_en_pin;
+ bool hwflow_en;
+ /* Use enable-divide-by-one in divisor? */
+ bool edbo;
+ struct clk *clk;
+};
+
+#define ADI_UART_NO_HWFLOW 0
+#define ADI_UART_HWFLOW_PERI 1
+
+#define ADI_UART_NR_PORTS 4
+static struct adi_uart_serial_port *adi_uart_serial_ports[ADI_UART_NR_PORTS];
+
+/* UART_CTL Masks */
+#define UCEN 0x1 /* Enable UARTx Clocks */
+#define LOOP_ENA 0x2 /* Loopback Mode Enable */
+#define UMOD_MDB 0x10 /* Enable MDB Mode */
+#define UMOD_IRDA 0x20 /* Enable IrDA Mode */
+#define UMOD_MASK 0x30 /* Uart Mode Mask */
+#define WLS(x) (((x-5) & 0x03) << 8) /* Word Length Select */
+#define WLS_MASK 0x300 /* Word length Select Mask */
+#define WLS_OFFSET 8 /* Word length Select Offset */
+#define STB 0x1000 /* Stop Bits */
+#define STBH 0x2000 /* Half Stop Bits */
+#define PEN 0x4000 /* Parity Enable */
+#define EPS 0x8000 /* Even Parity Select */
+#define STP 0x10000 /* Stick Parity */
+#define FPE 0x20000 /* Force Parity Error On Transmit */
+#define FFE 0x40000 /* Force Framing Error On Transmit */
+#define SB 0x80000 /* Set Break */
+#define LCR_MASK (SB | STP | EPS | PEN | STB | WLS_MASK)
+#define FCPOL 0x400000 /* Flow Control Pin Polarity */
+#define RPOLC 0x800000 /* IrDA RX Polarity Change */
+#define TPOLC 0x1000000 /* IrDA TX Polarity Change */
+#define MRTS 0x2000000 /* Manual Request To Send */
+#define XOFF 0x4000000 /* Transmitter Off */
+#define ARTS 0x8000000 /* Automatic Request To Send */
+#define ACTS 0x10000000 /* Automatic Clear To Send */
+#define RFIT 0x20000000 /* Receive FIFO IRQ Threshold */
+#define RFRT 0x40000000 /* Receive FIFO RTS Threshold */
+
+/* UART_STAT Masks */
+#define DR 0x01 /* Data Ready */
+#define OE 0x02 /* Overrun Error */
+#define PE 0x04 /* Parity Error */
+#define FE 0x08 /* Framing Error */
+#define BI 0x10 /* Break Interrupt */
+#define THRE 0x20 /* THR Empty */
+#define TEMT 0x80 /* TSR and UART_THR Empty */
+#define TFI 0x100 /* Transmission Finished Indicator */
+
+#define ASTKY 0x200 /* Address Sticky */
+#define ADDR 0x400 /* Address bit status */
+#define RO 0x800 /* Reception Ongoing */
+#define SCTS 0x1000 /* Sticky CTS */
+#define CTS 0x10000 /* Clear To Send */
+#define RFCS 0x20000 /* Receive FIFO Count Status */
+
+/* UART_CLOCK Masks */
+#define EDBO 0x80000000 /* Enable Devide by One */
+
+/* UART_IER Masks */
+#define ERBFI 0x01 /* Enable Receive Buffer Full Interrupt */
+#define ETBEI 0x02 /* Enable Transmit Buffer Empty Interrupt */
+#define ELSI 0x04 /* Enable RX Status Interrupt */
+#define EDSSI 0x08 /* Enable Modem Status Interrupt */
+#define EDTPTI 0x10 /* Enable DMA Transmit PIRQ Interrupt */
+#define ETFI 0x20 /* Enable Transmission Finished Interrupt */
+#define ERFCI 0x40 /* Enable Receive FIFO Count Interrupt */
+
+# define OFFSET_REDIV 0x00 /* Version ID Register */
+# define OFFSET_CTL 0x04 /* Control Register */
+# define OFFSET_STAT 0x08 /* Status Register */
+# define OFFSET_SCR 0x0C /* SCR Scratch Register */
+# define OFFSET_CLK 0x10 /* Clock Rate Register */
+# define OFFSET_IER 0x14 /* Interrupt Enable Register */
+# define OFFSET_IER_SET 0x18 /* Set Interrupt Enable Register */
+# define OFFSET_IER_CLEAR 0x1C /* Clear Interrupt Enable Register */
+# define OFFSET_RBR 0x20 /* Receive Buffer register */
+# define OFFSET_THR 0x24 /* Transmit Holding register */
+
+#define UART_GET_CHAR(p) readl(p->port.membase + OFFSET_RBR)
+#define UART_GET_CLK(p) readl(p->port.membase + OFFSET_CLK)
+#define UART_GET_CTL(p) readl(p->port.membase + OFFSET_CTL)
+#define UART_GET_GCTL(p) UART_GET_CTL(p)
+#define UART_GET_LCR(p) UART_GET_CTL(p)
+#define UART_GET_MCR(p) UART_GET_CTL(p)
+#define UART_GET_STAT(p) readl(p->port.membase + OFFSET_STAT)
+#define UART_GET_MSR(p) UART_GET_STAT(p)
+
+#define UART_PUT_CHAR(p, v) writel(v, p->port.membase + OFFSET_THR)
+#define UART_PUT_CLK(p, v) writel(v, p->port.membase + OFFSET_CLK)
+#define UART_PUT_CTL(p, v) writel(v, p->port.membase + OFFSET_CTL)
+#define UART_PUT_GCTL(p, v) UART_PUT_CTL(p, v)
+#define UART_PUT_LCR(p, v) UART_PUT_CTL(p, v)
+#define UART_PUT_MCR(p, v) UART_PUT_CTL(p, v)
+#define UART_PUT_STAT(p, v) writel(v, p->port.membase + OFFSET_STAT)
+
+#define UART_CLEAR_IER(p, v) writel(v, p->port.membase + OFFSET_IER_CLEAR)
+#define UART_GET_IER(p) readl(p->port.membase + OFFSET_IER)
+#define UART_SET_IER(p, v) writel(v, p->port.membase + OFFSET_IER_SET)
+
+#define UART_CLEAR_LSR(p) UART_PUT_STAT(p, -1)
+#define UART_GET_LSR(p) UART_GET_STAT(p)
+#define UART_PUT_LSR(p, v) UART_PUT_STAT(p, v)
+
+/* This handles hard CTS/RTS */
+#define UART_CLEAR_SCTS(p) UART_PUT_STAT(p, SCTS)
+#define UART_GET_CTS(x) (UART_GET_MSR(x) & CTS)
+#define UART_DISABLE_RTS(x) UART_PUT_MCR(x, UART_GET_MCR(x) & ~(ARTS | MRTS))
+#define UART_ENABLE_RTS(x) UART_PUT_MCR(x, UART_GET_MCR(x) | MRTS | ARTS)
+#define UART_ENABLE_INTS(x, v) UART_SET_IER(x, v)
+#define UART_DISABLE_INTS(x) UART_CLEAR_IER(x, 0xF)
+
+#define DMA_RX_XCOUNT 512
+#define DMA_RX_YCOUNT (PAGE_SIZE / DMA_RX_XCOUNT)
+
+#define DMA_RX_FLUSH_JIFFIES (msecs_to_jiffies(50))
+
+
+static void adi_uart_serial_tx_chars(struct adi_uart_serial_port *uart);
+static void adi_uart_serial_reset_irda(struct uart_port *port);
+
+
+static struct adi_uart_serial_port *to_adi_serial_port(struct uart_port *port)
+{
+ return container_of(port, struct adi_uart_serial_port, port);
+}
+
+static unsigned int adi_uart_serial_get_mctrl(struct uart_port *port)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+
+ if (!uart->hwflow_mode || !uart->hwflow_en)
+ return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR;
+
+ /* CTS PIN is negative assertive. */
+ if (UART_GET_CTS(uart))
+ return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR;
+ else
+ return TIOCM_DSR | TIOCM_CAR;
+}
+
+static void adi_uart_serial_set_mctrl(struct uart_port *port,
+ unsigned int mctrl)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+
+ if (!uart->hwflow_mode || !uart->hwflow_en)
+ return;
+
+ /* RTS PIN is negative assertive. */
+ if (mctrl & TIOCM_RTS)
+ UART_ENABLE_RTS(uart);
+ else
+ UART_DISABLE_RTS(uart);
+}
+
+/*
+ * Handle any change of modem status signal.
+ */
+static irqreturn_t adi_uart_serial_mctrl_cts_int(int irq, void *dev_id)
+{
+ struct adi_uart_serial_port *uart = dev_id;
+ unsigned int status = adi_uart_serial_get_mctrl(&uart->port);
+ struct tty_struct *tty = uart->port.state->port.tty;
+
+ if (uart->hwflow_mode == ADI_UART_HWFLOW_PERI) {
+ UART_CLEAR_SCTS(uart);
+ if (tty->hw_stopped) {
+ if (status) {
+ tty->hw_stopped = 0;
+ uart_write_wakeup(&uart->port);
+ }
+ } else {
+ if (!status)
+ tty->hw_stopped = 1;
+ }
+ }
+
+ uart_handle_cts_change(&uart->port, status & TIOCM_CTS);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * interrupts are disabled on entry
+ */
+static void adi_uart_serial_stop_tx(struct uart_port *port)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+
+ while (!(UART_GET_LSR(uart) & TEMT))
+ cpu_relax();
+
+ UART_PUT_LSR(uart, TFI);
+ UART_CLEAR_IER(uart, ETBEI);
+}
+
+/*
+ * port is locked and interrupts are disabled
+ */
+static void adi_uart_serial_start_tx(struct uart_port *port)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+ struct tty_struct *tty = uart->port.state->port.tty;
+
+ /*
+ * To avoid losting RX interrupt, we reset IR function
+ * before sending data.
+ */
+ if (tty->termios.c_line == N_IRDA)
+ adi_uart_serial_reset_irda(port);
+
+ UART_SET_IER(uart, ETBEI);
+ adi_uart_serial_tx_chars(uart);
+}
+
+/*
+ * Interrupts are enabled
+ */
+static void adi_uart_serial_stop_rx(struct uart_port *port)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+
+ UART_CLEAR_IER(uart, ERBFI);
+}
+
+/*
+ * Set the modem control timer to fire immediately.
+ */
+static void adi_uart_serial_enable_ms(struct uart_port *port)
+{
+}
+
+static void adi_uart_serial_rx_chars(struct adi_uart_serial_port *uart)
+{
+ unsigned int status, ch, flg;
+
+ status = UART_GET_LSR(uart);
+ UART_CLEAR_LSR(uart);
+
+ ch = UART_GET_CHAR(uart);
+ uart->port.icount.rx++;
+
+ if (status & BI) {
+ uart->port.icount.brk++;
+ if (uart_handle_break(&uart->port))
+ goto ignore_char;
+ status &= ~(PE | FE);
+ }
+ if (status & PE)
+ uart->port.icount.parity++;
+ if (status & OE)
+ uart->port.icount.overrun++;
+ if (status & FE)
+ uart->port.icount.frame++;
+
+ status &= uart->port.read_status_mask;
+
+ if (status & BI)
+ flg = TTY_BREAK;
+ else if (status & PE)
+ flg = TTY_PARITY;
+ else if (status & FE)
+ flg = TTY_FRAME;
+ else
+ flg = TTY_NORMAL;
+
+ if (uart_handle_sysrq_char(&uart->port, ch))
+ goto ignore_char;
+
+ uart_insert_char(&uart->port, status, OE, ch, flg);
+
+ ignore_char:
+ tty_flip_buffer_push(&uart->port.state->port);
+}
+
+static void adi_uart_serial_tx_chars(struct adi_uart_serial_port *uart)
+{
+ struct tty_port *tport = &uart->port.state->port;
+ unsigned char c;
+
+ if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(&uart->port)) {
+ /* Clear TFI bit */
+ UART_PUT_LSR(uart, TFI);
+ /* Anomaly notes:
+ * 05000215 - we always clear ETBEI within last UART TX
+ * interrupt to end a string. It is always set
+ * when start a new tx.
+ */
+ UART_CLEAR_IER(uart, ETBEI);
+ return;
+ }
+
+ if (uart->port.x_char) {
+ UART_PUT_CHAR(uart, uart->port.x_char);
+ uart->port.icount.tx++;
+ uart->port.x_char = 0;
+ }
+
+ if (UART_GET_LSR(uart) & THRE) {
+ /* pop data from fifo */
+ if (kfifo_get(&tport->xmit_fifo, &c)) {
+ UART_PUT_CHAR(uart, c);
+ uart->port.icount.tx++;
+ }
+ }
+
+ if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+ uart_write_wakeup(&uart->port);
+}
+
+static irqreturn_t adi_uart_serial_rx_int(int irq, void *dev_id)
+{
+ struct adi_uart_serial_port *uart = dev_id;
+
+ while (UART_GET_LSR(uart) & DR)
+ adi_uart_serial_rx_chars(uart);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t adi_uart_serial_tx_int(int irq, void *dev_id)
+{
+ struct adi_uart_serial_port *uart = dev_id;
+
+ spin_lock(&uart->port.lock);
+ if (UART_GET_LSR(uart) & THRE)
+ adi_uart_serial_tx_chars(uart);
+ spin_unlock(&uart->port.lock);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Return TIOCSER_TEMT when transmitter is not busy.
+ */
+static unsigned int adi_uart_serial_tx_empty(struct uart_port *port)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+ unsigned int lsr;
+
+ lsr = UART_GET_LSR(uart);
+ if (lsr & TEMT)
+ return TIOCSER_TEMT;
+ else
+ return 0;
+}
+
+static void adi_uart_serial_break_ctl(struct uart_port *port, int break_state)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+ u32 lcr = UART_GET_LCR(uart);
+
+ if (break_state)
+ lcr |= SB;
+ else
+ lcr &= ~SB;
+ UART_PUT_LCR(uart, lcr);
+}
+
+static int adi_uart_serial_startup(struct uart_port *port)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+ int ret;
+
+ ret = clk_prepare_enable(uart->clk);
+ if (ret)
+ return ret;
+
+ if (uart->hwflow_mode == ADI_UART_HWFLOW_PERI) {
+ /* CTS RTS PINs are negative assertive. */
+ UART_PUT_MCR(uart, UART_GET_MCR(uart) | ACTS);
+ UART_SET_IER(uart, EDSSI);
+ }
+
+ UART_SET_IER(uart, ERBFI);
+ return 0;
+}
+
+static void adi_uart_serial_shutdown(struct uart_port *port)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+
+ dev_dbg(uart->dev, "in serial_shutdown\n");
+
+ clk_disable_unprepare(uart->clk);
+}
+
+static void adi_uart_serial_set_termios(struct uart_port *port,
+ struct ktermios *termios, const struct ktermios *old)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+ unsigned long flags;
+ unsigned int baud, quot;
+ unsigned int ier, lcr = 0;
+ unsigned long timeout;
+
+ if (uart->hwflow_mode == ADI_UART_HWFLOW_PERI)
+ termios->c_cflag |= CRTSCTS;
+
+ switch (termios->c_cflag & CSIZE) {
+ case CS8:
+ lcr = WLS(8);
+ break;
+ case CS7:
+ lcr = WLS(7);
+ break;
+ case CS6:
+ lcr = WLS(6);
+ break;
+ case CS5:
+ lcr = WLS(5);
+ break;
+ default:
+ dev_err(port->dev, "%s: word length not supported\n",
+ __func__);
+ }
+
+ if (termios->c_cflag & CSTOPB)
+ lcr |= STB;
+ if (termios->c_cflag & PARENB)
+ lcr |= PEN;
+ if (!(termios->c_cflag & PARODD))
+ lcr |= EPS;
+ if (termios->c_cflag & CMSPAR)
+ lcr |= STP;
+ if (termios->c_cflag & CRTSCTS)
+ uart->hwflow_en = true;
+ else
+ uart->hwflow_en = false;
+
+ spin_lock_irqsave(&uart->port.lock, flags);
+
+ port->read_status_mask = OE;
+ if (termios->c_iflag & INPCK)
+ port->read_status_mask |= (FE | PE);
+ if (termios->c_iflag & (BRKINT | PARMRK))
+ port->read_status_mask |= BI;
+
+ /*
+ * Characters to ignore
+ */
+ port->ignore_status_mask = 0;
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= FE | PE;
+ if (termios->c_iflag & IGNBRK) {
+ port->ignore_status_mask |= BI;
+ /*
+ * If we're ignoring parity and break indicators,
+ * ignore overruns too (for real raw support).
+ */
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= OE;
+ }
+
+ /*
+ * uart_get_divisor has a hardcoded /16 factor that will cause integer
+ * round off errors if we're in divide-by-one mode
+ */
+ if (uart->edbo) {
+ baud = uart_get_baud_rate(port, termios, old, 0,
+ port->uartclk);
+ quot = EDBO | DIV_ROUND_CLOSEST(port->uartclk, baud);
+ } else {
+ baud = uart_get_baud_rate(port, termios, old, 0,
+ port->uartclk/16);
+ quot = uart_get_divisor(port, baud);
+ }
+
+ /* Wait till the transfer buffer is empty */
+ timeout = jiffies + msecs_to_jiffies(10);
+ while (UART_GET_GCTL(uart) & UCEN && !(UART_GET_LSR(uart) & TEMT))
+ if (time_after(jiffies, timeout)) {
+ dev_warn(port->dev,
+ "timeout waiting for TX buffer empty\n");
+ break;
+ }
+
+ /* Wait till the transfer buffer is empty */
+ timeout = jiffies + msecs_to_jiffies(10);
+ while (UART_GET_GCTL(uart) & UCEN && !(UART_GET_LSR(uart) & TEMT))
+ if (time_after(jiffies, timeout)) {
+ dev_warn(port->dev,
+ "timeout waiting for TX buffer empty\n");
+ break;
+ }
+
+ /* Disable UART */
+ ier = UART_GET_IER(uart);
+ UART_PUT_GCTL(uart, UART_GET_GCTL(uart) & ~UCEN);
+ UART_DISABLE_INTS(uart);
+
+ UART_PUT_CLK(uart, quot);
+
+ UART_PUT_LCR(uart, (UART_GET_LCR(uart) & ~LCR_MASK) | lcr);
+
+ /* Enable UART */
+ UART_ENABLE_INTS(uart, ier);
+ UART_PUT_GCTL(uart, UART_GET_GCTL(uart) | UCEN);
+
+ /* Port speed changed, update the per-port timeout. */
+ uart_update_timeout(port, termios->c_cflag, baud);
+
+ spin_unlock_irqrestore(&uart->port.lock, flags);
+}
+
+static const char *adi_uart_serial_type(struct uart_port *port)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+
+ return uart->port.type == PORT_BFIN ? "ADI-UART" : NULL;
+}
+
+/*
+ * Release the memory region(s) being used by 'port'.
+ */
+static void adi_uart_serial_release_port(struct uart_port *port)
+{
+}
+
+/*
+ * Request the memory region(s) being used by 'port'.
+ */
+static int adi_uart_serial_request_port(struct uart_port *port)
+{
+ return 0;
+}
+
+/*
+ * Configure/autoconfigure the port.
+ */
+static void adi_uart_serial_config_port(struct uart_port *port, int flags)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+
+ if (flags & UART_CONFIG_TYPE &&
+ adi_uart_serial_request_port(&uart->port) == 0)
+ uart->port.type = PORT_BFIN;
+}
+
+/*
+ * Verify the new serial_struct (for TIOCSSERIAL).
+ * The only change we allow are to the flags and type, and
+ * even then only between PORT_BFIN and PORT_UNKNOWN
+ */
+static int
+adi_uart_serial_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+ return 0;
+}
+
+/*
+ * Enable the IrDA function if tty->ldisc.num is N_IRDA.
+ * In other cases, disable IrDA function.
+ */
+static void adi_uart_serial_set_ldisc(struct uart_port *port,
+ struct ktermios *termios)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+ unsigned int val;
+
+ switch (termios->c_line) {
+ case N_IRDA:
+ val = UART_GET_GCTL(uart);
+ val |= (UMOD_IRDA | RPOLC);
+ UART_PUT_GCTL(uart, val);
+ break;
+ default:
+ val = UART_GET_GCTL(uart);
+ val &= ~(UMOD_MASK | RPOLC);
+ UART_PUT_GCTL(uart, val);
+ }
+}
+
+static void adi_uart_serial_reset_irda(struct uart_port *port)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+ unsigned int val;
+
+ val = UART_GET_GCTL(uart);
+ val &= ~(UMOD_MASK | RPOLC);
+ UART_PUT_GCTL(uart, val);
+ val |= (UMOD_IRDA | RPOLC);
+ UART_PUT_GCTL(uart, val);
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+static void adi_uart_serial_poll_put_char(struct uart_port *port,
+ unsigned char chr)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+
+ while (!(UART_GET_LSR(uart) & THRE))
+ cpu_relax();
+
+ UART_PUT_CHAR(uart, (unsigned char)chr);
+}
+
+static int adi_uart_serial_poll_get_char(struct uart_port *port)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+ unsigned char chr;
+
+ while (!(UART_GET_LSR(uart) & DR))
+ cpu_relax();
+
+ chr = UART_GET_CHAR(uart);
+
+ return chr;
+}
+#endif
+
+
+static const struct uart_ops adi_uart_serial_pops = {
+ .tx_empty = adi_uart_serial_tx_empty,
+ .set_mctrl = adi_uart_serial_set_mctrl,
+ .get_mctrl = adi_uart_serial_get_mctrl,
+ .stop_tx = adi_uart_serial_stop_tx,
+ .start_tx = adi_uart_serial_start_tx,
+ .stop_rx = adi_uart_serial_stop_rx,
+ .enable_ms = adi_uart_serial_enable_ms,
+ .break_ctl = adi_uart_serial_break_ctl,
+ .startup = adi_uart_serial_startup,
+ .shutdown = adi_uart_serial_shutdown,
+ .set_termios = adi_uart_serial_set_termios,
+ .set_ldisc = adi_uart_serial_set_ldisc,
+ .type = adi_uart_serial_type,
+ .release_port = adi_uart_serial_release_port,
+ .request_port = adi_uart_serial_request_port,
+ .config_port = adi_uart_serial_config_port,
+ .verify_port = adi_uart_serial_verify_port,
+#ifdef CONFIG_CONSOLE_POLL
+ .poll_put_char = adi_uart_serial_poll_put_char,
+ .poll_get_char = adi_uart_serial_poll_get_char,
+#endif
+};
+
+#ifdef CONFIG_SERIAL_ADI_UART_CONSOLE
+static void adi_uart_serial_console_putchar(struct uart_port *port,
+ unsigned char ch)
+{
+ struct adi_uart_serial_port *uart = to_adi_serial_port(port);
+
+ while (!(UART_GET_LSR(uart) & THRE))
+ barrier();
+ UART_PUT_CHAR(uart, ch);
+}
+
+static void __init
+adi_uart_serial_console_get_options(struct adi_uart_serial_port *uart,
+ int *baud, int *parity, int *bits)
+{
+ unsigned int status;
+
+ status = UART_GET_IER(uart) & (ERBFI | ETBEI);
+ if (status == (ERBFI | ETBEI)) {
+ /* ok, the port was enabled */
+ u32 lcr, clk;
+
+ lcr = UART_GET_LCR(uart);
+
+ *parity = 'n';
+ if (lcr & PEN) {
+ if (lcr & EPS)
+ *parity = 'e';
+ else
+ *parity = 'o';
+ }
+ *bits = ((lcr & WLS_MASK) >> WLS_OFFSET) + 5;
+
+ clk = UART_GET_CLK(uart);
+
+ /* Only the lowest 16 bits are the divisor */
+ if (clk & EDBO)
+ *baud = uart->port.uartclk / (clk & 0xffff);
+ else
+ *baud = uart->port.uartclk / (16*clk);
+ }
+ pr_debug("%s:baud = %d, parity = %c, bits= %d\n", __func__,
+ *baud, *parity, *bits);
+}
+
+static void
+adi_uart_serial_console_write(struct console *co, const char *s,
+ unsigned int count)
+{
+ struct adi_uart_serial_port *uart = adi_uart_serial_ports[co->index];
+ unsigned long flags;
+
+ spin_lock_irqsave(&uart->port.lock, flags);
+ uart_console_write(&uart->port, s, count,
+ adi_uart_serial_console_putchar);
+ spin_unlock_irqrestore(&uart->port.lock, flags);
+
+}
+
+static int __init
+adi_uart_serial_console_setup(struct console *co, char *options)
+{
+ struct adi_uart_serial_port *uart;
+ 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 >= ADI_UART_NR_PORTS)
+ return -ENODEV;
+
+ uart = adi_uart_serial_ports[co->index];
+ if (!uart)
+ return -ENODEV;
+
+ if (uart->hwflow_mode == ADI_UART_HWFLOW_PERI)
+ flow = 'r';
+
+ if (options)
+ uart_parse_options(options, &baud, &parity, &bits, &flow);
+ else
+ adi_uart_serial_console_get_options(uart, &baud, &parity,
+ &bits);
+
+ return uart_set_options(&uart->port, co, baud, parity, bits, flow);
+}
+
+static struct uart_driver adi_uart_serial_reg;
+
+static struct console adi_uart_serial_console = {
+ .name = "ttySC",
+ .write = adi_uart_serial_console_write,
+ .device = uart_console_device,
+ .setup = adi_uart_serial_console_setup,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+ .data = &adi_uart_serial_reg,
+};
+
+
+#define ADI_SERIAL_UART_CONSOLE (&adi_uart_serial_console)
+#else
+#define ADI_SERIAL_UART_CONSOLE NULL
+#endif
+
+static struct uart_driver adi_uart_serial_reg = {
+ .owner = THIS_MODULE,
+ .driver_name = DRIVER_NAME,
+ .dev_name = "ttySC",
+ .major = TTY_MAJOR,
+#ifdef CONFIG_ARCH_SC59X_64
+ // Other serial drivers are using 64 --
+ // Can probably disable in the future and set this back to 64
+ .minor = 74,
+#else
+ .minor = 64,
+#endif
+ .nr = ADI_UART_NR_PORTS,
+ .cons = ADI_SERIAL_UART_CONSOLE,
+};
+
+static int adi_uart_serial_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct adi_uart_serial_port *uart = platform_get_drvdata(pdev);
+
+ clk_disable(uart->clk);
+ return uart_suspend_port(&adi_uart_serial_reg, &uart->port);
+}
+
+static int adi_uart_serial_resume(struct platform_device *pdev)
+{
+ struct adi_uart_serial_port *uart = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = clk_enable(uart->clk);
+ if (ret)
+ return ret;
+
+ return uart_resume_port(&adi_uart_serial_reg, &uart->port);
+}
+
+static int adi_uart_serial_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct adi_uart_serial_port *uart = NULL;
+ int ret = 0;
+ int uartid;
+ dev_info(dev, "Serial probe\n");
+
+ uartid = of_alias_get_id(np, "serial");
+
+ if (uartid < 0) {
+ dev_err(&pdev->dev, "failed to get alias/pdev id, errno %d\n",
+ uartid);
+ ret = -ENODEV;
+ return ret;
+ }
+
+ if (adi_uart_serial_ports[uartid] == NULL) {
+ uart = kzalloc(sizeof(*uart), GFP_KERNEL);
+ if (!uart)
+ return -ENOMEM;
+
+ adi_uart_serial_ports[uartid] = uart;
+ uart->dev = &pdev->dev;
+
+ uart->clk = devm_clk_get(dev, "sclk0");
+ if (IS_ERR(uart->clk))
+ return -ENODEV;
+
+ spin_lock_init(&uart->port.lock);
+ uart->port.uartclk = clk_get_rate(uart->clk);
+ uart->port.fifosize = 8;
+ uart->port.ops = &adi_uart_serial_pops;
+ uart->port.line = uartid;
+ uart->port.iotype = UPIO_MEM;
+ uart->port.flags = UPF_BOOT_AUTOCONF;
+
+ uart->port.membase = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(uart->port.membase))
+ return PTR_ERR(uart->port.membase);
+
+ uart->tx_irq = platform_get_irq_byname(pdev, "tx");
+ uart->rx_irq = platform_get_irq_byname(pdev, "rx");
+ uart->status_irq =
+ platform_get_irq_byname(pdev, "status");
+ uart->port.irq = uart->rx_irq;//
+ ret = devm_request_threaded_irq(dev, uart->rx_irq,
+ adi_uart_serial_rx_int, NULL, 0, "ADI UART RX",
+ uart);
+ if (ret) {
+ dev_err(dev, "Unable to attach UART RX int\n");
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(dev, uart->tx_irq,
+ adi_uart_serial_tx_int, NULL, 0, "ADI UART TX",
+ uart);
+ if (ret) {
+ dev_err(dev, "Unable to attach UART TX int\n");
+ return ret;
+ }
+
+ /* adi,uart-has-rtscts is deprecated */
+ if (of_property_read_bool(np, "uart-has-rtscts") ||
+ of_property_read_bool(np, "adi,uart-has-rtscts")) {
+ uart->hwflow_mode = ADI_UART_HWFLOW_PERI;
+ ret = devm_request_threaded_irq(dev, uart->status_irq,
+ adi_uart_serial_mctrl_cts_int, NULL, 0,
+ "ADI UART Modem Status",
+ uart);
+ if (ret) {
+ uart->hwflow_mode = ADI_UART_NO_HWFLOW;
+ dev_info(dev,
+ "Unable to attach UART Modem Status int.\n");
+ }
+ } else
+ uart->hwflow_mode = ADI_UART_NO_HWFLOW;
+
+ uart->edbo = false;
+ if (of_property_read_bool(np, "adi,use-edbo"))
+ uart->edbo = true;
+
+ if (uart->hwflow_mode == ADI_UART_HWFLOW_PERI) {
+ uart->hwflow_en_pin = devm_gpiod_get(dev, "hwflow-en",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(uart->hwflow_en_pin)) {
+ dev_err(dev,
+ "hwflow-en required in peripheral hwflow mode\n");
+ return PTR_ERR(uart->hwflow_en_pin);
+ }
+ }
+ }
+
+ uart = adi_uart_serial_ports[uartid];
+ uart->port.dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, uart);
+
+ ret = uart_add_one_port(&adi_uart_serial_reg, &uart->port);
+ if (!ret)
+ return 0;
+
+ if (uart) {
+ adi_uart_serial_ports[uartid] = NULL;
+ kfree(uart);
+ }
+
+ return ret;
+}
+
+static void adi_uart_serial_remove(struct platform_device *pdev)
+{
+ struct adi_uart_serial_port *uart = platform_get_drvdata(pdev);
+
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ if (uart) {
+ uart_remove_one_port(&adi_uart_serial_reg, &uart->port);
+ adi_uart_serial_ports[uart->port.line] = NULL;
+ kfree(uart);
+ }
+}
+
+static const struct of_device_id adi_uart_dt_match[] = {
+ { .compatible = "adi,uart"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, adi_uart_dt_match);
+
+static struct platform_driver adi_uart_serial_driver = {
+ .probe = adi_uart_serial_probe,
+ .remove = adi_uart_serial_remove,
+ .suspend = adi_uart_serial_suspend,
+ .resume = adi_uart_serial_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = adi_uart_dt_match,
+ },
+};
+
+static int __init adi_uart_serial_init(void)
+{
+ int ret;
+
+ pr_info("ADI serial driver\n");
+
+ ret = uart_register_driver(&adi_uart_serial_reg);
+ if (ret) {
+ pr_err("failed to register %s:%d\n",
+ adi_uart_serial_reg.driver_name, ret);
+ }
+
+ ret = platform_driver_register(&adi_uart_serial_driver);
+ if (ret) {
+ pr_err("fail to register ADI uart\n");
+ uart_unregister_driver(&adi_uart_serial_reg);
+ }
+
+ return ret;
+}
+
+static void __exit adi_uart_serial_exit(void)
+{
+ platform_driver_unregister(&adi_uart_serial_driver);
+ uart_unregister_driver(&adi_uart_serial_reg);
+}
+
+module_init(adi_uart_serial_init);
+module_exit(adi_uart_serial_exit);
+
+/* Early Console Support */
+static inline u32 adi_uart_read(struct uart_port *port, u32 off)
+{
+ return readl(port->membase + off);
+}
+
+static inline void adi_uart_write(struct uart_port *port, u32 val,
+ u32 off)
+{
+ writel(val, port->membase + off);
+}
+
+
+static void adi_uart_wait_bit_set(struct uart_port *port, unsigned int offset,
+ u32 bit)
+{
+ while (!(adi_uart_read(port, offset) & bit))
+ cpu_relax();
+}
+
+
+static void adi_uart_console_putchar(struct uart_port *port, unsigned char ch)
+{
+ /* wait for the hardware fifo to clear up */
+ adi_uart_wait_bit_set(port, OFFSET_STAT, THRE);
+
+ /* queue the character for transmission */
+ adi_uart_write(port, ch, OFFSET_THR);
+}
+
+
+static void adi_uart_early_write(struct console *con, const char *s,
+ unsigned int n)
+{
+ struct earlycon_device *dev = con->data;
+
+ uart_console_write(&dev->port, s, n, adi_uart_console_putchar);
+}
+
+
+static int __init adi_uart_early_console_setup(struct earlycon_device *device,
+ const char *opt)
+{
+ if (!device->port.membase)
+ return -ENODEV;
+
+ device->con->write = adi_uart_early_write;
+ return 0;
+}
+
+EARLYCON_DECLARE(adi_uart, adi_uart_early_console_setup);
+
+MODULE_AUTHOR("Sonic Zhang, Aubrey Li");
+MODULE_DESCRIPTION("Blackfin/ADSP generic serial port driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS_CHARDEV_MAJOR(BFIN_SERIAL_MAJOR);
\ No newline at end of file
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 9c007a106330b90b92cbcf60a9ac806b290d6d44..ce3c50dfa5cacd09eb30e93e408c2d92992ac755 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -109,6 +109,9 @@
/* Xilinx uartlite */
#define PORT_UARTLITE 74
+/* Blackfin */
+#define PORT_BFIN 75
+
/* Broadcom BCM7271 UART */
#define PORT_BCM7271 76
--
2.25.1
Powered by blists - more mailing lists