Linux Serial subsystem development
 help / color / mirror / Atom feed
* [PATCH v2] serial: Add Milbeaut serial control
From: Sugaya Taichi @ 2019-04-15 11:31 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby
  Cc: Arnd Bergmann, Takao Orito, Kazuhiro Kasai, Shinji Kanematsu,
	Jassi Brar, Masami Hiramatsu, linux-kernel, linux-serial,
	Sugaya Taichi

Add Milbeaut serial control including earlycon and console.

Signed-off-by: Sugaya Taichi <sugaya.taichi@socionext.com>
---
Changes from v1:
 - Add "COMPILE_TEST" dependency for coverage test.

 drivers/tty/serial/Kconfig         |  26 ++
 drivers/tty/serial/Makefile        |   1 +
 drivers/tty/serial/milbeaut_usio.c | 621 +++++++++++++++++++++++++++++++++++++
 include/uapi/linux/serial_core.h   |   3 +
 4 files changed, 651 insertions(+)
 create mode 100644 drivers/tty/serial/milbeaut_usio.c

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 72966bc..d1971a8 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1582,6 +1582,32 @@ config SERIAL_RDA_CONSOLE
 	  Say 'Y' here if you wish to use the RDA8810PL UART as the system
 	  console. Only earlycon is implemented currently.
 
+config SERIAL_MILBEAUT_USIO
+	tristate "Milbeaut USIO/UART serial port support"
+	depends on ARCH_MILBEAUT || (COMPILE_TEST && OF)
+	default ARCH_MILBEAUT
+	select SERIAL_CORE
+	help
+	  This selects the USIO/UART IP found in Socionext Milbeaut SoCs.
+
+config SERIAL_MILBEAUT_USIO_PORTS
+	int "Maximum number of CSIO/UART ports (1-8)"
+	range 1 8
+	depends on SERIAL_MILBEAUT_USIO
+	default "4"
+
+config SERIAL_MILBEAUT_USIO_CONSOLE
+	bool "Support for console on MILBEAUT USIO/UART serial port"
+	depends on SERIAL_MILBEAUT_USIO=y
+	default y
+	select SERIAL_CORE_CONSOLE
+	select SERIAL_EARLYCON
+	help
+	  Say 'Y' here if you wish to use a USIO/UART of Socionext Milbeaut
+	  SoCs as the system console (the system console is the device which
+	  receives all kernel messages and warnings and which allows logins in
+	  single user mode).
+
 endmenu
 
 config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 40b702a..43ca2d0 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_SERIAL_PIC32)	+= pic32_uart.o
 obj-$(CONFIG_SERIAL_MPS2_UART)	+= mps2-uart.o
 obj-$(CONFIG_SERIAL_OWL)	+= owl-uart.o
 obj-$(CONFIG_SERIAL_RDA)	+= rda-uart.o
+obj-$(CONFIG_SERIAL_MILBEAUT_USIO) += milbeaut_usio.o
 
 # GPIOLIB helpers for modem control lines
 obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
diff --git a/drivers/tty/serial/milbeaut_usio.c b/drivers/tty/serial/milbeaut_usio.c
new file mode 100644
index 0000000..d303b7d
--- /dev/null
+++ b/drivers/tty/serial/milbeaut_usio.c
@@ -0,0 +1,621 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Socionext Inc.
+ */
+
+#if defined(CONFIG_SERIAL_MILBEAUT_USIO_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
+#define SUPPORT_SYSRQ
+#endif
+
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/serial_core.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+
+#define USIO_NAME		"mlb-usio-uart"
+#define USIO_UART_DEV_NAME	"ttyUSI"
+
+static struct uart_port mlb_usio_ports[CONFIG_SERIAL_MILBEAUT_USIO_PORTS];
+
+#define RX	0
+#define TX	1
+static int mlb_usio_irq[CONFIG_SERIAL_MILBEAUT_USIO_PORTS][2];
+
+#define MLB_USIO_REG_SMR		0
+#define MLB_USIO_REG_SCR		1
+#define MLB_USIO_REG_ESCR		2
+#define MLB_USIO_REG_SSR		3
+#define MLB_USIO_REG_DR			4
+#define MLB_USIO_REG_BGR		6
+#define MLB_USIO_REG_FCR		12
+#define MLB_USIO_REG_FBYTE		14
+
+#define MLB_USIO_SMR_SOE		BIT(0)
+#define MLB_USIO_SMR_SBL		BIT(3)
+#define MLB_USIO_SCR_TXE		BIT(0)
+#define MLB_USIO_SCR_RXE		BIT(1)
+#define MLB_USIO_SCR_TBIE		BIT(2)
+#define MLB_USIO_SCR_TIE		BIT(3)
+#define MLB_USIO_SCR_RIE		BIT(4)
+#define MLB_USIO_SCR_UPCL		BIT(7)
+#define MLB_USIO_ESCR_L_8BIT		0
+#define MLB_USIO_ESCR_L_5BIT		1
+#define MLB_USIO_ESCR_L_6BIT		2
+#define MLB_USIO_ESCR_L_7BIT		3
+#define MLB_USIO_ESCR_P			BIT(3)
+#define MLB_USIO_ESCR_PEN		BIT(4)
+#define MLB_USIO_ESCR_FLWEN		BIT(7)
+#define MLB_USIO_SSR_TBI		BIT(0)
+#define MLB_USIO_SSR_TDRE		BIT(1)
+#define MLB_USIO_SSR_RDRF		BIT(2)
+#define MLB_USIO_SSR_ORE		BIT(3)
+#define MLB_USIO_SSR_FRE		BIT(4)
+#define MLB_USIO_SSR_PE			BIT(5)
+#define MLB_USIO_SSR_REC		BIT(7)
+#define MLB_USIO_SSR_BRK		BIT(8)
+#define MLB_USIO_FCR_FE1		BIT(0)
+#define MLB_USIO_FCR_FE2		BIT(1)
+#define MLB_USIO_FCR_FCL1		BIT(2)
+#define MLB_USIO_FCR_FCL2		BIT(3)
+#define MLB_USIO_FCR_FSET		BIT(4)
+#define MLB_USIO_FCR_FTIE		BIT(9)
+#define MLB_USIO_FCR_FDRQ		BIT(10)
+#define MLB_USIO_FCR_FRIIE		BIT(11)
+
+static void mlb_usio_stop_tx(struct uart_port *port)
+{
+	writew(readw(port->membase + MLB_USIO_REG_FCR) & ~MLB_USIO_FCR_FTIE,
+	       port->membase + MLB_USIO_REG_FCR);
+	writeb(readb(port->membase + MLB_USIO_REG_SCR) & ~MLB_USIO_SCR_TBIE,
+	       port->membase + MLB_USIO_REG_SCR);
+}
+
+static void mlb_usio_tx_chars(struct uart_port *port)
+{
+	struct circ_buf *xmit = &port->state->xmit;
+	int count;
+
+	writew(readw(port->membase + MLB_USIO_REG_FCR) & ~MLB_USIO_FCR_FTIE,
+	       port->membase + MLB_USIO_REG_FCR);
+	writeb(readb(port->membase + MLB_USIO_REG_SCR) &
+	       ~(MLB_USIO_SCR_TIE | MLB_USIO_SCR_TBIE),
+	       port->membase + MLB_USIO_REG_SCR);
+
+	if (port->x_char) {
+		writew(port->x_char, port->membase + MLB_USIO_REG_DR);
+		port->icount.tx++;
+		port->x_char = 0;
+		return;
+	}
+	if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
+		mlb_usio_stop_tx(port);
+		return;
+	}
+
+	count = port->fifosize -
+		(readw(port->membase + MLB_USIO_REG_FBYTE) & 0xff);
+
+	do {
+		writew(xmit->buf[xmit->tail], port->membase + MLB_USIO_REG_DR);
+
+		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+		port->icount.tx++;
+		if (uart_circ_empty(xmit))
+			break;
+
+	} while (--count > 0);
+
+	writew(readw(port->membase + MLB_USIO_REG_FCR) & ~MLB_USIO_FCR_FDRQ,
+	       port->membase + MLB_USIO_REG_FCR);
+
+	writeb(readb(port->membase + MLB_USIO_REG_SCR) | MLB_USIO_SCR_TBIE,
+	       port->membase + MLB_USIO_REG_SCR);
+
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(port);
+
+	if (uart_circ_empty(xmit))
+		mlb_usio_stop_tx(port);
+}
+
+static void mlb_usio_start_tx(struct uart_port *port)
+{
+	u16 fcr = readw(port->membase + MLB_USIO_REG_FCR);
+
+	writew(fcr | MLB_USIO_FCR_FTIE, port->membase + MLB_USIO_REG_FCR);
+	if (!(fcr & MLB_USIO_FCR_FDRQ))
+		return;
+
+	writeb(readb(port->membase + MLB_USIO_REG_SCR) | MLB_USIO_SCR_TBIE,
+	       port->membase + MLB_USIO_REG_SCR);
+
+	if (readb(port->membase + MLB_USIO_REG_SSR) & MLB_USIO_SSR_TBI)
+		mlb_usio_tx_chars(port);
+}
+
+static void mlb_usio_stop_rx(struct uart_port *port)
+{
+	writeb(readb(port->membase + MLB_USIO_REG_SCR) & ~MLB_USIO_SCR_RIE,
+	       port->membase + MLB_USIO_REG_SCR);
+}
+
+static void mlb_usio_enable_ms(struct uart_port *port)
+{
+	writeb(readb(port->membase + MLB_USIO_REG_SCR) |
+	       MLB_USIO_SCR_RIE | MLB_USIO_SCR_RXE,
+	       port->membase + MLB_USIO_REG_SCR);
+}
+
+static void mlb_usio_rx_chars(struct uart_port *port)
+{
+	struct tty_port *ttyport = &port->state->port;
+	unsigned long flag = 0;
+	char ch = 0;
+	u8 status;
+	int max_count = 2;
+
+	while (max_count--) {
+		status = readb(port->membase + MLB_USIO_REG_SSR);
+
+		if (!(status & MLB_USIO_SSR_RDRF))
+			break;
+
+		if (!(status & (MLB_USIO_SSR_ORE | MLB_USIO_SSR_FRE |
+				MLB_USIO_SSR_PE))) {
+			ch = readw(port->membase + MLB_USIO_REG_DR);
+			flag = TTY_NORMAL;
+			port->icount.rx++;
+			if (uart_handle_sysrq_char(port, ch))
+				continue;
+			uart_insert_char(port, status, MLB_USIO_SSR_ORE,
+					 ch, flag);
+			continue;
+		}
+		if (status & MLB_USIO_SSR_PE)
+			port->icount.parity++;
+		if (status & MLB_USIO_SSR_ORE)
+			port->icount.overrun++;
+		status &= port->read_status_mask;
+		if (status & MLB_USIO_SSR_BRK) {
+			flag = TTY_BREAK;
+			ch = 0;
+		} else
+			if (status & MLB_USIO_SSR_PE) {
+				flag = TTY_PARITY;
+				ch = 0;
+			} else
+				if (status & MLB_USIO_SSR_FRE) {
+					flag = TTY_FRAME;
+					ch = 0;
+				}
+		if (flag)
+			uart_insert_char(port, status, MLB_USIO_SSR_ORE,
+					 ch, flag);
+
+		writeb(readb(port->membase + MLB_USIO_REG_SSR) |
+				MLB_USIO_SSR_REC,
+				port->membase + MLB_USIO_REG_SSR);
+
+		max_count = readw(port->membase + MLB_USIO_REG_FBYTE) >> 8;
+		writew(readw(port->membase + MLB_USIO_REG_FCR) |
+		       MLB_USIO_FCR_FE2 | MLB_USIO_FCR_FRIIE,
+		port->membase + MLB_USIO_REG_FCR);
+	}
+
+	tty_flip_buffer_push(ttyport);
+}
+
+static irqreturn_t mlb_usio_rx_irq(int irq, void *dev_id)
+{
+	struct uart_port *port = dev_id;
+
+	spin_lock(&port->lock);
+	mlb_usio_rx_chars(port);
+	spin_unlock(&port->lock);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t mlb_usio_tx_irq(int irq, void *dev_id)
+{
+	struct uart_port *port = dev_id;
+
+	spin_lock(&port->lock);
+	if (readb(port->membase + MLB_USIO_REG_SSR) & MLB_USIO_SSR_TBI)
+		mlb_usio_tx_chars(port);
+	spin_unlock(&port->lock);
+
+	return IRQ_HANDLED;
+}
+
+static unsigned int mlb_usio_tx_empty(struct uart_port *port)
+{
+	return (readb(port->membase + MLB_USIO_REG_SSR) & MLB_USIO_SSR_TBI) ?
+		TIOCSER_TEMT : 0;
+}
+
+static void mlb_usio_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+}
+
+static unsigned int mlb_usio_get_mctrl(struct uart_port *port)
+{
+	return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
+
+}
+
+static void mlb_usio_break_ctl(struct uart_port *port, int break_state)
+{
+}
+
+static int mlb_usio_startup(struct uart_port *port)
+{
+	const char *portname = to_platform_device(port->dev)->name;
+	unsigned long flags;
+	int ret, index = port->line;
+	unsigned char  escr;
+
+	ret = request_irq(mlb_usio_irq[index][RX], mlb_usio_rx_irq,
+				0, portname, port);
+	if (ret)
+		return ret;
+	ret = request_irq(mlb_usio_irq[index][TX], mlb_usio_tx_irq,
+				0, portname, port);
+	if (ret) {
+		free_irq(mlb_usio_irq[index][RX], port);
+		return ret;
+	}
+
+	escr = readb(port->membase + MLB_USIO_REG_ESCR);
+	if (of_property_read_bool(port->dev->of_node, "auto-flow-control"))
+		escr |= MLB_USIO_ESCR_FLWEN;
+	spin_lock_irqsave(&port->lock, flags);
+	writeb(0, port->membase + MLB_USIO_REG_SCR);
+	writeb(escr, port->membase + MLB_USIO_REG_ESCR);
+	writeb(MLB_USIO_SCR_UPCL, port->membase + MLB_USIO_REG_SCR);
+	writeb(MLB_USIO_SSR_REC, port->membase + MLB_USIO_REG_SSR);
+	writew(0, port->membase + MLB_USIO_REG_FCR);
+	writew(MLB_USIO_FCR_FCL1 | MLB_USIO_FCR_FCL2,
+	       port->membase + MLB_USIO_REG_FCR);
+	writew(MLB_USIO_FCR_FE1 | MLB_USIO_FCR_FE2 | MLB_USIO_FCR_FRIIE,
+	       port->membase + MLB_USIO_REG_FCR);
+	writew(0, port->membase + MLB_USIO_REG_FBYTE);
+	writew(BIT(12), port->membase + MLB_USIO_REG_FBYTE);
+
+	writeb(MLB_USIO_SCR_TXE  | MLB_USIO_SCR_RIE | MLB_USIO_SCR_TBIE |
+	       MLB_USIO_SCR_RXE, port->membase + MLB_USIO_REG_SCR);
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	return 0;
+}
+
+static void mlb_usio_shutdown(struct uart_port *port)
+{
+	int index = port->line;
+
+	free_irq(mlb_usio_irq[index][RX], port);
+	free_irq(mlb_usio_irq[index][TX], port);
+}
+
+static void mlb_usio_set_termios(struct uart_port *port,
+			struct ktermios *termios, struct ktermios *old)
+{
+	unsigned int escr, smr = MLB_USIO_SMR_SOE;
+	unsigned long flags, baud, quot;
+
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:
+		escr = MLB_USIO_ESCR_L_5BIT;
+		break;
+	case CS6:
+		escr = MLB_USIO_ESCR_L_6BIT;
+		break;
+	case CS7:
+		escr = MLB_USIO_ESCR_L_7BIT;
+		break;
+	case CS8:
+	default:
+		escr = MLB_USIO_ESCR_L_8BIT;
+		break;
+	}
+
+	if (termios->c_cflag & CSTOPB)
+		smr |= MLB_USIO_SMR_SBL;
+
+	if (termios->c_cflag & PARENB) {
+		escr |= MLB_USIO_ESCR_PEN;
+		if (termios->c_cflag & PARODD)
+			escr |= MLB_USIO_ESCR_P;
+	}
+	/* Set hard flow control */
+	if (of_property_read_bool(port->dev->of_node, "auto-flow-control") ||
+			(termios->c_cflag & CRTSCTS))
+		escr |= MLB_USIO_ESCR_FLWEN;
+
+	baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk);
+	if (baud > 1)
+		quot = port->uartclk / baud - 1;
+	else
+		quot = 0;
+
+	spin_lock_irqsave(&port->lock, flags);
+	uart_update_timeout(port, termios->c_cflag, baud);
+	port->read_status_mask = MLB_USIO_SSR_ORE | MLB_USIO_SSR_RDRF |
+				 MLB_USIO_SSR_TDRE;
+	if (termios->c_iflag & INPCK)
+		port->read_status_mask |= MLB_USIO_SSR_FRE | MLB_USIO_SSR_PE;
+
+	port->ignore_status_mask = 0;
+	if (termios->c_iflag & IGNPAR)
+		port->ignore_status_mask |= MLB_USIO_SSR_FRE | MLB_USIO_SSR_PE;
+	if ((termios->c_iflag & IGNBRK) && (termios->c_iflag & IGNPAR))
+		port->ignore_status_mask |= MLB_USIO_SSR_ORE;
+	if ((termios->c_cflag & CREAD) == 0)
+		port->ignore_status_mask |= MLB_USIO_SSR_RDRF;
+
+	writeb(0, port->membase + MLB_USIO_REG_SCR);
+	writeb(MLB_USIO_SCR_UPCL, port->membase + MLB_USIO_REG_SCR);
+	writeb(MLB_USIO_SSR_REC, port->membase + MLB_USIO_REG_SSR);
+	writew(0, port->membase + MLB_USIO_REG_FCR);
+	writeb(smr, port->membase + MLB_USIO_REG_SMR);
+	writeb(escr, port->membase + MLB_USIO_REG_ESCR);
+	writew(quot, port->membase + MLB_USIO_REG_BGR);
+	writew(0, port->membase + MLB_USIO_REG_FCR);
+	writew(MLB_USIO_FCR_FCL1 | MLB_USIO_FCR_FCL2 | MLB_USIO_FCR_FE1 |
+	       MLB_USIO_FCR_FE2 | MLB_USIO_FCR_FRIIE,
+	       port->membase + MLB_USIO_REG_FCR);
+	writew(0, port->membase + MLB_USIO_REG_FBYTE);
+	writew(BIT(12), port->membase + MLB_USIO_REG_FBYTE);
+	writeb(MLB_USIO_SCR_RIE | MLB_USIO_SCR_RXE | MLB_USIO_SCR_TBIE |
+	       MLB_USIO_SCR_TXE, port->membase + MLB_USIO_REG_SCR);
+	spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static const char *mlb_usio_type(struct uart_port *port)
+{
+	return ((port->type == PORT_MLB_USIO) ? USIO_NAME : NULL);
+}
+
+static void mlb_usio_config_port(struct uart_port *port, int flags)
+{
+	if (flags & UART_CONFIG_TYPE)
+		port->type = PORT_MLB_USIO;
+}
+
+static const struct uart_ops mlb_usio_ops = {
+	.tx_empty	= mlb_usio_tx_empty,
+	.set_mctrl	= mlb_usio_set_mctrl,
+	.get_mctrl	= mlb_usio_get_mctrl,
+	.stop_tx	= mlb_usio_stop_tx,
+	.start_tx	= mlb_usio_start_tx,
+	.stop_rx	= mlb_usio_stop_rx,
+	.enable_ms	= mlb_usio_enable_ms,
+	.break_ctl	= mlb_usio_break_ctl,
+	.startup	= mlb_usio_startup,
+	.shutdown	= mlb_usio_shutdown,
+	.set_termios	= mlb_usio_set_termios,
+	.type		= mlb_usio_type,
+	.config_port	= mlb_usio_config_port,
+};
+
+#ifdef CONFIG_SERIAL_MILBEAUT_USIO_CONSOLE
+
+static void mlb_usio_console_putchar(struct uart_port *port, int c)
+{
+	while (!(readb(port->membase + MLB_USIO_REG_SSR) & MLB_USIO_SSR_TDRE))
+		cpu_relax();
+
+	writew(c, port->membase + MLB_USIO_REG_DR);
+}
+
+static void mlb_usio_console_write(struct console *co, const char *s,
+			       unsigned int count)
+{
+	struct uart_port *port = &mlb_usio_ports[co->index];
+
+	uart_console_write(port, s, count, mlb_usio_console_putchar);
+}
+
+static int __init mlb_usio_console_setup(struct console *co, char *options)
+{
+	struct uart_port *port;
+	int baud = 115200;
+	int parity = 'n';
+	int flow = 'n';
+	int bits = 8;
+
+	if (co->index >= CONFIG_SERIAL_MILBEAUT_USIO_PORTS)
+		return -ENODEV;
+
+	port = &mlb_usio_ports[co->index];
+	if (!port->membase)
+		return -ENODEV;
+
+
+	if (options)
+		uart_parse_options(options, &baud, &parity, &bits, &flow);
+
+	if (of_property_read_bool(port->dev->of_node, "auto-flow-control"))
+		flow = 'r';
+
+	return uart_set_options(port, co, baud, parity, bits, flow);
+}
+
+
+static struct uart_driver mlb_usio_uart_driver;
+static struct console mlb_usio_console = {
+	.name   = USIO_UART_DEV_NAME,
+	.write  = mlb_usio_console_write,
+	.device = uart_console_device,
+	.setup  = mlb_usio_console_setup,
+	.flags  = CON_PRINTBUFFER,
+	.index  = -1,
+	.data   = &mlb_usio_uart_driver,
+};
+
+static int __init mlb_usio_console_init(void)
+{
+	register_console(&mlb_usio_console);
+	return 0;
+}
+console_initcall(mlb_usio_console_init);
+
+
+static void mlb_usio_early_console_write(struct console *co, const char *s,
+					u_int count)
+{
+	struct earlycon_device *dev = co->data;
+
+	uart_console_write(&dev->port, s, count, mlb_usio_console_putchar);
+}
+
+static int __init mlb_usio_early_console_setup(struct earlycon_device *device,
+						const char *opt)
+{
+	if (!device->port.membase)
+		return -ENODEV;
+	device->con->write = mlb_usio_early_console_write;
+	return 0;
+}
+
+OF_EARLYCON_DECLARE(mlb_usio, "socionext,milbeaut-usio-uart",
+			mlb_usio_early_console_setup);
+
+#define USIO_CONSOLE	(&mlb_usio_console)
+#else
+#define USIO_CONSOLE	NULL
+#endif
+
+static struct  uart_driver mlb_usio_uart_driver = {
+	.owner		= THIS_MODULE,
+	.driver_name	= USIO_NAME,
+	.dev_name	= USIO_UART_DEV_NAME,
+	.cons           = USIO_CONSOLE,
+	.nr		= CONFIG_SERIAL_MILBEAUT_USIO_PORTS,
+};
+
+static int mlb_usio_probe(struct platform_device *pdev)
+{
+	struct clk *clk = devm_clk_get(&pdev->dev, 0);
+	struct uart_port *port;
+	struct resource *res;
+	int index = 0;
+	int ret;
+
+	if (IS_ERR(clk)) {
+		dev_err(&pdev->dev, "Missing clock\n");
+		return PTR_ERR(clk);
+	}
+	ret = clk_prepare_enable(clk);
+	if (ret) {
+		dev_err(&pdev->dev, "Clock enable failed: %d\n", ret);
+		return ret;
+	}
+	of_property_read_u32(pdev->dev.of_node, "index", &index);
+	port = &mlb_usio_ports[index];
+
+	port->private_data = (void *)clk;
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "Missing regs\n");
+		ret = -ENODEV;
+		goto failed;
+	}
+	port->mapbase = res->start;
+	port->membase = ioremap(res->start, (res->end - res->start + 1));
+	port->membase = devm_ioremap(&pdev->dev, res->start,
+				resource_size(res));
+
+	ret = platform_get_irq_byname(pdev, "rx");
+	mlb_usio_irq[index][RX] = ret;
+
+	ret = platform_get_irq_byname(pdev, "tx");
+	mlb_usio_irq[index][TX] = ret;
+
+	port->irq = mlb_usio_irq[index][RX];
+	port->uartclk = clk_get_rate(clk);
+	port->fifosize = 128;
+	port->iotype = UPIO_MEM32;
+	port->flags = UPF_BOOT_AUTOCONF | UPF_SPD_VHI;
+	port->line = index;
+	port->ops = &mlb_usio_ops;
+	port->dev = &pdev->dev;
+
+	ret = uart_add_one_port(&mlb_usio_uart_driver, port);
+	if (ret) {
+		dev_err(&pdev->dev, "Adding port failed: %d\n", ret);
+		goto failed1;
+	}
+	return 0;
+
+failed1:
+	iounmap(port->membase);
+
+failed:
+	clk_disable_unprepare(clk);
+	clk_put(clk);
+
+	return ret;
+}
+
+static int mlb_usio_remove(struct platform_device *pdev)
+{
+	struct uart_port *port = &mlb_usio_ports[pdev->id];
+	struct clk *clk = port->private_data;
+
+	uart_remove_one_port(&mlb_usio_uart_driver, port);
+	clk_disable_unprepare(clk);
+	clk_put(clk);
+
+	return 0;
+}
+
+static const struct of_device_id mlb_usio_dt_ids[] = {
+	{ .compatible = "socionext,milbeaut-usio-uart" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mlb_usio_dt_ids);
+
+static struct platform_driver mlb_usio_driver = {
+	.probe          = mlb_usio_probe,
+	.remove         = mlb_usio_remove,
+	.driver         = {
+		.name   = USIO_NAME,
+		.of_match_table = mlb_usio_dt_ids,
+	},
+};
+
+static int __init mlb_usio_init(void)
+{
+	int ret = uart_register_driver(&mlb_usio_uart_driver);
+
+	if (ret) {
+		pr_err("%s: uart registration failed: %d\n", __func__, ret);
+		return ret;
+	}
+	ret = platform_driver_register(&mlb_usio_driver);
+	if (ret) {
+		uart_unregister_driver(&mlb_usio_uart_driver);
+		pr_err("%s: drv registration failed: %d\n", __func__, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void __exit mlb_usio_exit(void)
+{
+	platform_driver_unregister(&mlb_usio_driver);
+	uart_unregister_driver(&mlb_usio_uart_driver);
+}
+
+module_init(mlb_usio_init);
+module_exit(mlb_usio_exit);
+
+MODULE_AUTHOR("SOCIONEXT");
+MODULE_DESCRIPTION("MILBEAUT_USIO/UART Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 6009ee2..a51c747 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -287,4 +287,7 @@
 /* RDA UART */
 #define PORT_RDA	118
 
+/* Socionext Milbeaut UART */
+#define PORT_MLB_USIO	119
+
 #endif /* _UAPILINUX_SERIAL_CORE_H */
-- 
1.9.1

^ permalink raw reply related

* [PATCH v5 2/2] tty: serial: add driver for the SiFive UART
From: Paul Walmsley @ 2019-04-13  2:01 UTC (permalink / raw)
  To: linux-kernel, linux-serial, linux-riscv, gregkh
  Cc: Paul Walmsley, Paul Walmsley, Jiri Slaby, Palmer Dabbelt,
	Wesley Terpstra, Julia Lawall, Emil Renner Berthing,
	Andreas Schwab
In-Reply-To: <20190413020111.23400-1-paul.walmsley@sifive.com>

Add a serial driver for the SiFive UART, found on SiFive FU540 devices
(among others).

The underlying serial IP block is relatively basic, and currently does
not support serial break detection.  Further information on the IP
block can be found in the documentation and Chisel sources:

    https://static.dev.sifive.com/FU540-C000-v1.0.pdf

    https://github.com/sifive/sifive-blocks/tree/master/src/main/scala/devices/uart

This driver was written in collaboration with Wesley Terpstra
<wesley@sifive.com>.

Tested on a SiFive HiFive Unleashed A00 board, using BBL and the open-
source FSBL (using a DT file based on what's targeted for mainline).

This revision incorporates changes based on comments by Julia Lawall
<julia.lawall@lip6.fr>, Emil Renner Berthing <kernel@esmil.dk>, and
Andreas Schwab <schwab@suse.de>.  Thanks also to Andreas for testing
the driver with his userspace and reporting a bug with the
set_termios implementation.

Signed-off-by: Paul Walmsley <paul.walmsley@sifive.com>
Signed-off-by: Paul Walmsley <paul@pwsan.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jslaby@suse.com>
Cc: Palmer Dabbelt <palmer@sifive.com>
Cc: Wesley Terpstra <wesley@sifive.com>
Cc: linux-serial@vger.kernel.org
Cc: linux-riscv@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: Julia Lawall <julia.lawall@lip6.fr>
Cc: Emil Renner Berthing <kernel@esmil.dk>
Cc: Andreas Schwab <schwab@suse.de>
---
 drivers/tty/serial/Kconfig       |   24 +
 drivers/tty/serial/Makefile      |    1 +
 drivers/tty/serial/sifive.c      | 1056 ++++++++++++++++++++++++++++++
 include/uapi/linux/serial_core.h |    3 +
 4 files changed, 1084 insertions(+)
 create mode 100644 drivers/tty/serial/sifive.c

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 72966bc0ac76..561e053b690a 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1095,6 +1095,30 @@ config SERIAL_OMAP_CONSOLE
 	  your boot loader about how to pass options to the kernel at
 	  boot time.)
 
+config SERIAL_SIFIVE
+	tristate "SiFive UART support"
+	depends on OF
+	select SERIAL_CORE
+	help
+	  Select this option if you are building a kernel for a device that
+	  contains a SiFive UART IP block.  This type of UART is present on
+	  SiFive FU540 SoCs, among others.
+
+config SERIAL_SIFIVE_CONSOLE
+	bool "Console on SiFive UART"
+	depends on SERIAL_SIFIVE=y
+	select SERIAL_CORE_CONSOLE
+	help
+	  Select this option if you would like to use a SiFive UART as the
+	  system console.
+
+	  Even if you say Y here, the currently visible virtual console
+	  (/dev/tty0) will still be used as the system console by default, but
+	  you can alter that using a kernel command line option such as
+	  "console=ttySIFx". (Try "man bootparam" or see the documentation of
+	  your boot loader about how to pass options to the kernel at
+	  boot time.)
+
 config SERIAL_LANTIQ
 	bool "Lantiq serial driver"
 	depends on LANTIQ
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 40b702aaa85e..2aff1d07d08b 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_SERIAL_PIC32)	+= pic32_uart.o
 obj-$(CONFIG_SERIAL_MPS2_UART)	+= mps2-uart.o
 obj-$(CONFIG_SERIAL_OWL)	+= owl-uart.o
 obj-$(CONFIG_SERIAL_RDA)	+= rda-uart.o
+obj-$(CONFIG_SERIAL_SIFIVE)	+= sifive.o
 
 # GPIOLIB helpers for modem control lines
 obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c
new file mode 100644
index 000000000000..be4687814353
--- /dev/null
+++ b/drivers/tty/serial/sifive.c
@@ -0,0 +1,1056 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * SiFive UART driver
+ * Copyright (C) 2018 Paul Walmsley <paul@pwsan.com>
+ * Copyright (C) 2018-2019 SiFive
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Based partially on:
+ * - drivers/tty/serial/pxa.c
+ * - drivers/tty/serial/amba-pl011.c
+ * - drivers/tty/serial/uartlite.c
+ * - drivers/tty/serial/omap-serial.c
+ * - drivers/pwm/pwm-sifive.c
+ *
+ * See the following sources for further documentation:
+ * - Chapter 19 "Universal Asynchronous Receiver/Transmitter (UART)" of
+ *   SiFive FE310-G000 v2p3
+ * - The tree/master/src/main/scala/devices/uart directory of
+ *   https://github.com/sifive/sifive-blocks/
+ *
+ * The SiFive UART design is not 8250-compatible.  The following common
+ * features are not supported:
+ * - Word lengths other than 8 bits
+ * - Break handling
+ * - Parity
+ * - Flow control
+ * - Modem signals (DSR, RI, etc.)
+ * On the other hand, the design is free from the baggage of the 8250
+ * programming model.
+ */
+
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/serial_core.h>
+#include <linux/serial_reg.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+
+/*
+ * Register offsets
+ */
+
+/* TXDATA */
+#define SIFIVE_SERIAL_TXDATA_OFFS		0x0
+#define SIFIVE_SERIAL_TXDATA_FULL_SHIFT		31
+#define SIFIVE_SERIAL_TXDATA_FULL_MASK		(1 << SIFIVE_SERIAL_TXDATA_FULL_SHIFT)
+#define SIFIVE_SERIAL_TXDATA_DATA_SHIFT		0
+#define SIFIVE_SERIAL_TXDATA_DATA_MASK		(0xff << SIFIVE_SERIAL_TXDATA_DATA_SHIFT)
+
+/* RXDATA */
+#define SIFIVE_SERIAL_RXDATA_OFFS		0x4
+#define SIFIVE_SERIAL_RXDATA_EMPTY_SHIFT	31
+#define SIFIVE_SERIAL_RXDATA_EMPTY_MASK		(1 << SIFIVE_SERIAL_RXDATA_EMPTY_SHIFT)
+#define SIFIVE_SERIAL_RXDATA_DATA_SHIFT		0
+#define SIFIVE_SERIAL_RXDATA_DATA_MASK		(0xff << SIFIVE_SERIAL_RXDATA_DATA_SHIFT)
+
+/* TXCTRL */
+#define SIFIVE_SERIAL_TXCTRL_OFFS		0x8
+#define SIFIVE_SERIAL_TXCTRL_TXCNT_SHIFT	16
+#define SIFIVE_SERIAL_TXCTRL_TXCNT_MASK		(0x7 << SIFIVE_SERIAL_TXCTRL_TXCNT_SHIFT)
+#define SIFIVE_SERIAL_TXCTRL_NSTOP_SHIFT	1
+#define SIFIVE_SERIAL_TXCTRL_NSTOP_MASK		(1 << SIFIVE_SERIAL_TXCTRL_NSTOP_SHIFT)
+#define SIFIVE_SERIAL_TXCTRL_TXEN_SHIFT		0
+#define SIFIVE_SERIAL_TXCTRL_TXEN_MASK		(1 << SIFIVE_SERIAL_TXCTRL_TXEN_SHIFT)
+
+/* RXCTRL */
+#define SIFIVE_SERIAL_RXCTRL_OFFS		0xC
+#define SIFIVE_SERIAL_RXCTRL_RXCNT_SHIFT	16
+#define SIFIVE_SERIAL_RXCTRL_RXCNT_MASK		(0x7 << SIFIVE_SERIAL_TXCTRL_TXCNT_SHIFT)
+#define SIFIVE_SERIAL_RXCTRL_RXEN_SHIFT		0
+#define SIFIVE_SERIAL_RXCTRL_RXEN_MASK		(1 << SIFIVE_SERIAL_RXCTRL_RXEN_SHIFT)
+
+/* IE */
+#define SIFIVE_SERIAL_IE_OFFS			0x10
+#define SIFIVE_SERIAL_IE_RXWM_SHIFT		1
+#define SIFIVE_SERIAL_IE_RXWM_MASK		(1 << SIFIVE_SERIAL_IE_RXWM_SHIFT)
+#define SIFIVE_SERIAL_IE_TXWM_SHIFT		0
+#define SIFIVE_SERIAL_IE_TXWM_MASK		(1 << SIFIVE_SERIAL_IE_TXWM_SHIFT)
+
+/* IP */
+#define SIFIVE_SERIAL_IP_OFFS			0x14
+#define SIFIVE_SERIAL_IP_RXWM_SHIFT		1
+#define SIFIVE_SERIAL_IP_RXWM_MASK		(1 << SIFIVE_SERIAL_IP_RXWM_SHIFT)
+#define SIFIVE_SERIAL_IP_TXWM_SHIFT		0
+#define SIFIVE_SERIAL_IP_TXWM_MASK		(1 << SIFIVE_SERIAL_IP_TXWM_SHIFT)
+
+/* DIV */
+#define SIFIVE_SERIAL_DIV_OFFS			0x18
+#define SIFIVE_SERIAL_DIV_DIV_SHIFT		0
+#define SIFIVE_SERIAL_DIV_DIV_MASK		(0xffff << SIFIVE_SERIAL_IP_DIV_SHIFT)
+
+/*
+ * Config macros
+ */
+
+/*
+ * SIFIVE_SERIAL_MAX_PORTS: maximum number of UARTs on a device that can
+ *                          host a serial console
+ */
+#define SIFIVE_SERIAL_MAX_PORTS			8
+
+/*
+ * SIFIVE_DEFAULT_BAUD_RATE: default baud rate that the driver should
+ *                           configure itself to use
+ */
+#define SIFIVE_DEFAULT_BAUD_RATE		115200
+
+/* SIFIVE_SERIAL_NAME: our driver's name that we pass to the operating system */
+#define SIFIVE_SERIAL_NAME			"sifive-serial"
+
+/* SIFIVE_TTY_PREFIX: tty name prefix for SiFive serial ports */
+#define SIFIVE_TTY_PREFIX			"ttySIF"
+
+/* SIFIVE_TX_FIFO_DEPTH: depth of the TX FIFO (in bytes) */
+#define SIFIVE_TX_FIFO_DEPTH			8
+
+/* SIFIVE_RX_FIFO_DEPTH: depth of the TX FIFO (in bytes) */
+#define SIFIVE_RX_FIFO_DEPTH			8
+
+#if (SIFIVE_TX_FIFO_DEPTH != SIFIVE_RX_FIFO_DEPTH)
+#error Driver does not support configurations with different TX, RX FIFO sizes
+#endif
+
+/*
+ *
+ */
+
+/**
+ * sifive_serial_port - driver-specific data extension to struct uart_port
+ * @port: struct uart_port embedded in this struct
+ * @dev: struct device *
+ * @ier: shadowed copy of the interrupt enable register
+ * @clkin_rate: input clock to the UART IP block.
+ * @baud_rate: UART serial line rate (e.g., 115200 baud)
+ * @clk_notifier: clock rate change notifier for upstream clock changes
+ *
+ * Configuration data specific to this SiFive UART.
+ */
+struct sifive_serial_port {
+	struct uart_port	port;
+	struct device		*dev;
+	unsigned char		ier;
+	unsigned long		clkin_rate;
+	unsigned long		baud_rate;
+	struct clk		*clk;
+	struct notifier_block	clk_notifier;
+};
+
+/*
+ * Structure container-of macros
+ */
+
+#define port_to_sifive_serial_port(p) (container_of((p), \
+						    struct sifive_serial_port, \
+						    port))
+
+#define notifier_to_sifive_serial_port(nb) (container_of((nb), \
+							 struct sifive_serial_port, \
+							 clk_notifier))
+
+/*
+ * Forward declarations
+ */
+static void sifive_serial_stop_tx(struct uart_port *port);
+
+/*
+ * Internal functions
+ */
+
+/**
+ * __ssp_early_writel() - write to a SiFive serial port register (early)
+ * @port: pointer to a struct uart_port record
+ * @offs: register address offset from the IP block base address
+ * @v: value to write to the register
+ *
+ * Given a pointer @port to a struct uart_port record, write the value
+ * @v to the IP block register address offset @offs.  This function is
+ * intended for early console use.
+ *
+ * Context: Intended to be used only by the earlyconsole code.
+ */
+static void __ssp_early_writel(u32 v, u16 offs, struct uart_port *port)
+{
+	writel_relaxed(v, port->membase + offs);
+}
+
+/**
+ * __ssp_early_readl() - read from a SiFive serial port register (early)
+ * @port: pointer to a struct uart_port record
+ * @offs: register address offset from the IP block base address
+ *
+ * Given a pointer @port to a struct uart_port record, read the
+ * contents of the IP block register located at offset @offs from the
+ * IP block base and return it.  This function is intended for early
+ * console use.
+ *
+ * Context: Intended to be called only by the earlyconsole code or by
+ *          __ssp_readl() or __ssp_writel() (in this driver)
+ *
+ * Returns: the register value read from the UART.
+ */
+static u32 __ssp_early_readl(struct uart_port *port, u16 offs)
+{
+	return readl_relaxed(port->membase + offs);
+}
+
+/**
+ * __ssp_writel() - write to a SiFive serial port register
+ * @v: value to write to the register
+ * @offs: register address offset from the IP block base address
+ * @ssp: pointer to a struct sifive_serial_port record
+ *
+ * Write the value @v to the IP block register located at offset @offs from the
+ * IP block base, given a pointer @ssp to a struct sifive_serial_port record.
+ *
+ * Context: Any context.
+ */
+static void __ssp_writel(u32 v, u16 offs, struct sifive_serial_port *ssp)
+{
+	__ssp_early_writel(v, offs, &ssp->port);
+}
+
+/**
+ * __ssp_readl() - read from a SiFive serial port register
+ * @ssp: pointer to a struct sifive_serial_port record
+ * @offs: register address offset from the IP block base address
+ *
+ * Read the contents of the IP block register located at offset @offs from the
+ * IP block base, given a pointer @ssp to a struct sifive_serial_port record.
+ *
+ * Context: Any context.
+ *
+ * Returns: the value of the UART register
+ */
+static u32 __ssp_readl(struct sifive_serial_port *ssp, u16 offs)
+{
+	return __ssp_early_readl(&ssp->port, offs);
+}
+
+/**
+ * sifive_serial_is_txfifo_full() - is the TXFIFO full?
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Read the transmit FIFO "full" bit, returning a non-zero value if the
+ * TX FIFO is full, or zero if space remains.  Intended to be used to prevent
+ * writes to the TX FIFO when it's full.
+ *
+ * Returns: SIFIVE_SERIAL_TXDATA_FULL_MASK (non-zero) if the transmit FIFO
+ * is full, or 0 if space remains.
+ */
+static int sifive_serial_is_txfifo_full(struct sifive_serial_port *ssp)
+{
+	return __ssp_readl(ssp, SIFIVE_SERIAL_TXDATA_OFFS) &
+		SIFIVE_SERIAL_TXDATA_FULL_MASK;
+}
+
+/**
+ * __ssp_transmit_char() - enqueue a byte to transmit onto the TX FIFO
+ * @ssp: pointer to a struct sifive_serial_port
+ * @ch: character to transmit
+ *
+ * Enqueue a byte @ch onto the transmit FIFO, given a pointer @ssp to the
+ * struct sifive_serial_port * to transmit on.  Caller should first check to
+ * ensure that the TXFIFO has space; see sifive_serial_is_txfifo_full().
+ *
+ * Context: Any context.
+ */
+static void __ssp_transmit_char(struct sifive_serial_port *ssp, int ch)
+{
+	__ssp_writel(ch, SIFIVE_SERIAL_TXDATA_OFFS, ssp);
+}
+
+/**
+ * __ssp_transmit_chars() - enqueue multiple bytes onto the TX FIFO
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Transfer up to a TX FIFO size's worth of characters from the Linux serial
+ * transmit buffer to the SiFive UART TX FIFO.
+ *
+ * Context: Any context.  Expects @ssp->port.lock to be held by caller.
+ */
+static void __ssp_transmit_chars(struct sifive_serial_port *ssp)
+{
+	struct circ_buf *xmit = &ssp->port.state->xmit;
+	int count;
+
+	if (ssp->port.x_char) {
+		__ssp_transmit_char(ssp, ssp->port.x_char);
+		ssp->port.icount.tx++;
+		ssp->port.x_char = 0;
+		return;
+	}
+	if (uart_circ_empty(xmit) || uart_tx_stopped(&ssp->port)) {
+		sifive_serial_stop_tx(&ssp->port);
+		return;
+	}
+	count = SIFIVE_TX_FIFO_DEPTH;
+	do {
+		__ssp_transmit_char(ssp, xmit->buf[xmit->tail]);
+		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+		ssp->port.icount.tx++;
+		if (uart_circ_empty(xmit))
+			break;
+	} while (--count > 0);
+
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(&ssp->port);
+
+	if (uart_circ_empty(xmit))
+		sifive_serial_stop_tx(&ssp->port);
+}
+
+/**
+ * __ssp_enable_txwm() - enable transmit watermark interrupts
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Enable interrupt generation when the transmit FIFO watermark is reached
+ * on the SiFive UART referred to by @ssp.
+ */
+static void __ssp_enable_txwm(struct sifive_serial_port *ssp)
+{
+	if (ssp->ier & SIFIVE_SERIAL_IE_TXWM_MASK)
+		return;
+
+	ssp->ier |= SIFIVE_SERIAL_IE_TXWM_MASK;
+	__ssp_writel(ssp->ier, SIFIVE_SERIAL_IE_OFFS, ssp);
+}
+
+/**
+ * __ssp_enable_rxwm() - enable receive watermark interrupts
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Enable interrupt generation when the receive FIFO watermark is reached
+ * on the SiFive UART referred to by @ssp.
+ */
+static void __ssp_enable_rxwm(struct sifive_serial_port *ssp)
+{
+	if (ssp->ier & SIFIVE_SERIAL_IE_RXWM_MASK)
+		return;
+
+	ssp->ier |= SIFIVE_SERIAL_IE_RXWM_MASK;
+	__ssp_writel(ssp->ier, SIFIVE_SERIAL_IE_OFFS, ssp);
+}
+
+/**
+ * __ssp_disable_txwm() - disable transmit watermark interrupts
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Disable interrupt generation when the transmit FIFO watermark is reached
+ * on the UART referred to by @ssp.
+ */
+static void __ssp_disable_txwm(struct sifive_serial_port *ssp)
+{
+	if (!(ssp->ier & SIFIVE_SERIAL_IE_TXWM_MASK))
+		return;
+
+	ssp->ier &= ~SIFIVE_SERIAL_IE_TXWM_MASK;
+	__ssp_writel(ssp->ier, SIFIVE_SERIAL_IE_OFFS, ssp);
+}
+
+/**
+ * __ssp_disable_rxwm() - disable receive watermark interrupts
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Disable interrupt generation when the receive FIFO watermark is reached
+ * on the UART referred to by @ssp.
+ */
+static void __ssp_disable_rxwm(struct sifive_serial_port *ssp)
+{
+	if (!(ssp->ier & SIFIVE_SERIAL_IE_RXWM_MASK))
+		return;
+
+	ssp->ier &= ~SIFIVE_SERIAL_IE_RXWM_MASK;
+	__ssp_writel(ssp->ier, SIFIVE_SERIAL_IE_OFFS, ssp);
+}
+
+/**
+ * __ssp_receive_char() - receive a byte from the UART
+ * @ssp: pointer to a struct sifive_serial_port
+ * @is_empty: char pointer to return whether the RX FIFO is empty
+ *
+ * Try to read a byte from the SiFive UART RX FIFO, referenced by
+ * @ssp, and to return it.  Also returns the RX FIFO empty bit in
+ * the char pointed to by @ch.  The caller must pass the byte back to the
+ * Linux serial layer if needed.
+ *
+ * Returns: the byte read from the UART RX FIFO.
+ */
+static char __ssp_receive_char(struct sifive_serial_port *ssp, char *is_empty)
+{
+	u32 v;
+	u8 ch;
+
+	v = __ssp_readl(ssp, SIFIVE_SERIAL_RXDATA_OFFS);
+
+	if (!is_empty)
+		WARN_ON(1);
+	else
+		*is_empty = (v & SIFIVE_SERIAL_RXDATA_EMPTY_MASK) >>
+			SIFIVE_SERIAL_RXDATA_EMPTY_SHIFT;
+
+	ch = (v & SIFIVE_SERIAL_RXDATA_DATA_MASK) >>
+		SIFIVE_SERIAL_RXDATA_DATA_SHIFT;
+
+	return ch;
+}
+
+/**
+ * __ssp_receive_chars() - receive multiple bytes from the UART
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Receive up to an RX FIFO's worth of bytes from the SiFive UART referred
+ * to by @ssp and pass them up to the Linux serial layer.
+ *
+ * Context: Expects ssp->port.lock to be held by caller.
+ */
+static void __ssp_receive_chars(struct sifive_serial_port *ssp)
+{
+	unsigned char ch;
+	char is_empty;
+	int c;
+
+	for (c = SIFIVE_RX_FIFO_DEPTH; c > 0; --c) {
+		ch = __ssp_receive_char(ssp, &is_empty);
+		if (is_empty)
+			break;
+
+		ssp->port.icount.rx++;
+		uart_insert_char(&ssp->port, 0, 0, ch, TTY_NORMAL);
+	}
+
+	spin_unlock(&ssp->port.lock);
+	tty_flip_buffer_push(&ssp->port.state->port);
+	spin_lock(&ssp->port.lock);
+}
+
+/**
+ * __ssp_update_div() - calculate the divisor setting by the line rate
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Calculate the appropriate value of the clock divisor for the UART
+ * and target line rate referred to by @ssp and write it into the
+ * hardware.
+ */
+static void __ssp_update_div(struct sifive_serial_port *ssp)
+{
+	u16 div;
+
+	div = DIV_ROUND_UP(ssp->clkin_rate, ssp->baud_rate) - 1;
+
+	__ssp_writel(div, SIFIVE_SERIAL_DIV_OFFS, ssp);
+}
+
+/**
+ * __ssp_update_baud_rate() - set the UART "baud rate"
+ * @ssp: pointer to a struct sifive_serial_port
+ * @rate: new target bit rate
+ *
+ * Calculate the UART divisor value for the target bit rate @rate for the
+ * SiFive UART described by @ssp and program it into the UART.  There may
+ * be some error between the target bit rate and the actual bit rate implemented
+ * by the UART due to clock ratio granularity.
+ */
+static void __ssp_update_baud_rate(struct sifive_serial_port *ssp,
+				   unsigned int rate)
+{
+	if (ssp->baud_rate == rate)
+		return;
+
+	ssp->baud_rate = rate;
+	__ssp_update_div(ssp);
+}
+
+/**
+ * __ssp_set_stop_bits() - set the number of stop bits
+ * @ssp: pointer to a struct sifive_serial_port
+ * @nstop: 1 or 2 (stop bits)
+ *
+ * Program the SiFive UART referred to by @ssp to use @nstop stop bits.
+ */
+static void __ssp_set_stop_bits(struct sifive_serial_port *ssp, char nstop)
+{
+	u32 v;
+
+	if (nstop < 1 || nstop > 2) {
+		WARN_ON(1);
+		return;
+	}
+
+	v = __ssp_readl(ssp, SIFIVE_SERIAL_TXCTRL_OFFS);
+	v &= ~SIFIVE_SERIAL_TXCTRL_NSTOP_MASK;
+	v |= (nstop - 1) << SIFIVE_SERIAL_TXCTRL_NSTOP_SHIFT;
+	__ssp_writel(v, SIFIVE_SERIAL_TXCTRL_OFFS, ssp);
+}
+
+/**
+ * __ssp_wait_for_xmitr() - wait for an empty slot on the TX FIFO
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Delay while the UART TX FIFO referred to by @ssp is marked as full.
+ *
+ * Context: Any context.
+ */
+static void __maybe_unused __ssp_wait_for_xmitr(struct sifive_serial_port *ssp)
+{
+	while (sifive_serial_is_txfifo_full(ssp))
+		udelay(1); /* XXX Could probably be more intelligent here */
+}
+
+/*
+ * Linux serial API functions
+ */
+
+static void sifive_serial_stop_tx(struct uart_port *port)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	__ssp_disable_txwm(ssp);
+}
+
+static void sifive_serial_stop_rx(struct uart_port *port)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	__ssp_disable_rxwm(ssp);
+}
+
+static void sifive_serial_start_tx(struct uart_port *port)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	__ssp_enable_txwm(ssp);
+}
+
+static irqreturn_t sifive_serial_irq(int irq, void *dev_id)
+{
+	struct sifive_serial_port *ssp = dev_id;
+	u32 ip;
+
+	spin_lock(&ssp->port.lock);
+
+	ip = __ssp_readl(ssp, SIFIVE_SERIAL_IP_OFFS);
+	if (!ip) {
+		spin_unlock(&ssp->port.lock);
+		return IRQ_NONE;
+	}
+
+	if (ip & SIFIVE_SERIAL_IP_RXWM_MASK)
+		__ssp_receive_chars(ssp);
+	if (ip & SIFIVE_SERIAL_IP_TXWM_MASK)
+		__ssp_transmit_chars(ssp);
+
+	spin_unlock(&ssp->port.lock);
+
+	return IRQ_HANDLED;
+}
+
+static unsigned int sifive_serial_tx_empty(struct uart_port *port)
+{
+	return TIOCSER_TEMT;
+}
+
+static unsigned int sifive_serial_get_mctrl(struct uart_port *port)
+{
+	return TIOCM_CAR | TIOCM_CTS | TIOCM_DSR;
+}
+
+static void sifive_serial_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+	/* IP block does not support these signals */
+}
+
+static void sifive_serial_break_ctl(struct uart_port *port, int break_state)
+{
+	/* IP block does not support sending a break */
+}
+
+static int sifive_serial_startup(struct uart_port *port)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	__ssp_enable_rxwm(ssp);
+
+	return 0;
+}
+
+static void sifive_serial_shutdown(struct uart_port *port)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	__ssp_disable_rxwm(ssp);
+	__ssp_disable_txwm(ssp);
+}
+
+/**
+ * sifive_serial_clk_notifier() - clock post-rate-change notifier
+ * @nb: pointer to the struct notifier_block, from the notifier code
+ * @event: event mask from the notifier code
+ * @data: pointer to the struct clk_notifier_data from the notifier code
+ *
+ * On the V0 SoC, the UART IP block is derived from the CPU clock source
+ * after a synchronous divide-by-two divider, so any CPU clock rate change
+ * requires the UART baud rate to be updated.  This presumably could corrupt any
+ * serial word currently being transmitted or received.  It would probably
+ * be better to stop receives and transmits, then complete the baud rate
+ * change, then re-enable them.
+ */
+static int sifive_serial_clk_notifier(struct notifier_block *nb,
+				      unsigned long event, void *data)
+{
+	struct clk_notifier_data *cnd = data;
+	struct sifive_serial_port *ssp = notifier_to_sifive_serial_port(nb);
+
+	if (event == POST_RATE_CHANGE && ssp->clkin_rate != cnd->new_rate) {
+		ssp->clkin_rate = cnd->new_rate;
+		__ssp_update_div(ssp);
+	}
+
+	return NOTIFY_OK;
+}
+
+static void sifive_serial_set_termios(struct uart_port *port,
+				      struct ktermios *termios,
+				      struct ktermios *old)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+	unsigned long flags;
+	u32 v, old_v;
+	int rate;
+	char nstop;
+
+	if ((termios->c_cflag & CSIZE) != CS8)
+		dev_err_once(ssp->port.dev, "only 8-bit words supported\n");
+	if (termios->c_iflag & (INPCK | PARMRK))
+		dev_err_once(ssp->port.dev, "parity checking not supported\n");
+	if (termios->c_iflag & BRKINT)
+		dev_err_once(ssp->port.dev, "BREAK detection not supported\n");
+
+	/* Set number of stop bits */
+	nstop = (termios->c_cflag & CSTOPB) ? 2 : 1;
+	__ssp_set_stop_bits(ssp, nstop);
+
+	/* Set line rate */
+	rate = uart_get_baud_rate(port, termios, old, 0, ssp->clkin_rate / 16);
+	__ssp_update_baud_rate(ssp, rate);
+
+	spin_lock_irqsave(&ssp->port.lock, flags);
+
+	/* Update the per-port timeout */
+	uart_update_timeout(port, termios->c_cflag, rate);
+
+	ssp->port.read_status_mask = 0;
+
+	/* Ignore all characters if CREAD is not set */
+	v = __ssp_readl(ssp, SIFIVE_SERIAL_RXCTRL_OFFS);
+	old_v = v;
+	if ((termios->c_cflag & CREAD) == 0)
+		v &= SIFIVE_SERIAL_RXCTRL_RXEN_MASK;
+	else
+		v |= SIFIVE_SERIAL_RXCTRL_RXEN_MASK;
+	if (v != old_v)
+		__ssp_writel(v, SIFIVE_SERIAL_RXCTRL_OFFS, ssp);
+
+	spin_unlock_irqrestore(&ssp->port.lock, flags);
+}
+
+static void sifive_serial_release_port(struct uart_port *port)
+{
+}
+
+static int sifive_serial_request_port(struct uart_port *port)
+{
+	return 0;
+}
+
+static void sifive_serial_config_port(struct uart_port *port, int flags)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	ssp->port.type = PORT_SIFIVE_V0;
+}
+
+static int sifive_serial_verify_port(struct uart_port *port,
+				     struct serial_struct *ser)
+{
+	return -EINVAL;
+}
+
+static const char *sifive_serial_type(struct uart_port *port)
+{
+	return port->type == PORT_SIFIVE_V0 ? "SiFive UART v0" : NULL;
+}
+
+/*
+ * Early console support
+ */
+
+#ifdef CONFIG_SERIAL_EARLYCON
+static void early_sifive_serial_putc(struct uart_port *port, int c)
+{
+	while (__ssp_early_readl(port, SIFIVE_SERIAL_TXDATA_OFFS) &
+	       SIFIVE_SERIAL_TXDATA_FULL_MASK)
+		cpu_relax();
+
+	__ssp_early_writel(c, SIFIVE_SERIAL_TXDATA_OFFS, port);
+}
+
+static void early_sifive_serial_write(struct console *con, const char *s,
+				      unsigned int n)
+{
+	struct earlycon_device *dev = con->data;
+	struct uart_port *port = &dev->port;
+
+	uart_console_write(port, s, n, early_sifive_serial_putc);
+}
+
+static int __init early_sifive_serial_setup(struct earlycon_device *dev,
+					    const char *options)
+{
+	struct uart_port *port = &dev->port;
+
+	if (!port->membase)
+		return -ENODEV;
+
+	dev->con->write = early_sifive_serial_write;
+
+	return 0;
+}
+
+OF_EARLYCON_DECLARE(sifive, "sifive,uart0", early_sifive_serial_setup);
+OF_EARLYCON_DECLARE(sifive, "sifive,fu540-c000-uart0",
+		    early_sifive_serial_setup);
+#endif /* CONFIG_SERIAL_EARLYCON */
+
+/*
+ * Linux console interface
+ */
+
+#ifdef CONFIG_SERIAL_SIFIVE_CONSOLE
+
+static struct sifive_serial_port *sifive_serial_console_ports[SIFIVE_SERIAL_MAX_PORTS];
+
+static void sifive_serial_console_putchar(struct uart_port *port, int ch)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	__ssp_wait_for_xmitr(ssp);
+	__ssp_transmit_char(ssp, ch);
+}
+
+static void sifive_serial_console_write(struct console *co, const char *s,
+					unsigned int count)
+{
+	struct sifive_serial_port *ssp = sifive_serial_console_ports[co->index];
+	unsigned long flags;
+	unsigned int ier;
+	int locked = 1;
+
+	if (!ssp)
+		return;
+
+	local_irq_save(flags);
+	if (ssp->port.sysrq)
+		locked = 0;
+	else if (oops_in_progress)
+		locked = spin_trylock(&ssp->port.lock);
+	else
+		spin_lock(&ssp->port.lock);
+
+	ier = __ssp_readl(ssp, SIFIVE_SERIAL_IE_OFFS);
+	__ssp_writel(0, SIFIVE_SERIAL_IE_OFFS, ssp);
+
+	uart_console_write(&ssp->port, s, count, sifive_serial_console_putchar);
+
+	__ssp_writel(ier, SIFIVE_SERIAL_IE_OFFS, ssp);
+
+	if (locked)
+		spin_unlock(&ssp->port.lock);
+	local_irq_restore(flags);
+}
+
+static int __init sifive_serial_console_setup(struct console *co, char *options)
+{
+	struct sifive_serial_port *ssp;
+	int baud = SIFIVE_DEFAULT_BAUD_RATE;
+	int bits = 8;
+	int parity = 'n';
+	int flow = 'n';
+
+	if (co->index < 0 || co->index >= SIFIVE_SERIAL_MAX_PORTS)
+		return -ENODEV;
+
+	ssp = sifive_serial_console_ports[co->index];
+	if (!ssp)
+		return -ENODEV;
+
+	if (options)
+		uart_parse_options(options, &baud, &parity, &bits, &flow);
+
+	return uart_set_options(&ssp->port, co, baud, parity, bits, flow);
+}
+
+static struct uart_driver sifive_serial_uart_driver;
+
+static struct console sifive_serial_console = {
+	.name		= SIFIVE_TTY_PREFIX,
+	.write		= sifive_serial_console_write,
+	.device		= uart_console_device,
+	.setup		= sifive_serial_console_setup,
+	.flags		= CON_PRINTBUFFER,
+	.index		= -1,
+	.data		= &sifive_serial_uart_driver,
+};
+
+static int __init sifive_console_init(void)
+{
+	register_console(&sifive_serial_console);
+	return 0;
+}
+
+console_initcall(sifive_console_init);
+
+static void __ssp_add_console_port(struct sifive_serial_port *ssp)
+{
+	sifive_serial_console_ports[ssp->port.line] = ssp;
+}
+
+static void __ssp_remove_console_port(struct sifive_serial_port *ssp)
+{
+	sifive_serial_console_ports[ssp->port.line] = 0;
+}
+
+#define SIFIVE_SERIAL_CONSOLE	(&sifive_serial_console)
+
+#else
+
+#define SIFIVE_SERIAL_CONSOLE	NULL
+
+static void __ssp_add_console_port(struct sifive_serial_port *ssp)
+{}
+static void __ssp_remove_console_port(struct sifive_serial_port *ssp)
+{}
+
+#endif
+
+static const struct uart_ops sifive_serial_uops = {
+	.tx_empty	= sifive_serial_tx_empty,
+	.set_mctrl	= sifive_serial_set_mctrl,
+	.get_mctrl	= sifive_serial_get_mctrl,
+	.stop_tx	= sifive_serial_stop_tx,
+	.start_tx	= sifive_serial_start_tx,
+	.stop_rx	= sifive_serial_stop_rx,
+	.break_ctl	= sifive_serial_break_ctl,
+	.startup	= sifive_serial_startup,
+	.shutdown	= sifive_serial_shutdown,
+	.set_termios	= sifive_serial_set_termios,
+	.type		= sifive_serial_type,
+	.release_port	= sifive_serial_release_port,
+	.request_port	= sifive_serial_request_port,
+	.config_port	= sifive_serial_config_port,
+	.verify_port	= sifive_serial_verify_port,
+};
+
+static struct uart_driver sifive_serial_uart_driver = {
+	.owner		= THIS_MODULE,
+	.driver_name	= SIFIVE_SERIAL_NAME,
+	.dev_name	= SIFIVE_TTY_PREFIX,
+	.nr		= SIFIVE_SERIAL_MAX_PORTS,
+	.cons		= SIFIVE_SERIAL_CONSOLE,
+};
+
+static int sifive_serial_probe(struct platform_device *pdev)
+{
+	struct sifive_serial_port *ssp;
+	struct resource *mem;
+	struct clk *clk;
+	void __iomem *base;
+	int irq, id, r;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "could not acquire interrupt\n");
+		return -EPROBE_DEFER;
+	}
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	base = devm_ioremap_resource(&pdev->dev, mem);
+	if (IS_ERR(base)) {
+		dev_err(&pdev->dev, "could not acquire device memory\n");
+		return PTR_ERR(base);
+	}
+
+	clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(clk)) {
+		dev_err(&pdev->dev, "unable to find controller clock\n");
+		return PTR_ERR(clk);
+	}
+
+	id = of_alias_get_id(pdev->dev.of_node, "serial");
+	if (id < 0) {
+		dev_err(&pdev->dev, "missing aliases entry\n");
+		return id;
+	}
+
+#ifdef CONFIG_SERIAL_SIFIVE_CONSOLE
+	if (id > SIFIVE_SERIAL_MAX_PORTS) {
+		dev_err(&pdev->dev, "too many UARTs (%d)\n", id);
+		return -EINVAL;
+	}
+#endif
+
+	ssp = devm_kzalloc(&pdev->dev, sizeof(*ssp), GFP_KERNEL);
+	if (!ssp)
+		return -ENOMEM;
+
+	ssp->port.dev = &pdev->dev;
+	ssp->port.type = PORT_SIFIVE_V0;
+	ssp->port.iotype = UPIO_MEM;
+	ssp->port.irq = irq;
+	ssp->port.fifosize = SIFIVE_TX_FIFO_DEPTH;
+	ssp->port.ops = &sifive_serial_uops;
+	ssp->port.line = id;
+	ssp->port.mapbase = mem->start;
+	ssp->port.membase = base;
+	ssp->dev = &pdev->dev;
+	ssp->clk = clk;
+	ssp->clk_notifier.notifier_call = sifive_serial_clk_notifier;
+
+	r = clk_notifier_register(ssp->clk, &ssp->clk_notifier);
+	if (r) {
+		dev_err(&pdev->dev, "could not register clock notifier: %d\n",
+			r);
+		goto probe_out1;
+	}
+
+	/* Set up clock divider */
+	ssp->clkin_rate = clk_get_rate(ssp->clk);
+	ssp->baud_rate = SIFIVE_DEFAULT_BAUD_RATE;
+	__ssp_update_div(ssp);
+
+	platform_set_drvdata(pdev, ssp);
+
+	/* Enable transmits and set the watermark level to 1 */
+	__ssp_writel((1 << SIFIVE_SERIAL_TXCTRL_TXCNT_SHIFT) |
+		     SIFIVE_SERIAL_TXCTRL_TXEN_MASK,
+		     SIFIVE_SERIAL_TXCTRL_OFFS, ssp);
+
+	/* Enable receives and set the watermark level to 0 */
+	__ssp_writel((0 << SIFIVE_SERIAL_RXCTRL_RXCNT_SHIFT) |
+		     SIFIVE_SERIAL_RXCTRL_RXEN_MASK,
+		     SIFIVE_SERIAL_RXCTRL_OFFS, ssp);
+
+	r = request_irq(ssp->port.irq, sifive_serial_irq, ssp->port.irqflags,
+			dev_name(&pdev->dev), ssp);
+	if (r) {
+		dev_err(&pdev->dev, "could not attach interrupt: %d\n", r);
+		goto probe_out2;
+	}
+
+	__ssp_add_console_port(ssp);
+
+	r = uart_add_one_port(&sifive_serial_uart_driver, &ssp->port);
+	if (r != 0) {
+		dev_err(&pdev->dev, "could not add uart: %d\n", r);
+		goto probe_out3;
+	}
+
+	return 0;
+
+probe_out3:
+	__ssp_remove_console_port(ssp);
+	free_irq(ssp->port.irq, ssp);
+probe_out2:
+	clk_notifier_unregister(ssp->clk, &ssp->clk_notifier);
+probe_out1:
+	return r;
+}
+
+static int sifive_serial_remove(struct platform_device *dev)
+{
+	struct sifive_serial_port *ssp = platform_get_drvdata(dev);
+
+	__ssp_remove_console_port(ssp);
+	uart_remove_one_port(&sifive_serial_uart_driver, &ssp->port);
+	free_irq(ssp->port.irq, ssp);
+	clk_notifier_unregister(ssp->clk, &ssp->clk_notifier);
+
+	return 0;
+}
+
+static const struct of_device_id sifive_serial_of_match[] = {
+	{ .compatible = "sifive,fu540-c000-uart0" },
+	{ .compatible = "sifive,uart0" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, sifive_serial_of_match);
+
+static struct platform_driver sifive_serial_platform_driver = {
+	.probe		= sifive_serial_probe,
+	.remove		= sifive_serial_remove,
+	.driver		= {
+		.name	= SIFIVE_SERIAL_NAME,
+		.of_match_table = of_match_ptr(sifive_serial_of_match),
+	},
+};
+
+static int __init sifive_serial_init(void)
+{
+	int r;
+
+	r = uart_register_driver(&sifive_serial_uart_driver);
+	if (r)
+		goto init_out1;
+
+	r = platform_driver_register(&sifive_serial_platform_driver);
+	if (r)
+		goto init_out2;
+
+	return 0;
+
+init_out2:
+	uart_unregister_driver(&sifive_serial_uart_driver);
+init_out1:
+	return r;
+}
+
+static void __exit sifive_serial_exit(void)
+{
+	platform_driver_unregister(&sifive_serial_platform_driver);
+	uart_unregister_driver(&sifive_serial_uart_driver);
+}
+
+module_init(sifive_serial_init);
+module_exit(sifive_serial_exit);
+
+MODULE_DESCRIPTION("SiFive UART serial driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Paul Walmsley <paul@pwsan.com>");
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 6009ee2c2e99..2191fa691770 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -287,4 +287,7 @@
 /* RDA UART */
 #define PORT_RDA	118
 
+/* SiFive UART */
+#define PORT_SIFIVE_V0	119
+
 #endif /* _UAPILINUX_SERIAL_CORE_H */
-- 
2.20.1

^ permalink raw reply related

* [PATCH v5 1/2] dt-bindings: serial: add documentation for the SiFive UART driver
From: Paul Walmsley @ 2019-04-13  2:01 UTC (permalink / raw)
  To: linux-kernel, linux-serial, linux-riscv, gregkh
  Cc: Paul Walmsley, Paul Walmsley, devicetree, Rob Herring,
	Mark Rutland, Palmer Dabbelt
In-Reply-To: <20190413020111.23400-1-paul.walmsley@sifive.com>

Add DT binding documentation for the Linux driver for the SiFive
asynchronous serial IP block.

This revision incorporates changes based on feedback from Rob
Herring <robh@kernel.org>.

Signed-off-by: Paul Walmsley <paul.walmsley@sifive.com>
Signed-off-by: Paul Walmsley <paul@pwsan.com>
Cc: linux-serial@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-riscv@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Palmer Dabbelt <palmer@sifive.com>
---
 .../bindings/serial/sifive-serial.txt         | 33 +++++++++++++++++++
 1 file changed, 33 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serial/sifive-serial.txt

diff --git a/Documentation/devicetree/bindings/serial/sifive-serial.txt b/Documentation/devicetree/bindings/serial/sifive-serial.txt
new file mode 100644
index 000000000000..c86b1e524159
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/sifive-serial.txt
@@ -0,0 +1,33 @@
+SiFive asynchronous serial interface (UART)
+
+Required properties:
+
+- compatible: should be something similar to
+	      "sifive,<chip>-uart" for the UART as integrated
+	      on a particular chip, and "sifive,uart<version>" for the
+	      general UART IP block programming model.	Supported
+	      compatible strings as of the date of this writing are:
+	      "sifive,fu540-c000-uart" for the SiFive UART v0 as
+	      integrated onto the SiFive FU540 chip, or "sifive,uart0"
+	      for the SiFive UART v0 IP block with no chip integration
+	      tweaks (if any)
+- reg: address and length of the register space
+- interrupts: Should contain the UART interrupt identifier
+- clocks: Should contain a clock identifier for the UART's parent clock
+
+
+UART HDL that corresponds to the IP block version numbers can be found
+here:
+
+https://github.com/sifive/sifive-blocks/tree/master/src/main/scala/devices/uart
+
+
+Example:
+
+uart0: serial@10010000 {
+	compatible = "sifive,fu540-c000-uart", "sifive,uart0";
+	interrupt-parent = <&plic0>;
+	interrupts = <80>;
+	reg = <0x0 0x10010000 0x0 0x1000>;
+	clocks = <&prci PRCI_CLK_TLCLK>;
+};
-- 
2.20.1

^ permalink raw reply related

* [PATCH v5 0/2]  tty: serial: add DT bindings and serial driver for the SiFive FU540 UART
From: Paul Walmsley @ 2019-04-13  2:01 UTC (permalink / raw)
  To: linux-kernel, linux-serial, linux-riscv, gregkh; +Cc: Paul Walmsley

This series adds a serial driver, with console support, for the
UART IP block present on the SiFive FU540 SoC.  The programming
model is straightforward, but unique.

Boot-tested on a SiFive FU540 HiFive-U board, using BBL and the
open-source FSBL (with appropriate patches to the DT data).

This fifth version fixes a bug in the set_termios handler,
found by Andreas Schwab <schwab@suse.de>.

The patches in this series can also be found, with the PRCI patches,
DT patches, and DT prerequisite patch, at:

https://github.com/sifive/riscv-linux/tree/dev/paulw/serial-v5.1-rc4


- Paul

Paul Walmsley (2):
  dt-bindings: serial: add documentation for the SiFive UART driver
  tty: serial: add driver for the SiFive UART

 .../bindings/serial/sifive-serial.txt         |   33 +
 drivers/tty/serial/Kconfig                    |   24 +
 drivers/tty/serial/Makefile                   |    1 +
 drivers/tty/serial/sifive.c                   | 1056 +++++++++++++++++
 include/uapi/linux/serial_core.h              |    3 +
 5 files changed, 1117 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serial/sifive-serial.txt
 create mode 100644 drivers/tty/serial/sifive.c

-- 
2.20.1

^ permalink raw reply

* Re: [PATCH 05/24] soc: mediatek: pwrap: add support for MT8516 pwrap
From: Matthias Brugger @ 2019-04-12 17:44 UTC (permalink / raw)
  To: Fabien Parent, robh+dt, mark.rutland
  Cc: sean.wang, ryder.lee, hsin-hsiung.wang, wenzhen.yu, chaotian.jing,
	yong.mao, jjian.zhou, devicetree, linux-kernel, linux-i2c,
	linux-arm-kernel, linux-mediatek, linux-mmc, linux-gpio,
	linux-serial, linux-spi, linux-watchdog, linux-clk,
	stephane.leprovost
In-Reply-To: <20190323211612.860-6-fparent@baylibre.com>



On 23/03/2019 22:15, Fabien Parent wrote:
> Add the code to support the pwrap IP on the MediaTek MT8516 SoC.
> 
> Signed-off-by: Fabien Parent <fparent@baylibre.com>
> ---
>  drivers/soc/mediatek/mtk-pmic-wrap.c | 106 +++++++++++++++++++++++++++
>  1 file changed, 106 insertions(+)

applied to v5.1-next/soc

Thanks!

> 
> diff --git a/drivers/soc/mediatek/mtk-pmic-wrap.c b/drivers/soc/mediatek/mtk-pmic-wrap.c
> index b3ba2301f569..73f0be0567bd 100644
> --- a/drivers/soc/mediatek/mtk-pmic-wrap.c
> +++ b/drivers/soc/mediatek/mtk-pmic-wrap.c
> @@ -381,6 +381,10 @@ enum pwrap_regs {
>  	PWRAP_EXT_GPS_AUXADC_RDATA_ADDR,
>  	PWRAP_GPSINF_0_STA,
>  	PWRAP_GPSINF_1_STA,
> +
> +	/* MT8516 only regs */
> +	PWRAP_OP_TYPE,
> +	PWRAP_MSB_FIRST,
>  };
>  
>  static int mt2701_regs[] = {
> @@ -852,6 +856,91 @@ static int mt8183_regs[] = {
>  	[PWRAP_WACS2_VLDCLR] =			0xC28,
>  };
>  
> +static int mt8516_regs[] = {
> +	[PWRAP_MUX_SEL] =		0x0,
> +	[PWRAP_WRAP_EN] =		0x4,
> +	[PWRAP_DIO_EN] =		0x8,
> +	[PWRAP_SIDLY] =			0xc,
> +	[PWRAP_RDDMY] =			0x10,
> +	[PWRAP_SI_CK_CON] =		0x14,
> +	[PWRAP_CSHEXT_WRITE] =		0x18,
> +	[PWRAP_CSHEXT_READ] =		0x1c,
> +	[PWRAP_CSLEXT_START] =		0x20,
> +	[PWRAP_CSLEXT_END] =		0x24,
> +	[PWRAP_STAUPD_PRD] =		0x28,
> +	[PWRAP_STAUPD_GRPEN] =		0x2c,
> +	[PWRAP_STAUPD_MAN_TRIG] =	0x40,
> +	[PWRAP_STAUPD_STA] =		0x44,
> +	[PWRAP_WRAP_STA] =		0x48,
> +	[PWRAP_HARB_INIT] =		0x4c,
> +	[PWRAP_HARB_HPRIO] =		0x50,
> +	[PWRAP_HIPRIO_ARB_EN] =		0x54,
> +	[PWRAP_HARB_STA0] =		0x58,
> +	[PWRAP_HARB_STA1] =		0x5c,
> +	[PWRAP_MAN_EN] =		0x60,
> +	[PWRAP_MAN_CMD] =		0x64,
> +	[PWRAP_MAN_RDATA] =		0x68,
> +	[PWRAP_MAN_VLDCLR] =		0x6c,
> +	[PWRAP_WACS0_EN] =		0x70,
> +	[PWRAP_INIT_DONE0] =		0x74,
> +	[PWRAP_WACS0_CMD] =		0x78,
> +	[PWRAP_WACS0_RDATA] =		0x7c,
> +	[PWRAP_WACS0_VLDCLR] =		0x80,
> +	[PWRAP_WACS1_EN] =		0x84,
> +	[PWRAP_INIT_DONE1] =		0x88,
> +	[PWRAP_WACS1_CMD] =		0x8c,
> +	[PWRAP_WACS1_RDATA] =		0x90,
> +	[PWRAP_WACS1_VLDCLR] =		0x94,
> +	[PWRAP_WACS2_EN] =		0x98,
> +	[PWRAP_INIT_DONE2] =		0x9c,
> +	[PWRAP_WACS2_CMD] =		0xa0,
> +	[PWRAP_WACS2_RDATA] =		0xa4,
> +	[PWRAP_WACS2_VLDCLR] =		0xa8,
> +	[PWRAP_INT_EN] =		0xac,
> +	[PWRAP_INT_FLG_RAW] =		0xb0,
> +	[PWRAP_INT_FLG] =		0xb4,
> +	[PWRAP_INT_CLR] =		0xb8,
> +	[PWRAP_SIG_ADR] =		0xbc,
> +	[PWRAP_SIG_MODE] =		0xc0,
> +	[PWRAP_SIG_VALUE] =		0xc4,
> +	[PWRAP_SIG_ERRVAL] =		0xc8,
> +	[PWRAP_CRC_EN] =		0xcc,
> +	[PWRAP_TIMER_EN] =		0xd0,
> +	[PWRAP_TIMER_STA] =		0xd4,
> +	[PWRAP_WDT_UNIT] =		0xd8,
> +	[PWRAP_WDT_SRC_EN] =		0xdc,
> +	[PWRAP_WDT_FLG] =		0xe0,
> +	[PWRAP_DEBUG_INT_SEL] =		0xe4,
> +	[PWRAP_DVFS_ADR0] =		0xe8,
> +	[PWRAP_DVFS_WDATA0] =		0xec,
> +	[PWRAP_DVFS_ADR1] =		0xf0,
> +	[PWRAP_DVFS_WDATA1] =		0xf4,
> +	[PWRAP_DVFS_ADR2] =		0xf8,
> +	[PWRAP_DVFS_WDATA2] =		0xfc,
> +	[PWRAP_DVFS_ADR3] =		0x100,
> +	[PWRAP_DVFS_WDATA3] =		0x104,
> +	[PWRAP_DVFS_ADR4] =		0x108,
> +	[PWRAP_DVFS_WDATA4] =		0x10c,
> +	[PWRAP_DVFS_ADR5] =		0x110,
> +	[PWRAP_DVFS_WDATA5] =		0x114,
> +	[PWRAP_DVFS_ADR6] =		0x118,
> +	[PWRAP_DVFS_WDATA6] =		0x11c,
> +	[PWRAP_DVFS_ADR7] =		0x120,
> +	[PWRAP_DVFS_WDATA7] =		0x124,
> +	[PWRAP_SPMINF_STA] =		0x128,
> +	[PWRAP_CIPHER_KEY_SEL] =	0x12c,
> +	[PWRAP_CIPHER_IV_SEL] =		0x130,
> +	[PWRAP_CIPHER_EN] =		0x134,
> +	[PWRAP_CIPHER_RDY] =		0x138,
> +	[PWRAP_CIPHER_MODE] =		0x13c,
> +	[PWRAP_CIPHER_SWRST] =		0x140,
> +	[PWRAP_DCM_EN] =		0x144,
> +	[PWRAP_DCM_DBC_PRD] =		0x148,
> +	[PWRAP_SW_RST] =		0x168,
> +	[PWRAP_OP_TYPE] =		0x16c,
> +	[PWRAP_MSB_FIRST] =		0x170,
> +};
> +
>  enum pmic_type {
>  	PMIC_MT6323,
>  	PMIC_MT6351,
> @@ -869,6 +958,7 @@ enum pwrap_type {
>  	PWRAP_MT8135,
>  	PWRAP_MT8173,
>  	PWRAP_MT8183,
> +	PWRAP_MT8516,
>  };
>  
>  struct pmic_wrapper;
> @@ -1297,6 +1387,7 @@ static int pwrap_init_cipher(struct pmic_wrapper *wrp)
>  	case PWRAP_MT6765:
>  	case PWRAP_MT6797:
>  	case PWRAP_MT8173:
> +	case PWRAP_MT8516:
>  		pwrap_writel(wrp, 1, PWRAP_CIPHER_EN);
>  		break;
>  	case PWRAP_MT7622:
> @@ -1765,6 +1856,18 @@ static const struct pmic_wrapper_type pwrap_mt8183 = {
>  	.init_soc_specific = pwrap_mt8183_init_soc_specific,
>  };
>  
> +static struct pmic_wrapper_type pwrap_mt8516 = {
> +	.regs = mt8516_regs,
> +	.type = PWRAP_MT8516,
> +	.arb_en_all = 0xff,
> +	.int_en_all = ~(u32)(BIT(31) | BIT(2)),
> +	.spi_w = PWRAP_MAN_CMD_SPI_WRITE,
> +	.wdt_src = PWRAP_WDT_SRC_MASK_ALL,
> +	.caps = PWRAP_CAP_DCM,
> +	.init_reg_clock = pwrap_mt2701_init_reg_clock,
> +	.init_soc_specific = NULL,
> +};
> +
>  static const struct of_device_id of_pwrap_match_tbl[] = {
>  	{
>  		.compatible = "mediatek,mt2701-pwrap",
> @@ -1787,6 +1890,9 @@ static const struct of_device_id of_pwrap_match_tbl[] = {
>  	}, {
>  		.compatible = "mediatek,mt8183-pwrap",
>  		.data = &pwrap_mt8183,
> +	}, {
> +		.compatible = "mediatek,mt8516-pwrap",
> +		.data = &pwrap_mt8516,
>  	}, {
>  		/* sentinel */
>  	}
> 

^ permalink raw reply

* Re: [PATCH 04/24] soc: mediatek: pwrap: add missing check on rstc
From: Matthias Brugger @ 2019-04-12 17:44 UTC (permalink / raw)
  To: Fabien Parent, robh+dt, mark.rutland
  Cc: sean.wang, ryder.lee, hsin-hsiung.wang, wenzhen.yu, chaotian.jing,
	yong.mao, jjian.zhou, devicetree, linux-kernel, linux-i2c,
	linux-arm-kernel, linux-mediatek, linux-mmc, linux-gpio,
	linux-serial, linux-spi, linux-watchdog, linux-clk,
	stephane.leprovost
In-Reply-To: <20190323211612.860-5-fparent@baylibre.com>



On 23/03/2019 22:15, Fabien Parent wrote:
> The variable rstc is set only when the SoC PWRAP have the
> PWRAP_CAP_RESET capability. Check whether rstc is set before
> using it to avoid errors.
> 
> Signed-off-by: Fabien Parent <fparent@baylibre.com>
> ---
>  drivers/soc/mediatek/mtk-pmic-wrap.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)

applied to v5.1-next/soc

Thanks!

> 
> diff --git a/drivers/soc/mediatek/mtk-pmic-wrap.c b/drivers/soc/mediatek/mtk-pmic-wrap.c
> index 8236a6c87e19..b3ba2301f569 100644
> --- a/drivers/soc/mediatek/mtk-pmic-wrap.c
> +++ b/drivers/soc/mediatek/mtk-pmic-wrap.c
> @@ -1478,7 +1478,8 @@ static int pwrap_init(struct pmic_wrapper *wrp)
>  {
>  	int ret;
>  
> -	reset_control_reset(wrp->rstc);
> +	if (wrp->rstc)
> +		reset_control_reset(wrp->rstc);
>  	if (wrp->rstc_bridge)
>  		reset_control_reset(wrp->rstc_bridge);
>  
> 

^ permalink raw reply

* Re: [PATCH 03/24] dt-bindings: pwrap: mediatek: add pwrap support for MT8516
From: Matthias Brugger @ 2019-04-12 17:44 UTC (permalink / raw)
  To: Fabien Parent, robh+dt, mark.rutland
  Cc: sean.wang, ryder.lee, hsin-hsiung.wang, wenzhen.yu, chaotian.jing,
	yong.mao, jjian.zhou, devicetree, linux-kernel, linux-i2c,
	linux-arm-kernel, linux-mediatek, linux-mmc, linux-gpio,
	linux-serial, linux-spi, linux-watchdog, linux-clk,
	stephane.leprovost
In-Reply-To: <20190323211612.860-4-fparent@baylibre.com>



On 23/03/2019 22:15, Fabien Parent wrote:
> Add binding documentation of pwrap for MT8516 SoCs.
> 
> Signed-off-by: Fabien Parent <fparent@baylibre.com>
> ---
>  Documentation/devicetree/bindings/soc/mediatek/pwrap.txt | 1 +
>  1 file changed, 1 insertion(+)

applied to v5.1-next/soc

Thanks!

> 
> diff --git a/Documentation/devicetree/bindings/soc/mediatek/pwrap.txt b/Documentation/devicetree/bindings/soc/mediatek/pwrap.txt
> index 5a2ef1726e2a..7a32404c6114 100644
> --- a/Documentation/devicetree/bindings/soc/mediatek/pwrap.txt
> +++ b/Documentation/devicetree/bindings/soc/mediatek/pwrap.txt
> @@ -25,6 +25,7 @@ Required properties in pwrap device node.
>  	"mediatek,mt8135-pwrap" for MT8135 SoCs
>  	"mediatek,mt8173-pwrap" for MT8173 SoCs
>  	"mediatek,mt8183-pwrap" for MT8183 SoCs
> +	"mediatek,mt8516-pwrap" for MT8516 SoCs
>  - interrupts: IRQ for pwrap in SOC
>  - reg-names: Must include the following entries:
>    "pwrap": Main registers base
> 

^ permalink raw reply

* Re: [PATCH v2 3/4] MIPS: SGI-IP27: fix readb/writeb addressing
From: Alexandre Belloni @ 2019-04-11 21:17 UTC (permalink / raw)
  To: Thomas Bogendoerfer
  Cc: Ralf Baechle, Paul Burton, James Hogan, Dmitry Torokhov,
	Lee Jones, David S. Miller, Alessandro Zummo, Greg Kroah-Hartman,
	Jiri Slaby, linux-mips, linux-kernel, linux-input, netdev,
	linux-rtc, linux-serial
In-Reply-To: <20190409154610.6735-4-tbogendoerfer@suse.de>

On 09/04/2019 17:46:07+0200, Thomas Bogendoerfer wrote:
> Our chosen byte swapping, which is what firmware already uses, is to
> do readl/writel by normal lw/sw intructions (data invariance). This
> also means we need to mangle addresses for u8 and u16 accesses. The
> mangling for 16bit has been done aready, but 8bit one was missing.
> Correcting this causes different addresses for accesses to the
> SuperIO and local bus of the IOC3 chip. This is fixed by changing
> byte order in ioc3 and m48rtc_rtc structs.
> 
> Signed-off-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>

Considering this is probably the only platform to have that RTC and that
is probably not worth having a better fix for a platform this old:

Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>

> ---
>  arch/mips/include/asm/mach-ip27/mangle-port.h |   2 +-
>  arch/mips/include/asm/sn/ioc3.h               | 198 +++++++++++++-------------
>  arch/mips/sgi-ip27/ip27-console.c             |   5 +-
>  drivers/rtc/rtc-m48t35.c                      |  11 ++
>  drivers/tty/serial/8250/8250_ioc3.c           |   4 +-
>  5 files changed, 114 insertions(+), 106 deletions(-)
> 
> diff --git a/arch/mips/include/asm/mach-ip27/mangle-port.h b/arch/mips/include/asm/mach-ip27/mangle-port.h
> index f6e4912ea062..7771ae0f3971 100644
> --- a/arch/mips/include/asm/mach-ip27/mangle-port.h
> +++ b/arch/mips/include/asm/mach-ip27/mangle-port.h
> @@ -8,7 +8,7 @@
>  #ifndef __ASM_MACH_IP27_MANGLE_PORT_H
>  #define __ASM_MACH_IP27_MANGLE_PORT_H
>  
> -#define __swizzle_addr_b(port)	(port)
> +#define __swizzle_addr_b(port)	((port) ^ 3)
>  #define __swizzle_addr_w(port)	((port) ^ 2)
>  #define __swizzle_addr_l(port)	(port)
>  #define __swizzle_addr_q(port)	(port)
> diff --git a/arch/mips/include/asm/sn/ioc3.h b/arch/mips/include/asm/sn/ioc3.h
> index 69069f420930..059885d736de 100644
> --- a/arch/mips/include/asm/sn/ioc3.h
> +++ b/arch/mips/include/asm/sn/ioc3.h
> @@ -10,35 +10,35 @@
>  
>  /* serial port register map */
>  struct ioc3_serialregs {
> -	uint32_t	sscr;
> -	uint32_t	stpir;
> -	uint32_t	stcir;
> -	uint32_t	srpir;
> -	uint32_t	srcir;
> -	uint32_t	srtr;
> -	uint32_t	shadow;
> +	u32	sscr;
> +	u32	stpir;
> +	u32	stcir;
> +	u32	srpir;
> +	u32	srcir;
> +	u32	srtr;
> +	u32	shadow;
>  };
>  
>  /* SUPERIO uart register map */
>  struct ioc3_uartregs {
> +	u8	iu_lcr;
>  	union {
> -		char	rbr;	/* read only, DLAB == 0 */
> -		char	thr;	/* write only, DLAB == 0 */
> -		char	dll;	/* DLAB == 1 */
> -	} u1;
> +		u8	iir;	/* read only */
> +		u8	fcr;	/* write only */
> +	};
>  	union {
> -		char	ier;	/* DLAB == 0 */
> -		char	dlm;	/* DLAB == 1 */
> -	} u2;
> +		u8	ier;	/* DLAB == 0 */
> +		u8	dlm;	/* DLAB == 1 */
> +	};
>  	union {
> -		char	iir;	/* read only */
> -		char	fcr;	/* write only */
> -	} u3;
> -	char	iu_lcr;
> -	char	iu_mcr;
> -	char	iu_lsr;
> -	char	iu_msr;
> -	char	iu_scr;
> +		u8	rbr;	/* read only, DLAB == 0 */
> +		u8	thr;	/* write only, DLAB == 0 */
> +		u8	dll;	/* DLAB == 1 */
> +	} u1;
> +	u8	iu_scr;
> +	u8	iu_msr;
> +	u8	iu_lsr;
> +	u8	iu_mcr;
>  };
>  
>  #define iu_rbr u1.rbr
> @@ -50,122 +50,122 @@ struct ioc3_uartregs {
>  #define iu_fcr u3.fcr
>  
>  struct ioc3_sioregs {
> -	char	fill[0x141];	/* starts at 0x141 */
> +	u8	fill[0x141];	/* starts at 0x141 */
>  
> -	char	uartc;
> -	char	kbdcg;
> +	u8	kbdcg;
> +	u8	uartc;
>  
> -	char	fill0[0x150 - 0x142 - 1];
> +	u8	fill0[0x151 - 0x142 - 1];
>  
> -	char	pp_data;
> -	char	pp_dsr;
> -	char	pp_dcr;
> +	u8	pp_dcr;
> +	u8	pp_dsr;
> +	u8	pp_data;
>  
> -	char	fill1[0x158 - 0x152 - 1];
> +	u8	fill1[0x159 - 0x153 - 1];
>  
> -	char	pp_fifa;
> -	char	pp_cfgb;
> -	char	pp_ecr;
> +	u8	pp_ecr;
> +	u8	pp_cfgb;
> +	u8	pp_fifa;
>  
> -	char	fill2[0x168 - 0x15a - 1];
> +	u8	fill2[0x16a - 0x15b - 1];
>  
> -	char	rtcad;
> -	char	rtcdat;
> +	u8	rtcdat;
> +	u8	rtcad;
>  
> -	char	fill3[0x170 - 0x169 - 1];
> +	u8	fill3[0x170 - 0x16b - 1];
>  
>  	struct ioc3_uartregs	uartb;	/* 0x20170  */
>  	struct ioc3_uartregs	uarta;	/* 0x20178  */
>  };
>  
>  struct ioc3_ethregs {
> -	uint32_t	emcr;		/* 0x000f0  */
> -	uint32_t	eisr;		/* 0x000f4  */
> -	uint32_t	eier;		/* 0x000f8  */
> -	uint32_t	ercsr;		/* 0x000fc  */
> -	uint32_t	erbr_h;		/* 0x00100  */
> -	uint32_t	erbr_l;		/* 0x00104  */
> -	uint32_t	erbar;		/* 0x00108  */
> -	uint32_t	ercir;		/* 0x0010c  */
> -	uint32_t	erpir;		/* 0x00110  */
> -	uint32_t	ertr;		/* 0x00114  */
> -	uint32_t	etcsr;		/* 0x00118  */
> -	uint32_t	ersr;		/* 0x0011c  */
> -	uint32_t	etcdc;		/* 0x00120  */
> -	uint32_t	ebir;		/* 0x00124  */
> -	uint32_t	etbr_h;		/* 0x00128  */
> -	uint32_t	etbr_l;		/* 0x0012c  */
> -	uint32_t	etcir;		/* 0x00130  */
> -	uint32_t	etpir;		/* 0x00134  */
> -	uint32_t	emar_h;		/* 0x00138  */
> -	uint32_t	emar_l;		/* 0x0013c  */
> -	uint32_t	ehar_h;		/* 0x00140  */
> -	uint32_t	ehar_l;		/* 0x00144  */
> -	uint32_t	micr;		/* 0x00148  */
> -	uint32_t	midr_r;		/* 0x0014c  */
> -	uint32_t	midr_w;		/* 0x00150  */
> +	u32	emcr;		/* 0x000f0  */
> +	u32	eisr;		/* 0x000f4  */
> +	u32	eier;		/* 0x000f8  */
> +	u32	ercsr;		/* 0x000fc  */
> +	u32	erbr_h;		/* 0x00100  */
> +	u32	erbr_l;		/* 0x00104  */
> +	u32	erbar;		/* 0x00108  */
> +	u32	ercir;		/* 0x0010c  */
> +	u32	erpir;		/* 0x00110  */
> +	u32	ertr;		/* 0x00114  */
> +	u32	etcsr;		/* 0x00118  */
> +	u32	ersr;		/* 0x0011c  */
> +	u32	etcdc;		/* 0x00120  */
> +	u32	ebir;		/* 0x00124  */
> +	u32	etbr_h;		/* 0x00128  */
> +	u32	etbr_l;		/* 0x0012c  */
> +	u32	etcir;		/* 0x00130  */
> +	u32	etpir;		/* 0x00134  */
> +	u32	emar_h;		/* 0x00138  */
> +	u32	emar_l;		/* 0x0013c  */
> +	u32	ehar_h;		/* 0x00140  */
> +	u32	ehar_l;		/* 0x00144  */
> +	u32	micr;		/* 0x00148  */
> +	u32	midr_r;		/* 0x0014c  */
> +	u32	midr_w;		/* 0x00150  */
>  };
>  
>  struct ioc3_serioregs {
> -	uint32_t	km_csr;		/* 0x0009c  */
> -	uint32_t	k_rd;		/* 0x000a0  */
> -	uint32_t	m_rd;		/* 0x000a4  */
> -	uint32_t	k_wd;		/* 0x000a8  */
> -	uint32_t	m_wd;		/* 0x000ac  */
> +	u32	km_csr;		/* 0x0009c  */
> +	u32	k_rd;		/* 0x000a0  */
> +	u32	m_rd;		/* 0x000a4  */
> +	u32	k_wd;		/* 0x000a8  */
> +	u32	m_wd;		/* 0x000ac  */
>  };
>  
>  /* Register layout of IOC3 in configuration space.  */
>  struct ioc3 {
>  	/* PCI Config Space registers  */
> -	uint32_t	pci_id;		/* 0x00000  */
> -	uint32_t	pci_scr;	/* 0x00004  */
> -	uint32_t	pci_rev;	/* 0x00008  */
> -	uint32_t	pci_lat;	/* 0x0000c  */
> -	uint32_t	pci_addr;	/* 0x00010  */
> -	uint32_t	pci_err_addr_l;	/* 0x00014  */
> -	uint32_t	pci_err_addr_h;	/* 0x00018  */
> -
> -	uint32_t	sio_ir;		/* 0x0001c  */
> -	uint32_t	sio_ies;	/* 0x00020  */
> -	uint32_t	sio_iec;	/* 0x00024  */
> -	uint32_t	sio_cr;		/* 0x00028  */
> -	uint32_t	int_out;	/* 0x0002c  */
> -	uint32_t	mcr;		/* 0x00030  */
> +	u32	pci_id;		/* 0x00000  */
> +	u32	pci_scr;	/* 0x00004  */
> +	u32	pci_rev;	/* 0x00008  */
> +	u32	pci_lat;	/* 0x0000c  */
> +	u32	pci_addr;	/* 0x00010  */
> +	u32	pci_err_addr_l;	/* 0x00014  */
> +	u32	pci_err_addr_h;	/* 0x00018  */
> +
> +	u32	sio_ir;		/* 0x0001c  */
> +	u32	sio_ies;	/* 0x00020  */
> +	u32	sio_iec;	/* 0x00024  */
> +	u32	sio_cr;		/* 0x00028  */
> +	u32	int_out;	/* 0x0002c  */
> +	u32	mcr;		/* 0x00030  */
>  
>  	/* General Purpose I/O registers  */
> -	uint32_t	gpcr_s;		/* 0x00034  */
> -	uint32_t	gpcr_c;		/* 0x00038  */
> -	uint32_t	gpdr;		/* 0x0003c  */
> -	uint32_t	gppr[16];	/* 0x00040  */
> +	u32	gpcr_s;		/* 0x00034  */
> +	u32	gpcr_c;		/* 0x00038  */
> +	u32	gpdr;		/* 0x0003c  */
> +	u32	gppr[16];	/* 0x00040  */
>  
>  	/* Parallel Port Registers  */
> -	uint32_t	ppbr_h_a;	/* 0x00080  */
> -	uint32_t	ppbr_l_a;	/* 0x00084  */
> -	uint32_t	ppcr_a;		/* 0x00088  */
> -	uint32_t	ppcr;		/* 0x0008c  */
> -	uint32_t	ppbr_h_b;	/* 0x00090  */
> -	uint32_t	ppbr_l_b;	/* 0x00094  */
> -	uint32_t	ppcr_b;		/* 0x00098  */
> +	u32	ppbr_h_a;	/* 0x00080  */
> +	u32	ppbr_l_a;	/* 0x00084  */
> +	u32	ppcr_a;		/* 0x00088  */
> +	u32	ppcr;		/* 0x0008c  */
> +	u32	ppbr_h_b;	/* 0x00090  */
> +	u32	ppbr_l_b;	/* 0x00094  */
> +	u32	ppcr_b;		/* 0x00098  */
>  
>  	/* Keyboard and Mouse Registers	 */
>  	struct ioc3_serioregs	serio;
>  
>  	/* Serial Port Registers  */
> -	uint32_t	sbbr_h;		/* 0x000b0  */
> -	uint32_t	sbbr_l;		/* 0x000b4  */
> +	u32	sbbr_h;		/* 0x000b0  */
> +	u32	sbbr_l;		/* 0x000b4  */
>  	struct ioc3_serialregs	port_a;
>  	struct ioc3_serialregs	port_b;
>  
>  	/* Ethernet Registers */
>  	struct ioc3_ethregs	eth;
> -	uint32_t	pad1[(0x20000 - 0x00154) / 4];
> +	u32	pad1[(0x20000 - 0x00154) / 4];
>  
>  	/* SuperIO Registers  XXX */
>  	struct ioc3_sioregs	sregs;	/* 0x20000 */
> -	uint32_t	pad2[(0x40000 - 0x20180) / 4];
> +	u32	pad2[(0x40000 - 0x20180) / 4];
>  
>  	/* SSRAM Diagnostic Access */
> -	uint32_t	ssram[(0x80000 - 0x40000) / 4];
> +	u32	ssram[(0x80000 - 0x40000) / 4];
>  
>  	/* Bytebus device offsets
>  	   0x80000 -   Access to the generic devices selected with   DEV0
> @@ -598,8 +598,4 @@ struct ioc3_etxd {
>  
>  #define MIDR_DATA_MASK		0x0000ffff
>  
> -#if defined(CONFIG_SGI_IP27) || defined(CONFIG_SGI_IP30)
> -extern int bridge_alloc_irq(struct pci_dev *dev);
> -#endif
> -
>  #endif /* MIPS_SN_IOC3_H */
> diff --git a/arch/mips/sgi-ip27/ip27-console.c b/arch/mips/sgi-ip27/ip27-console.c
> index 6bdb48d41276..5886bee89d06 100644
> --- a/arch/mips/sgi-ip27/ip27-console.c
> +++ b/arch/mips/sgi-ip27/ip27-console.c
> @@ -35,6 +35,7 @@ void prom_putchar(char c)
>  {
>  	struct ioc3_uartregs *uart = console_uart();
>  
> -	while ((uart->iu_lsr & 0x20) == 0);
> -	uart->iu_thr = c;
> +	while ((readb(&uart->iu_lsr) & 0x20) == 0)
> +		;
> +	writeb(c, &uart->iu_thr);
>  }
> diff --git a/drivers/rtc/rtc-m48t35.c b/drivers/rtc/rtc-m48t35.c
> index 0cf6507de3c7..05f0d91366af 100644
> --- a/drivers/rtc/rtc-m48t35.c
> +++ b/drivers/rtc/rtc-m48t35.c
> @@ -24,6 +24,16 @@
>  
>  struct m48t35_rtc {
>  	u8	pad[0x7ff8];    /* starts at 0x7ff8 */
> +#ifdef CONFIG_SGI_IP27
> +	u8	hour;
> +	u8	min;
> +	u8	sec;
> +	u8	control;
> +	u8	year;
> +	u8	month;
> +	u8	date;
> +	u8	day;
> +#else
>  	u8	control;
>  	u8	sec;
>  	u8	min;
> @@ -32,6 +42,7 @@ struct m48t35_rtc {
>  	u8	date;
>  	u8	month;
>  	u8	year;
> +#endif
>  };
>  
>  #define M48T35_RTC_SET		0x80
> diff --git a/drivers/tty/serial/8250/8250_ioc3.c b/drivers/tty/serial/8250/8250_ioc3.c
> index 2be6ed2967e0..4c405f1b9c67 100644
> --- a/drivers/tty/serial/8250/8250_ioc3.c
> +++ b/drivers/tty/serial/8250/8250_ioc3.c
> @@ -23,12 +23,12 @@ struct ioc3_8250_data {
>  
>  static unsigned int ioc3_serial_in(struct uart_port *p, int offset)
>  {
> -	return readb(p->membase + offset);
> +	return readb(p->membase + (offset ^ 3));
>  }
>  
>  static void ioc3_serial_out(struct uart_port *p, int offset, int value)
>  {
> -	writeb(value, p->membase + offset);
> +	writeb(value, p->membase + (offset ^ 3));
>  }
>  
>  static int serial8250_ioc3_probe(struct platform_device *pdev)
> -- 
> 2.13.7
> 

-- 
Alexandre Belloni, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply

* Re: [PATCH v4 2/2] tty: serial: add driver for the SiFive UART
From: Atish Patra @ 2019-04-11 21:14 UTC (permalink / raw)
  To: Paul Walmsley, linux-kernel@vger.kernel.org,
	linux-riscv@lists.infradead.org, linux-serial@vger.kernel.org,
	gregkh@linuxfoundation.org
  Cc: Paul Walmsley, Wesley Terpstra, Palmer Dabbelt, Julia Lawall,
	Jiri Slaby
In-Reply-To: <20190411090027.8670-3-paul.walmsley@sifive.com>

On 4/11/19 2:02 AM, Paul Walmsley wrote:
> Add a serial driver for the SiFive UART, found on SiFive FU540 devices
> (among others).
> 
> The underlying serial IP block is relatively basic, and currently does
> not support serial break detection.  Further information on the IP
> block can be found in the documentation and Chisel sources:
> 
>      https://static.dev.sifive.com/FU540-C000-v1.0.pdf
> 
>      https://github.com/sifive/sifive-blocks/tree/master/src/main/scala/devices/uart
> 
> This driver was written in collaboration with Wesley Terpstra
> <wesley@sifive.com>.
> 
> Tested on a SiFive HiFive Unleashed A00 board, using BBL and the open-
> source FSBL (using a DT file based on what's targeted for mainline).
> 
> This revision incorporates changes based on comments by Emil Renner
> Berthing <kernel@esmil.dk>.
> 
> Signed-off-by: Paul Walmsley <paul.walmsley@sifive.com>
> Signed-off-by: Paul Walmsley <paul@pwsan.com>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Jiri Slaby <jslaby@suse.com>
> Cc: Palmer Dabbelt <palmer@sifive.com>
> Cc: Wesley Terpstra <wesley@sifive.com>
> Cc: linux-serial@vger.kernel.org
> Cc: linux-riscv@lists.infradead.org
> Cc: linux-kernel@vger.kernel.org
> Cc: Julia Lawall <julia.lawall@lip6.fr>
> ---
>   drivers/tty/serial/Kconfig       |   24 +
>   drivers/tty/serial/Makefile      |    1 +
>   drivers/tty/serial/sifive.c      | 1067 ++++++++++++++++++++++++++++++
>   include/uapi/linux/serial_core.h |    3 +
>   4 files changed, 1095 insertions(+)
>   create mode 100644 drivers/tty/serial/sifive.c
> 
> diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
> index 72966bc0ac76..561e053b690a 100644
> --- a/drivers/tty/serial/Kconfig
> +++ b/drivers/tty/serial/Kconfig
> @@ -1095,6 +1095,30 @@ config SERIAL_OMAP_CONSOLE
>   	  your boot loader about how to pass options to the kernel at
>   	  boot time.)
>   
> +config SERIAL_SIFIVE
> +	tristate "SiFive UART support"
> +	depends on OF
> +	select SERIAL_CORE
> +	help
> +	  Select this option if you are building a kernel for a device that
> +	  contains a SiFive UART IP block.  This type of UART is present on
> +	  SiFive FU540 SoCs, among others.
> +
> +config SERIAL_SIFIVE_CONSOLE
> +	bool "Console on SiFive UART"
> +	depends on SERIAL_SIFIVE=y
> +	select SERIAL_CORE_CONSOLE
> +	help
> +	  Select this option if you would like to use a SiFive UART as the
> +	  system console.
> +
> +	  Even if you say Y here, the currently visible virtual console
> +	  (/dev/tty0) will still be used as the system console by default, but
> +	  you can alter that using a kernel command line option such as
> +	  "console=ttySIFx". (Try "man bootparam" or see the documentation of
> +	  your boot loader about how to pass options to the kernel at
> +	  boot time.)
> +
>   config SERIAL_LANTIQ
>   	bool "Lantiq serial driver"
>   	depends on LANTIQ
> diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
> index 40b702aaa85e..2aff1d07d08b 100644
> --- a/drivers/tty/serial/Makefile
> +++ b/drivers/tty/serial/Makefile
> @@ -92,6 +92,7 @@ obj-$(CONFIG_SERIAL_PIC32)	+= pic32_uart.o
>   obj-$(CONFIG_SERIAL_MPS2_UART)	+= mps2-uart.o
>   obj-$(CONFIG_SERIAL_OWL)	+= owl-uart.o
>   obj-$(CONFIG_SERIAL_RDA)	+= rda-uart.o
> +obj-$(CONFIG_SERIAL_SIFIVE)	+= sifive.o
>   
>   # GPIOLIB helpers for modem control lines
>   obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
> diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c
> new file mode 100644
> index 000000000000..7401cfed0f48
> --- /dev/null
> +++ b/drivers/tty/serial/sifive.c
> @@ -0,0 +1,1067 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * SiFive UART driver
> + * Copyright (C) 2018 Paul Walmsley <paul@pwsan.com>
> + * Copyright (C) 2018 SiFive
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * Based partially on:
> + * - drivers/tty/serial/pxa.c
> + * - drivers/tty/serial/amba-pl011.c
> + * - drivers/tty/serial/uartlite.c
> + * - drivers/tty/serial/omap-serial.c
> + * - drivers/pwm/pwm-sifive.c
> + *
> + * See the following sources for further documentation:
> + * - Chapter 19 "Universal Asynchronous Receiver/Transmitter (UART)" of
> + *   SiFive FE310-G000 v2p3
> + * - The tree/master/src/main/scala/devices/uart directory of
> + *   https://github.com/sifive/sifive-blocks/
> + *
> + * The SiFive UART design is not 8250-compatible.  The following common
> + * features are not supported:
> + * - Word lengths other than 8 bits
> + * - Break handling
> + * - Parity
> + * - Flow control
> + * - Modem signals (DSR, RI, etc.)
> + * On the other hand, the design is free from the baggage of the 8250
> + * programming model.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/console.h>
> +#include <linux/delay.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +#include <linux/serial_core.h>
> +#include <linux/serial_reg.h>
> +#include <linux/slab.h>
> +#include <linux/tty.h>
> +#include <linux/tty_flip.h>
> +
> +/*
> + * Register offsets
> + */
> +
> +/* TXDATA */
> +#define SIFIVE_SERIAL_TXDATA_OFFS		0x0
> +#define SIFIVE_SERIAL_TXDATA_FULL_SHIFT		31
> +#define SIFIVE_SERIAL_TXDATA_FULL_MASK		(1 << SIFIVE_SERIAL_TXDATA_FULL_SHIFT)
> +#define SIFIVE_SERIAL_TXDATA_DATA_SHIFT		0
> +#define SIFIVE_SERIAL_TXDATA_DATA_MASK		(0xff << SIFIVE_SERIAL_TXDATA_DATA_SHIFT)
> +
> +/* RXDATA */
> +#define SIFIVE_SERIAL_RXDATA_OFFS		0x4
> +#define SIFIVE_SERIAL_RXDATA_EMPTY_SHIFT	31
> +#define SIFIVE_SERIAL_RXDATA_EMPTY_MASK		(1 << SIFIVE_SERIAL_RXDATA_EMPTY_SHIFT)
> +#define SIFIVE_SERIAL_RXDATA_DATA_SHIFT		0
> +#define SIFIVE_SERIAL_RXDATA_DATA_MASK		(0xff << SIFIVE_SERIAL_RXDATA_DATA_SHIFT)
> +
> +/* TXCTRL */
> +#define SIFIVE_SERIAL_TXCTRL_OFFS		0x8
> +#define SIFIVE_SERIAL_TXCTRL_TXCNT_SHIFT	16
> +#define SIFIVE_SERIAL_TXCTRL_TXCNT_MASK		(0x7 << SIFIVE_SERIAL_TXCTRL_TXCNT_SHIFT)
> +#define SIFIVE_SERIAL_TXCTRL_NSTOP_SHIFT	1
> +#define SIFIVE_SERIAL_TXCTRL_NSTOP_MASK		(1 << SIFIVE_SERIAL_TXCTRL_NSTOP_SHIFT)
> +#define SIFIVE_SERIAL_TXCTRL_TXEN_SHIFT		0
> +#define SIFIVE_SERIAL_TXCTRL_TXEN_MASK		(1 << SIFIVE_SERIAL_TXCTRL_TXEN_SHIFT)
> +
> +/* RXCTRL */
> +#define SIFIVE_SERIAL_RXCTRL_OFFS		0xC
> +#define SIFIVE_SERIAL_RXCTRL_RXCNT_SHIFT	16
> +#define SIFIVE_SERIAL_RXCTRL_RXCNT_MASK		(0x7 << SIFIVE_SERIAL_TXCTRL_TXCNT_SHIFT)
> +#define SIFIVE_SERIAL_RXCTRL_RXEN_SHIFT		0
> +#define SIFIVE_SERIAL_RXCTRL_RXEN_MASK		(1 << SIFIVE_SERIAL_RXCTRL_RXEN_SHIFT)
> +
> +/* IE */
> +#define SIFIVE_SERIAL_IE_OFFS			0x10
> +#define SIFIVE_SERIAL_IE_RXWM_SHIFT		1
> +#define SIFIVE_SERIAL_IE_RXWM_MASK		(1 << SIFIVE_SERIAL_IE_RXWM_SHIFT)
> +#define SIFIVE_SERIAL_IE_TXWM_SHIFT		0
> +#define SIFIVE_SERIAL_IE_TXWM_MASK		(1 << SIFIVE_SERIAL_IE_TXWM_SHIFT)
> +
> +/* IP */
> +#define SIFIVE_SERIAL_IP_OFFS			0x14
> +#define SIFIVE_SERIAL_IP_RXWM_SHIFT		1
> +#define SIFIVE_SERIAL_IP_RXWM_MASK		(1 << SIFIVE_SERIAL_IP_RXWM_SHIFT)
> +#define SIFIVE_SERIAL_IP_TXWM_SHIFT		0
> +#define SIFIVE_SERIAL_IP_TXWM_MASK		(1 << SIFIVE_SERIAL_IP_TXWM_SHIFT)
> +
> +/* DIV */
> +#define SIFIVE_SERIAL_DIV_OFFS			0x18
> +#define SIFIVE_SERIAL_DIV_DIV_SHIFT		0
> +#define SIFIVE_SERIAL_DIV_DIV_MASK		(0xffff << SIFIVE_SERIAL_IP_DIV_SHIFT)
> +
> +/*
> + * Config macros
> + */
> +
> +/*
> + * SIFIVE_SERIAL_MAX_PORTS: maximum number of UARTs on a device that can
> + *                          host a serial console
> + */
> +#define SIFIVE_SERIAL_MAX_PORTS			8
> +
> +/*
> + * SIFIVE_DEFAULT_BAUD_RATE: default baud rate that the driver should
> + *                           configure itself to use
> + */
> +#define SIFIVE_DEFAULT_BAUD_RATE		115200
> +
> +/* SIFIVE_SERIAL_NAME: our driver's name that we pass to the operating system */
> +#define SIFIVE_SERIAL_NAME			"sifive-serial"
> +
> +/* SIFIVE_TTY_PREFIX: tty name prefix for SiFive serial ports */
> +#define SIFIVE_TTY_PREFIX			"ttySIF"
> +
> +/* SIFIVE_TX_FIFO_DEPTH: depth of the TX FIFO (in bytes) */
> +#define SIFIVE_TX_FIFO_DEPTH			8
> +
> +/* SIFIVE_RX_FIFO_DEPTH: depth of the TX FIFO (in bytes) */
> +#define SIFIVE_RX_FIFO_DEPTH			8
> +
> +#if (SIFIVE_TX_FIFO_DEPTH != SIFIVE_RX_FIFO_DEPTH)
> +#error Driver does not support configurations with different TX, RX FIFO sizes
> +#endif
> +
> +/*
> + *
> + */
> +

Remove it if no comment here ?

Regards,
Atish
> +/**
> + * sifive_serial_port - driver-specific data extension to struct uart_port
> + * @port: struct uart_port embedded in this struct
> + * @dev: struct device *
> + * @ier: shadowed copy of the interrupt enable register
> + * @clkin_rate: input clock to the UART IP block.
> + * @baud_rate: UART serial line rate (e.g., 115200 baud)
> + * @clk_notifier: clock rate change notifier for upstream clock changes
> + *
> + * Configuration data specific to this SiFive UART.
> + */
> +struct sifive_serial_port {
> +	struct uart_port	port;
> +	struct device		*dev;
> +	unsigned char		ier;
> +	unsigned long		clkin_rate;
> +	unsigned long		baud_rate;
> +	struct clk		*clk;
> +	struct notifier_block	clk_notifier;
> +};
> +
> +/*
> + * Structure container-of macros
> + */
> +
> +#define port_to_sifive_serial_port(p) (container_of((p), \
> +						    struct sifive_serial_port, \
> +						    port))
> +
> +#define notifier_to_sifive_serial_port(nb) (container_of((nb), \
> +							 struct sifive_serial_port, \
> +							 clk_notifier))
> +
> +/*
> + * Forward declarations
> + */
> +static void sifive_serial_stop_tx(struct uart_port *port);
> +
> +/*
> + * Internal functions
> + */
> +
> +/**
> + * __ssp_early_writel() - write to a SiFive serial port register (early)
> + * @port: pointer to a struct uart_port record
> + * @offs: register address offset from the IP block base address
> + * @v: value to write to the register
> + *
> + * Given a pointer @port to a struct uart_port record, write the value
> + * @v to the IP block register address offset @offs.  This function is
> + * intended for early console use.
> + *
> + * Context: Intended to be used only by the earlyconsole code.
> + */
> +static void __ssp_early_writel(u32 v, u16 offs, struct uart_port *port)
> +{
> +	writel_relaxed(v, port->membase + offs);
> +}
> +
> +/**
> + * __ssp_early_readl() - read from a SiFive serial port register (early)
> + * @port: pointer to a struct uart_port record
> + * @offs: register address offset from the IP block base address
> + *
> + * Given a pointer @port to a struct uart_port record, read the
> + * contents of the IP block register located at offset @offs from the
> + * IP block base and return it.  This function is intended for early
> + * console use.
> + *
> + * Context: Intended to be called only by the earlyconsole code or by
> + *          __ssp_readl() or __ssp_writel() (in this driver)
> + *
> + * Returns: the register value read from the UART.
> + */
> +static u32 __ssp_early_readl(struct uart_port *port, u16 offs)
> +{
> +	return readl_relaxed(port->membase + offs);
> +}
> +
> +/**
> + * __ssp_writel() - write to a SiFive serial port register
> + * @v: value to write to the register
> + * @offs: register address offset from the IP block base address
> + * @ssp: pointer to a struct sifive_serial_port record
> + *
> + * Write the value @v to the IP block register located at offset @offs from the
> + * IP block base, given a pointer @ssp to a struct sifive_serial_port record.
> + *
> + * Context: Any context.
> + */
> +static void __ssp_writel(u32 v, u16 offs, struct sifive_serial_port *ssp)
> +{
> +	__ssp_early_writel(v, offs, &ssp->port);
> +}
> +
> +/**
> + * __ssp_readl() - read from a SiFive serial port register
> + * @ssp: pointer to a struct sifive_serial_port record
> + * @offs: register address offset from the IP block base address
> + *
> + * Read the contents of the IP block register located at offset @offs from the
> + * IP block base, given a pointer @ssp to a struct sifive_serial_port record.
> + *
> + * Context: Any context.
> + *
> + * Returns: the value of the UART register
> + */
> +static u32 __ssp_readl(struct sifive_serial_port *ssp, u16 offs)
> +{
> +	return __ssp_early_readl(&ssp->port, offs);
> +}
> +
> +/**
> + * sifive_serial_is_txfifo_full() - is the TXFIFO full?
> + * @ssp: pointer to a struct sifive_serial_port
> + *
> + * Read the transmit FIFO "full" bit, returning a non-zero value if the
> + * TX FIFO is full, or zero if space remains.  Intended to be used to prevent
> + * writes to the TX FIFO when it's full.
> + *
> + * Returns: SIFIVE_SERIAL_TXDATA_FULL_MASK (non-zero) if the transmit FIFO
> + * is full, or 0 if space remains.
> + */
> +static int sifive_serial_is_txfifo_full(struct sifive_serial_port *ssp)
> +{
> +	return __ssp_readl(ssp, SIFIVE_SERIAL_TXDATA_OFFS) &
> +		SIFIVE_SERIAL_TXDATA_FULL_MASK;
> +}
> +
> +/**
> + * __ssp_transmit_char() - enqueue a byte to transmit onto the TX FIFO
> + * @ssp: pointer to a struct sifive_serial_port
> + * @ch: character to transmit
> + *
> + * Enqueue a byte @ch onto the transmit FIFO, given a pointer @ssp to the
> + * struct sifive_serial_port * to transmit on.  Caller should first check to
> + * ensure that the TXFIFO has space; see sifive_serial_is_txfifo_full().
> + *
> + * Context: Any context.
> + */
> +static void __ssp_transmit_char(struct sifive_serial_port *ssp, int ch)
> +{
> +	__ssp_writel(ch, SIFIVE_SERIAL_TXDATA_OFFS, ssp);
> +}
> +
> +/**
> + * __ssp_transmit_chars() - enqueue multiple bytes onto the TX FIFO
> + * @ssp: pointer to a struct sifive_serial_port
> + *
> + * Transfer up to a TX FIFO size's worth of characters from the Linux serial
> + * transmit buffer to the SiFive UART TX FIFO.
> + *
> + * Context: Any context.  Expects @ssp->port.lock to be held by caller.
> + */
> +static void __ssp_transmit_chars(struct sifive_serial_port *ssp)
> +{
> +	struct circ_buf *xmit = &ssp->port.state->xmit;
> +	int count;
> +
> +	if (ssp->port.x_char) {
> +		__ssp_transmit_char(ssp, ssp->port.x_char);
> +		ssp->port.icount.tx++;
> +		ssp->port.x_char = 0;
> +		return;
> +	}
> +	if (uart_circ_empty(xmit) || uart_tx_stopped(&ssp->port)) {
> +		sifive_serial_stop_tx(&ssp->port);
> +		return;
> +	}
> +	count = SIFIVE_TX_FIFO_DEPTH;
> +	do {
> +		__ssp_transmit_char(ssp, xmit->buf[xmit->tail]);
> +		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
> +		ssp->port.icount.tx++;
> +		if (uart_circ_empty(xmit))
> +			break;
> +	} while (--count > 0);
> +
> +	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
> +		uart_write_wakeup(&ssp->port);
> +
> +	if (uart_circ_empty(xmit))
> +		sifive_serial_stop_tx(&ssp->port);
> +}
> +
> +/**
> + * __ssp_enable_txwm() - enable transmit watermark interrupts
> + * @ssp: pointer to a struct sifive_serial_port
> + *
> + * Enable interrupt generation when the transmit FIFO watermark is reached
> + * on the SiFive UART referred to by @ssp.
> + */
> +static void __ssp_enable_txwm(struct sifive_serial_port *ssp)
> +{
> +	if (ssp->ier & SIFIVE_SERIAL_IE_TXWM_MASK)
> +		return;
> +
> +	ssp->ier |= SIFIVE_SERIAL_IE_TXWM_MASK;
> +	__ssp_writel(ssp->ier, SIFIVE_SERIAL_IE_OFFS, ssp);
> +}
> +
> +/**
> + * __ssp_enable_rxwm() - enable receive watermark interrupts
> + * @ssp: pointer to a struct sifive_serial_port
> + *
> + * Enable interrupt generation when the receive FIFO watermark is reached
> + * on the SiFive UART referred to by @ssp.
> + */
> +static void __ssp_enable_rxwm(struct sifive_serial_port *ssp)
> +{
> +	if (ssp->ier & SIFIVE_SERIAL_IE_RXWM_MASK)
> +		return;
> +
> +	ssp->ier |= SIFIVE_SERIAL_IE_RXWM_MASK;
> +	__ssp_writel(ssp->ier, SIFIVE_SERIAL_IE_OFFS, ssp);
> +}
> +
> +/**
> + * __ssp_disable_txwm() - disable transmit watermark interrupts
> + * @ssp: pointer to a struct sifive_serial_port
> + *
> + * Disable interrupt generation when the transmit FIFO watermark is reached
> + * on the UART referred to by @ssp.
> + */
> +static void __ssp_disable_txwm(struct sifive_serial_port *ssp)
> +{
> +	if (!(ssp->ier & SIFIVE_SERIAL_IE_TXWM_MASK))
> +		return;
> +
> +	ssp->ier &= ~SIFIVE_SERIAL_IE_TXWM_MASK;
> +	__ssp_writel(ssp->ier, SIFIVE_SERIAL_IE_OFFS, ssp);
> +}
> +
> +/**
> + * __ssp_disable_rxwm() - disable receive watermark interrupts
> + * @ssp: pointer to a struct sifive_serial_port
> + *
> + * Disable interrupt generation when the receive FIFO watermark is reached
> + * on the UART referred to by @ssp.
> + */
> +static void __ssp_disable_rxwm(struct sifive_serial_port *ssp)
> +{
> +	if (!(ssp->ier & SIFIVE_SERIAL_IE_RXWM_MASK))
> +		return;
> +
> +	ssp->ier &= ~SIFIVE_SERIAL_IE_RXWM_MASK;
> +	__ssp_writel(ssp->ier, SIFIVE_SERIAL_IE_OFFS, ssp);
> +}
> +
> +/**
> + * __ssp_receive_char() - receive a byte from the UART
> + * @ssp: pointer to a struct sifive_serial_port
> + * @is_empty: char pointer to return whether the RX FIFO is empty
> + *
> + * Try to read a byte from the SiFive UART RX FIFO, referenced by
> + * @ssp, and to return it.  Also returns the RX FIFO empty bit in
> + * the char pointed to by @ch.  The caller must pass the byte back to the
> + * Linux serial layer if needed.
> + *
> + * Returns: the byte read from the UART RX FIFO.
> + */
> +static char __ssp_receive_char(struct sifive_serial_port *ssp, char *is_empty)
> +{
> +	u32 v;
> +	u8 ch;
> +
> +	v = __ssp_readl(ssp, SIFIVE_SERIAL_RXDATA_OFFS);
> +
> +	if (!is_empty)
> +		WARN_ON(1);
> +	else
> +		*is_empty = (v & SIFIVE_SERIAL_RXDATA_EMPTY_MASK) >>
> +			SIFIVE_SERIAL_RXDATA_EMPTY_SHIFT;
> +
> +	ch = (v & SIFIVE_SERIAL_RXDATA_DATA_MASK) >>
> +		SIFIVE_SERIAL_RXDATA_DATA_SHIFT;
> +
> +	return ch;
> +}
> +
> +/**
> + * __ssp_receive_chars() - receive multiple bytes from the UART
> + * @ssp: pointer to a struct sifive_serial_port
> + *
> + * Receive up to an RX FIFO's worth of bytes from the SiFive UART referred
> + * to by @ssp and pass them up to the Linux serial layer.
> + *
> + * Context: Expects ssp->port.lock to be held by caller.
> + */
> +static void __ssp_receive_chars(struct sifive_serial_port *ssp)
> +{
> +	unsigned char ch;
> +	char is_empty;
> +	int c;
> +
> +	for (c = SIFIVE_RX_FIFO_DEPTH; c > 0; --c) {
> +		ch = __ssp_receive_char(ssp, &is_empty);
> +		if (is_empty)
> +			break;
> +
> +		ssp->port.icount.rx++;
> +		uart_insert_char(&ssp->port, 0, 0, ch, TTY_NORMAL);
> +	}
> +
> +	spin_unlock(&ssp->port.lock);
> +	tty_flip_buffer_push(&ssp->port.state->port);
> +	spin_lock(&ssp->port.lock);
> +}
> +
> +/**
> + * __ssp_update_div() - calculate the divisor setting by the line rate
> + * @ssp: pointer to a struct sifive_serial_port
> + *
> + * Calculate the appropriate value of the clock divisor for the UART
> + * and target line rate referred to by @ssp and write it into the
> + * hardware.
> + */
> +static void __ssp_update_div(struct sifive_serial_port *ssp)
> +{
> +	u16 div;
> +
> +	div = DIV_ROUND_UP(ssp->clkin_rate, ssp->baud_rate) - 1;
> +
> +	__ssp_writel(div, SIFIVE_SERIAL_DIV_OFFS, ssp);
> +}
> +
> +/**
> + * __ssp_update_baud_rate() - set the UART "baud rate"
> + * @ssp: pointer to a struct sifive_serial_port
> + * @rate: new target bit rate
> + *
> + * Calculate the UART divisor value for the target bit rate @rate for the
> + * SiFive UART described by @ssp and program it into the UART.  There may
> + * be some error between the target bit rate and the actual bit rate implemented
> + * by the UART due to clock ratio granularity.
> + */
> +static void __ssp_update_baud_rate(struct sifive_serial_port *ssp,
> +				   unsigned int rate)
> +{
> +	if (ssp->baud_rate == rate)
> +		return;
> +
> +	ssp->baud_rate = rate;
> +	__ssp_update_div(ssp);
> +}
> +
> +/**
> + * __ssp_set_stop_bits() - set the number of stop bits
> + * @ssp: pointer to a struct sifive_serial_port
> + * @nstop: 1 or 2 (stop bits)
> + *
> + * Program the SiFive UART referred to by @ssp to use @nstop stop bits.
> + */
> +static void __ssp_set_stop_bits(struct sifive_serial_port *ssp, char nstop)
> +{
> +	u32 v;
> +
> +	if (nstop < 1 || nstop > 2) {
> +		WARN_ON(1);
> +		return;
> +	}
> +
> +	v = __ssp_readl(ssp, SIFIVE_SERIAL_TXCTRL_OFFS);
> +	v &= ~SIFIVE_SERIAL_TXCTRL_NSTOP_MASK;
> +	v |= (nstop - 1) << SIFIVE_SERIAL_TXCTRL_NSTOP_SHIFT;
> +	__ssp_writel(v, SIFIVE_SERIAL_TXCTRL_OFFS, ssp);
> +}
> +
> +/**
> + * __ssp_wait_for_xmitr() - wait for an empty slot on the TX FIFO
> + * @ssp: pointer to a struct sifive_serial_port
> + *
> + * Delay while the UART TX FIFO referred to by @ssp is marked as full.
> + *
> + * Context: Any context.
> + */
> +static void __maybe_unused __ssp_wait_for_xmitr(struct sifive_serial_port *ssp)
> +{
> +	while (sifive_serial_is_txfifo_full(ssp))
> +		udelay(1); /* XXX Could probably be more intelligent here */
> +}
> +
> +/*
> + * Linux serial API functions
> + */
> +
> +static void sifive_serial_stop_tx(struct uart_port *port)
> +{
> +	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
> +
> +	__ssp_disable_txwm(ssp);
> +}
> +
> +static void sifive_serial_stop_rx(struct uart_port *port)
> +{
> +	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
> +
> +	__ssp_disable_rxwm(ssp);
> +}
> +
> +static void sifive_serial_start_tx(struct uart_port *port)
> +{
> +	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
> +
> +	__ssp_enable_txwm(ssp);
> +}
> +
> +static irqreturn_t sifive_serial_irq(int irq, void *dev_id)
> +{
> +	struct sifive_serial_port *ssp = dev_id;
> +	u32 ip;
> +
> +	spin_lock(&ssp->port.lock);
> +
> +	ip = __ssp_readl(ssp, SIFIVE_SERIAL_IP_OFFS);
> +	if (!ip) {
> +		spin_unlock(&ssp->port.lock);
> +		return IRQ_NONE;
> +	}
> +
> +	if (ip & SIFIVE_SERIAL_IP_RXWM_MASK)
> +		__ssp_receive_chars(ssp);
> +	if (ip & SIFIVE_SERIAL_IP_TXWM_MASK)
> +		__ssp_transmit_chars(ssp);
> +
> +	spin_unlock(&ssp->port.lock);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static unsigned int sifive_serial_tx_empty(struct uart_port *port)
> +{
> +	return TIOCSER_TEMT;
> +}
> +
> +static unsigned int sifive_serial_get_mctrl(struct uart_port *port)
> +{
> +	return TIOCM_CAR | TIOCM_CTS | TIOCM_DSR;
> +}
> +
> +static void sifive_serial_set_mctrl(struct uart_port *port, unsigned int mctrl)
> +{
> +	/* IP block does not support these signals */
> +}
> +
> +static void sifive_serial_break_ctl(struct uart_port *port, int break_state)
> +{
> +	/* IP block does not support sending a break */
> +}
> +
> +static int sifive_serial_startup(struct uart_port *port)
> +{
> +	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
> +
> +	__ssp_enable_rxwm(ssp);
> +
> +	return 0;
> +}
> +
> +static void sifive_serial_shutdown(struct uart_port *port)
> +{
> +	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
> +
> +	__ssp_disable_rxwm(ssp);
> +	__ssp_disable_txwm(ssp);
> +}
> +
> +/**
> + * sifive_serial_clk_notifier() - clock post-rate-change notifier
> + * @nb: pointer to the struct notifier_block, from the notifier code
> + * @event: event mask from the notifier code
> + * @data: pointer to the struct clk_notifier_data from the notifier code
> + *
> + * On the V0 SoC, the UART IP block is derived from the CPU clock source
> + * after a synchronous divide-by-two divider, so any CPU clock rate change
> + * requires the UART baud rate to be updated.  This presumably could corrupt any
> + * serial word currently being transmitted or received.  It would probably
> + * be better to stop receives and transmits, then complete the baud rate
> + * change, then re-enable them.
> + */
> +static int sifive_serial_clk_notifier(struct notifier_block *nb,
> +				      unsigned long event, void *data)
> +{
> +	struct clk_notifier_data *cnd = data;
> +	struct sifive_serial_port *ssp = notifier_to_sifive_serial_port(nb);
> +
> +	if (event == POST_RATE_CHANGE && ssp->clkin_rate != cnd->new_rate) {
> +		ssp->clkin_rate = cnd->new_rate;
> +		__ssp_update_div(ssp);
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +static void sifive_serial_set_termios(struct uart_port *port,
> +				      struct ktermios *termios,
> +				      struct ktermios *old)
> +{
> +	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
> +	unsigned long flags;
> +	u32 v, old_v;
> +	int rate;
> +	char nstop;
> +
> +	if ((termios->c_cflag & CSIZE) != CS8) {
> +		dev_err(ssp->port.dev, "only 8-bit words supported\n");
> +		return;
> +	}
> +
> +	/* Set number of stop bits */
> +	nstop = (termios->c_cflag & CSTOPB) ? 2 : 1;
> +	__ssp_set_stop_bits(ssp, nstop);
> +
> +	/* Set line rate */
> +	rate = uart_get_baud_rate(port, termios, old, 0, ssp->clkin_rate / 16);
> +	__ssp_update_baud_rate(ssp, rate);
> +
> +	spin_lock_irqsave(&ssp->port.lock, flags);
> +
> +	/*
> +	 * Update the per-port timeout.
> +	 */
> +	uart_update_timeout(port, termios->c_cflag, rate);
> +
> +	ssp->port.read_status_mask = 0;
> +	if (termios->c_iflag & INPCK) {
> +		dev_err(ssp->port.dev, "INPCK flag not supported\n");
> +		goto ssst_out;
> +	}
> +	if (termios->c_iflag & (BRKINT | PARMRK)) {
> +		dev_err(ssp->port.dev, "BRKINT/PARMRK flag not supported\n");
> +		goto ssst_out;
> +	}
> +
> +	/*
> +	 * ignore all characters if CREAD is not set
> +	 */
> +	v = __ssp_readl(ssp, SIFIVE_SERIAL_RXCTRL_OFFS);
> +	old_v = v;
> +	if ((termios->c_cflag & CREAD) == 0)
> +		v &= SIFIVE_SERIAL_RXCTRL_RXEN_MASK;
> +	else
> +		v |= SIFIVE_SERIAL_RXCTRL_RXEN_MASK;
> +	if (v != old_v)
> +		__ssp_writel(v, SIFIVE_SERIAL_RXCTRL_OFFS, ssp);
> +
> +ssst_out:
> +	spin_unlock_irqrestore(&ssp->port.lock, flags);
> +}
> +
> +static void sifive_serial_release_port(struct uart_port *port)
> +{
> +}
> +
> +static int sifive_serial_request_port(struct uart_port *port)
> +{
> +	return 0;
> +}
> +
> +static void sifive_serial_config_port(struct uart_port *port, int flags)
> +{
> +	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
> +
> +	ssp->port.type = PORT_SIFIVE_V0;
> +}
> +
> +static int sifive_serial_verify_port(struct uart_port *port,
> +				     struct serial_struct *ser)
> +{
> +	return -EINVAL;
> +}
> +
> +static const char *sifive_serial_type(struct uart_port *port)
> +{
> +	return port->type == PORT_SIFIVE_V0 ? "SiFive UART v0" : NULL;
> +}
> +
> +/*
> + * Early console support
> + */
> +
> +#ifdef CONFIG_SERIAL_EARLYCON
> +static void early_sifive_serial_putc(struct uart_port *port, int c)
> +{
> +	while (__ssp_early_readl(port, SIFIVE_SERIAL_TXDATA_OFFS) &
> +	       SIFIVE_SERIAL_TXDATA_FULL_MASK)
> +		cpu_relax();
> +
> +	__ssp_early_writel(c, SIFIVE_SERIAL_TXDATA_OFFS, port);
> +}
> +
> +static void early_sifive_serial_write(struct console *con, const char *s,
> +				      unsigned int n)
> +{
> +	struct earlycon_device *dev = con->data;
> +	struct uart_port *port = &dev->port;
> +
> +	uart_console_write(port, s, n, early_sifive_serial_putc);
> +}
> +
> +static int __init early_sifive_serial_setup(struct earlycon_device *dev,
> +					    const char *options)
> +{
> +	struct uart_port *port = &dev->port;
> +
> +	if (!port->membase)
> +		return -ENODEV;
> +
> +	dev->con->write = early_sifive_serial_write;
> +
> +	return 0;
> +}
> +
> +OF_EARLYCON_DECLARE(sifive, "sifive,uart0", early_sifive_serial_setup);
> +OF_EARLYCON_DECLARE(sifive, "sifive,fu540-c000-uart0",
> +		    early_sifive_serial_setup);
> +#endif /* CONFIG_SERIAL_EARLYCON */
> +
> +/*
> + * Linux console interface
> + */
> +
> +#ifdef CONFIG_SERIAL_SIFIVE_CONSOLE
> +
> +static struct sifive_serial_port *sifive_serial_console_ports[SIFIVE_SERIAL_MAX_PORTS];
> +
> +static void sifive_serial_console_putchar(struct uart_port *port, int ch)
> +{
> +	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
> +
> +	__ssp_wait_for_xmitr(ssp);
> +	__ssp_transmit_char(ssp, ch);
> +}
> +
> +static void sifive_serial_console_write(struct console *co, const char *s,
> +					unsigned int count)
> +{
> +	struct sifive_serial_port *ssp = sifive_serial_console_ports[co->index];
> +	unsigned long flags;
> +	unsigned int ier;
> +	int locked = 1;
> +
> +	if (!ssp)
> +		return;
> +
> +	local_irq_save(flags);
> +	if (ssp->port.sysrq)
> +		locked = 0;
> +	else if (oops_in_progress)
> +		locked = spin_trylock(&ssp->port.lock);
> +	else
> +		spin_lock(&ssp->port.lock);
> +
> +	ier = __ssp_readl(ssp, SIFIVE_SERIAL_IE_OFFS);
> +	__ssp_writel(0, SIFIVE_SERIAL_IE_OFFS, ssp);
> +
> +	uart_console_write(&ssp->port, s, count, sifive_serial_console_putchar);
> +
> +	__ssp_writel(ier, SIFIVE_SERIAL_IE_OFFS, ssp);
> +
> +	if (locked)
> +		spin_unlock(&ssp->port.lock);
> +	local_irq_restore(flags);
> +}
> +
> +static int __init sifive_serial_console_setup(struct console *co, char *options)
> +{
> +	struct sifive_serial_port *ssp;
> +	int baud = SIFIVE_DEFAULT_BAUD_RATE;
> +	int bits = 8;
> +	int parity = 'n';
> +	int flow = 'n';
> +
> +	if (co->index < 0 || co->index >= SIFIVE_SERIAL_MAX_PORTS)
> +		return -ENODEV;
> +
> +	ssp = sifive_serial_console_ports[co->index];
> +	if (!ssp)
> +		return -ENODEV;
> +
> +	if (options)
> +		uart_parse_options(options, &baud, &parity, &bits, &flow);
> +
> +	return uart_set_options(&ssp->port, co, baud, parity, bits, flow);
> +}
> +
> +static struct uart_driver sifive_serial_uart_driver;
> +
> +static struct console sifive_serial_console = {
> +	.name		= SIFIVE_TTY_PREFIX,
> +	.write		= sifive_serial_console_write,
> +	.device		= uart_console_device,
> +	.setup		= sifive_serial_console_setup,
> +	.flags		= CON_PRINTBUFFER,
> +	.index		= -1,
> +	.data		= &sifive_serial_uart_driver,
> +};
> +
> +static int __init sifive_console_init(void)
> +{
> +	register_console(&sifive_serial_console);
> +	return 0;
> +}
> +
> +console_initcall(sifive_console_init);
> +
> +static void __ssp_add_console_port(struct sifive_serial_port *ssp)
> +{
> +	sifive_serial_console_ports[ssp->port.line] = ssp;
> +}
> +
> +static void __ssp_remove_console_port(struct sifive_serial_port *ssp)
> +{
> +	sifive_serial_console_ports[ssp->port.line] = 0;
> +}
> +
> +#define SIFIVE_SERIAL_CONSOLE	(&sifive_serial_console)
> +
> +#else
> +
> +#define SIFIVE_SERIAL_CONSOLE	NULL
> +
> +static void __ssp_add_console_port(struct sifive_serial_port *ssp)
> +{}
> +static void __ssp_remove_console_port(struct sifive_serial_port *ssp)
> +{}
> +
> +#endif
> +
> +static const struct uart_ops sifive_serial_uops = {
> +	.tx_empty	= sifive_serial_tx_empty,
> +	.set_mctrl	= sifive_serial_set_mctrl,
> +	.get_mctrl	= sifive_serial_get_mctrl,
> +	.stop_tx	= sifive_serial_stop_tx,
> +	.start_tx	= sifive_serial_start_tx,
> +	.stop_rx	= sifive_serial_stop_rx,
> +	.break_ctl	= sifive_serial_break_ctl,
> +	.startup	= sifive_serial_startup,
> +	.shutdown	= sifive_serial_shutdown,
> +	.set_termios	= sifive_serial_set_termios,
> +	.type		= sifive_serial_type,
> +	.release_port	= sifive_serial_release_port,
> +	.request_port	= sifive_serial_request_port,
> +	.config_port	= sifive_serial_config_port,
> +	.verify_port	= sifive_serial_verify_port,
> +};
> +
> +static struct uart_driver sifive_serial_uart_driver = {
> +	.owner		= THIS_MODULE,
> +	.driver_name	= SIFIVE_SERIAL_NAME,
> +	.dev_name	= SIFIVE_TTY_PREFIX,
> +	.nr		= SIFIVE_SERIAL_MAX_PORTS,
> +	.cons		= SIFIVE_SERIAL_CONSOLE,
> +};
> +
> +static int sifive_serial_probe(struct platform_device *pdev)
> +{
> +	struct sifive_serial_port *ssp;
> +	struct resource *mem;
> +	struct clk *clk;
> +	void __iomem *base;
> +	int irq, id, r;
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "could not acquire interrupt\n");
> +		return -EPROBE_DEFER;
> +	}
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	base = devm_ioremap_resource(&pdev->dev, mem);
> +	if (IS_ERR(base)) {
> +		dev_err(&pdev->dev, "could not acquire device memory\n");
> +		return PTR_ERR(base);
> +	}
> +
> +	clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(clk)) {
> +		dev_err(&pdev->dev, "unable to find controller clock\n");
> +		return PTR_ERR(clk);
> +	}
> +
> +	id = of_alias_get_id(pdev->dev.of_node, "serial");
> +	if (id < 0) {
> +		dev_err(&pdev->dev, "missing aliases entry\n");
> +		return id;
> +	}
> +
> +#ifdef CONFIG_SERIAL_SIFIVE_CONSOLE
> +	if (id > SIFIVE_SERIAL_MAX_PORTS) {
> +		dev_err(&pdev->dev, "too many UARTs (%d)\n", id);
> +		return -EINVAL;
> +	}
> +#endif
> +
> +	ssp = devm_kzalloc(&pdev->dev, sizeof(*ssp), GFP_KERNEL);
> +	if (!ssp)
> +		return -ENOMEM;
> +
> +	ssp->port.dev = &pdev->dev;
> +	ssp->port.type = PORT_SIFIVE_V0;
> +	ssp->port.iotype = UPIO_MEM;
> +	ssp->port.irq = irq;
> +	ssp->port.fifosize = SIFIVE_TX_FIFO_DEPTH;
> +	ssp->port.ops = &sifive_serial_uops;
> +	ssp->port.line = id;
> +	ssp->port.mapbase = mem->start;
> +	ssp->port.membase = base;
> +	ssp->dev = &pdev->dev;
> +	ssp->clk = clk;
> +	ssp->clk_notifier.notifier_call = sifive_serial_clk_notifier;
> +
> +	r = clk_notifier_register(ssp->clk, &ssp->clk_notifier);
> +	if (r) {
> +		dev_err(&pdev->dev, "could not register clock notifier: %d\n",
> +			r);
> +		goto probe_out1;
> +	}
> +
> +	/* Set up clock divider */
> +	ssp->clkin_rate = clk_get_rate(ssp->clk);
> +	ssp->baud_rate = SIFIVE_DEFAULT_BAUD_RATE;
> +	__ssp_update_div(ssp);
> +
> +	platform_set_drvdata(pdev, ssp);
> +
> +	/* Enable transmits and set the watermark level to 1 */
> +	__ssp_writel((1 << SIFIVE_SERIAL_TXCTRL_TXCNT_SHIFT) |
> +		     SIFIVE_SERIAL_TXCTRL_TXEN_MASK,
> +		     SIFIVE_SERIAL_TXCTRL_OFFS, ssp);
> +
> +	/* Enable receives and set the watermark level to 0 */
> +	__ssp_writel((0 << SIFIVE_SERIAL_RXCTRL_RXCNT_SHIFT) |
> +		     SIFIVE_SERIAL_RXCTRL_RXEN_MASK,
> +		     SIFIVE_SERIAL_RXCTRL_OFFS, ssp);
> +
> +	r = request_irq(ssp->port.irq, sifive_serial_irq, ssp->port.irqflags,
> +			dev_name(&pdev->dev), ssp);
> +	if (r) {
> +		dev_err(&pdev->dev, "could not attach interrupt: %d\n", r);
> +		goto probe_out2;
> +	}
> +
> +	__ssp_add_console_port(ssp);
> +
> +	r = uart_add_one_port(&sifive_serial_uart_driver, &ssp->port);
> +	if (r != 0) {
> +		dev_err(&pdev->dev, "could not add uart: %d\n", r);
> +		goto probe_out3;
> +	}
> +
> +	return 0;
> +
> +probe_out3:
> +	__ssp_remove_console_port(ssp);
> +	free_irq(ssp->port.irq, ssp);
> +probe_out2:
> +	clk_notifier_unregister(ssp->clk, &ssp->clk_notifier);
> +probe_out1:
> +	return r;
> +}
> +
> +static int sifive_serial_remove(struct platform_device *dev)
> +{
> +	struct sifive_serial_port *ssp = platform_get_drvdata(dev);
> +
> +	__ssp_remove_console_port(ssp);
> +	uart_remove_one_port(&sifive_serial_uart_driver, &ssp->port);
> +	free_irq(ssp->port.irq, ssp);
> +	clk_notifier_unregister(ssp->clk, &ssp->clk_notifier);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id sifive_serial_of_match[] = {
> +	{ .compatible = "sifive,fu540-c000-uart0" },
> +	{ .compatible = "sifive,uart0" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, sifive_serial_of_match);
> +
> +static struct platform_driver sifive_serial_platform_driver = {
> +	.probe		= sifive_serial_probe,
> +	.remove		= sifive_serial_remove,
> +	.driver		= {
> +		.name	= SIFIVE_SERIAL_NAME,
> +		.of_match_table = of_match_ptr(sifive_serial_of_match),
> +	},
> +};
> +
> +static int __init sifive_serial_init(void)
> +{
> +	int r;
> +
> +	r = uart_register_driver(&sifive_serial_uart_driver);
> +	if (r)
> +		goto init_out1;
> +
> +	r = platform_driver_register(&sifive_serial_platform_driver);
> +	if (r)
> +		goto init_out2;
> +
> +	return 0;
> +
> +init_out2:
> +	uart_unregister_driver(&sifive_serial_uart_driver);
> +init_out1:
> +	return r;
> +}
> +
> +static void __exit sifive_serial_exit(void)
> +{
> +	platform_driver_unregister(&sifive_serial_platform_driver);
> +	uart_unregister_driver(&sifive_serial_uart_driver);
> +}
> +
> +module_init(sifive_serial_init);
> +module_exit(sifive_serial_exit);
> +
> +MODULE_DESCRIPTION("SiFive UART serial driver");
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Paul Walmsley <paul@pwsan.com>");
> diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
> index 6009ee2c2e99..2191fa691770 100644
> --- a/include/uapi/linux/serial_core.h
> +++ b/include/uapi/linux/serial_core.h
> @@ -287,4 +287,7 @@
>   /* RDA UART */
>   #define PORT_RDA	118
>   
> +/* SiFive UART */
> +#define PORT_SIFIVE_V0	119
> +
>   #endif /* _UAPILINUX_SERIAL_CORE_H */
> 

^ permalink raw reply

* Re: [PATCH v4 2/2] tty: serial: add driver for the SiFive UART
From: Paul Walmsley @ 2019-04-11 21:13 UTC (permalink / raw)
  To: Andreas Schwab
  Cc: Paul Walmsley, linux-kernel, linux-riscv, linux-serial, gregkh,
	Paul Walmsley, Jiri Slaby, Palmer Dabbelt, Wesley Terpstra,
	Julia Lawall
In-Reply-To: <mvmtvf4ecvq.fsf@suse.de>

On Thu, 11 Apr 2019, Andreas Schwab wrote:

> On Apr 11 2019, Paul Walmsley <paul.walmsley@sifive.com> wrote:
> 
> > +static void sifive_serial_set_termios(struct uart_port *port,
> > +				      struct ktermios *termios,
> > +				      struct ktermios *old)
> > +{
> > +	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
> > +	unsigned long flags;
> > +	u32 v, old_v;
> > +	int rate;
> > +	char nstop;
> > +
> > +	if ((termios->c_cflag & CSIZE) != CS8) {
> > +		dev_err(ssp->port.dev, "only 8-bit words supported\n");
> > +		return;
> > +	}
> > +
> > +	/* Set number of stop bits */
> > +	nstop = (termios->c_cflag & CSTOPB) ? 2 : 1;
> > +	__ssp_set_stop_bits(ssp, nstop);
> > +
> > +	/* Set line rate */
> > +	rate = uart_get_baud_rate(port, termios, old, 0, ssp->clkin_rate / 16);
> > +	__ssp_update_baud_rate(ssp, rate);
> > +
> > +	spin_lock_irqsave(&ssp->port.lock, flags);
> > +
> > +	/*
> > +	 * Update the per-port timeout.
> > +	 */
> > +	uart_update_timeout(port, termios->c_cflag, rate);
> > +
> > +	ssp->port.read_status_mask = 0;
> > +	if (termios->c_iflag & INPCK) {
> > +		dev_err(ssp->port.dev, "INPCK flag not supported\n");
> > +		goto ssst_out;
> > +	}
> > +	if (termios->c_iflag & (BRKINT | PARMRK)) {
> > +		dev_err(ssp->port.dev, "BRKINT/PARMRK flag not supported\n");
> > +		goto ssst_out;
> > +	}
> 
> I don't think it is a good idea to print something while the port is
> locked.
> 
> And I think set_termios is supposed to apply all applicable settings,
> even if some of them are not supported.

Agreed.  Thank you for your help debugging (some of which was off-list).
Clearly there should have been a build-test with lockdep enabled. 

I will send a v5.


- Paul

^ permalink raw reply

* Re: [PATCH v4 2/2] tty: serial: add driver for the SiFive UART
From: Andreas Schwab @ 2019-04-11 16:16 UTC (permalink / raw)
  To: Paul Walmsley
  Cc: linux-kernel, linux-riscv, linux-serial, gregkh, Paul Walmsley,
	Jiri Slaby, Palmer Dabbelt, Wesley Terpstra, Julia Lawall
In-Reply-To: <20190411090027.8670-3-paul.walmsley@sifive.com>

On Apr 11 2019, Paul Walmsley <paul.walmsley@sifive.com> wrote:

> +static void sifive_serial_set_termios(struct uart_port *port,
> +				      struct ktermios *termios,
> +				      struct ktermios *old)
> +{
> +	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
> +	unsigned long flags;
> +	u32 v, old_v;
> +	int rate;
> +	char nstop;
> +
> +	if ((termios->c_cflag & CSIZE) != CS8) {
> +		dev_err(ssp->port.dev, "only 8-bit words supported\n");
> +		return;
> +	}
> +
> +	/* Set number of stop bits */
> +	nstop = (termios->c_cflag & CSTOPB) ? 2 : 1;
> +	__ssp_set_stop_bits(ssp, nstop);
> +
> +	/* Set line rate */
> +	rate = uart_get_baud_rate(port, termios, old, 0, ssp->clkin_rate / 16);
> +	__ssp_update_baud_rate(ssp, rate);
> +
> +	spin_lock_irqsave(&ssp->port.lock, flags);
> +
> +	/*
> +	 * Update the per-port timeout.
> +	 */
> +	uart_update_timeout(port, termios->c_cflag, rate);
> +
> +	ssp->port.read_status_mask = 0;
> +	if (termios->c_iflag & INPCK) {
> +		dev_err(ssp->port.dev, "INPCK flag not supported\n");
> +		goto ssst_out;
> +	}
> +	if (termios->c_iflag & (BRKINT | PARMRK)) {
> +		dev_err(ssp->port.dev, "BRKINT/PARMRK flag not supported\n");
> +		goto ssst_out;
> +	}

I don't think it is a good idea to print something while the port is
locked.

And I think set_termios is supposed to apply all applicable settings,
even if some of them are not supported.

Andreas.

-- 
Andreas Schwab, SUSE Labs, schwab@suse.de
GPG Key fingerprint = 0196 BAD8 1CE9 1970 F4BE  1748 E4D4 88E3 0EEA B9D7
"And now for something completely different."

^ permalink raw reply

* Re: [PATCH v3 0/2] tty: serial: add DT bindings and serial driver for the SiFive FU540 UART
From: Andreas Schwab @ 2019-04-11 14:45 UTC (permalink / raw)
  To: Paul Walmsley
  Cc: linux-serial, Greg Kroah-Hartman, Jiri Slaby, Palmer Dabbelt,
	Wesley Terpstra, linux-riscv, linux-kernel, devicetree
In-Reply-To: <alpine.DEB.2.21.9999.1904110151030.5868@viisi.sifive.com>

On Apr 11 2019, Paul Walmsley <paul.walmsley@sifive.com> wrote:

> On Wed, 6 Mar 2019, Andreas Schwab wrote:
>
>> Trying to log in on the serial console causes the system to freeze.  The
>> last message is:
>> 
>> [  115.597858] sifive-serial 10010000.serial: BRKINT/PARMRK flag not supported
>
> Have not seen that problem.

# stty sane -F /dev/ttySIF0
<crash>

Andreas.

-- 
Andreas Schwab, SUSE Labs, schwab@suse.de
GPG Key fingerprint = 0196 BAD8 1CE9 1970 F4BE  1748 E4D4 88E3 0EEA B9D7
"And now for something completely different."

^ permalink raw reply

* [PATCH v4 2/2] tty: serial: add driver for the SiFive UART
From: Paul Walmsley @ 2019-04-11  9:00 UTC (permalink / raw)
  To: linux-kernel, linux-riscv, linux-serial, gregkh
  Cc: Paul Walmsley, Paul Walmsley, Jiri Slaby, Palmer Dabbelt,
	Wesley Terpstra, Julia Lawall
In-Reply-To: <20190411090027.8670-1-paul.walmsley@sifive.com>

Add a serial driver for the SiFive UART, found on SiFive FU540 devices
(among others).

The underlying serial IP block is relatively basic, and currently does
not support serial break detection.  Further information on the IP
block can be found in the documentation and Chisel sources:

    https://static.dev.sifive.com/FU540-C000-v1.0.pdf

    https://github.com/sifive/sifive-blocks/tree/master/src/main/scala/devices/uart

This driver was written in collaboration with Wesley Terpstra
<wesley@sifive.com>.

Tested on a SiFive HiFive Unleashed A00 board, using BBL and the open-
source FSBL (using a DT file based on what's targeted for mainline).

This revision incorporates changes based on comments by Emil Renner
Berthing <kernel@esmil.dk>.

Signed-off-by: Paul Walmsley <paul.walmsley@sifive.com>
Signed-off-by: Paul Walmsley <paul@pwsan.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jslaby@suse.com>
Cc: Palmer Dabbelt <palmer@sifive.com>
Cc: Wesley Terpstra <wesley@sifive.com>
Cc: linux-serial@vger.kernel.org
Cc: linux-riscv@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: Julia Lawall <julia.lawall@lip6.fr>
---
 drivers/tty/serial/Kconfig       |   24 +
 drivers/tty/serial/Makefile      |    1 +
 drivers/tty/serial/sifive.c      | 1067 ++++++++++++++++++++++++++++++
 include/uapi/linux/serial_core.h |    3 +
 4 files changed, 1095 insertions(+)
 create mode 100644 drivers/tty/serial/sifive.c

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 72966bc0ac76..561e053b690a 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1095,6 +1095,30 @@ config SERIAL_OMAP_CONSOLE
 	  your boot loader about how to pass options to the kernel at
 	  boot time.)
 
+config SERIAL_SIFIVE
+	tristate "SiFive UART support"
+	depends on OF
+	select SERIAL_CORE
+	help
+	  Select this option if you are building a kernel for a device that
+	  contains a SiFive UART IP block.  This type of UART is present on
+	  SiFive FU540 SoCs, among others.
+
+config SERIAL_SIFIVE_CONSOLE
+	bool "Console on SiFive UART"
+	depends on SERIAL_SIFIVE=y
+	select SERIAL_CORE_CONSOLE
+	help
+	  Select this option if you would like to use a SiFive UART as the
+	  system console.
+
+	  Even if you say Y here, the currently visible virtual console
+	  (/dev/tty0) will still be used as the system console by default, but
+	  you can alter that using a kernel command line option such as
+	  "console=ttySIFx". (Try "man bootparam" or see the documentation of
+	  your boot loader about how to pass options to the kernel at
+	  boot time.)
+
 config SERIAL_LANTIQ
 	bool "Lantiq serial driver"
 	depends on LANTIQ
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 40b702aaa85e..2aff1d07d08b 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_SERIAL_PIC32)	+= pic32_uart.o
 obj-$(CONFIG_SERIAL_MPS2_UART)	+= mps2-uart.o
 obj-$(CONFIG_SERIAL_OWL)	+= owl-uart.o
 obj-$(CONFIG_SERIAL_RDA)	+= rda-uart.o
+obj-$(CONFIG_SERIAL_SIFIVE)	+= sifive.o
 
 # GPIOLIB helpers for modem control lines
 obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c
new file mode 100644
index 000000000000..7401cfed0f48
--- /dev/null
+++ b/drivers/tty/serial/sifive.c
@@ -0,0 +1,1067 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * SiFive UART driver
+ * Copyright (C) 2018 Paul Walmsley <paul@pwsan.com>
+ * Copyright (C) 2018 SiFive
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Based partially on:
+ * - drivers/tty/serial/pxa.c
+ * - drivers/tty/serial/amba-pl011.c
+ * - drivers/tty/serial/uartlite.c
+ * - drivers/tty/serial/omap-serial.c
+ * - drivers/pwm/pwm-sifive.c
+ *
+ * See the following sources for further documentation:
+ * - Chapter 19 "Universal Asynchronous Receiver/Transmitter (UART)" of
+ *   SiFive FE310-G000 v2p3
+ * - The tree/master/src/main/scala/devices/uart directory of
+ *   https://github.com/sifive/sifive-blocks/
+ *
+ * The SiFive UART design is not 8250-compatible.  The following common
+ * features are not supported:
+ * - Word lengths other than 8 bits
+ * - Break handling
+ * - Parity
+ * - Flow control
+ * - Modem signals (DSR, RI, etc.)
+ * On the other hand, the design is free from the baggage of the 8250
+ * programming model.
+ */
+
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/serial_core.h>
+#include <linux/serial_reg.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+
+/*
+ * Register offsets
+ */
+
+/* TXDATA */
+#define SIFIVE_SERIAL_TXDATA_OFFS		0x0
+#define SIFIVE_SERIAL_TXDATA_FULL_SHIFT		31
+#define SIFIVE_SERIAL_TXDATA_FULL_MASK		(1 << SIFIVE_SERIAL_TXDATA_FULL_SHIFT)
+#define SIFIVE_SERIAL_TXDATA_DATA_SHIFT		0
+#define SIFIVE_SERIAL_TXDATA_DATA_MASK		(0xff << SIFIVE_SERIAL_TXDATA_DATA_SHIFT)
+
+/* RXDATA */
+#define SIFIVE_SERIAL_RXDATA_OFFS		0x4
+#define SIFIVE_SERIAL_RXDATA_EMPTY_SHIFT	31
+#define SIFIVE_SERIAL_RXDATA_EMPTY_MASK		(1 << SIFIVE_SERIAL_RXDATA_EMPTY_SHIFT)
+#define SIFIVE_SERIAL_RXDATA_DATA_SHIFT		0
+#define SIFIVE_SERIAL_RXDATA_DATA_MASK		(0xff << SIFIVE_SERIAL_RXDATA_DATA_SHIFT)
+
+/* TXCTRL */
+#define SIFIVE_SERIAL_TXCTRL_OFFS		0x8
+#define SIFIVE_SERIAL_TXCTRL_TXCNT_SHIFT	16
+#define SIFIVE_SERIAL_TXCTRL_TXCNT_MASK		(0x7 << SIFIVE_SERIAL_TXCTRL_TXCNT_SHIFT)
+#define SIFIVE_SERIAL_TXCTRL_NSTOP_SHIFT	1
+#define SIFIVE_SERIAL_TXCTRL_NSTOP_MASK		(1 << SIFIVE_SERIAL_TXCTRL_NSTOP_SHIFT)
+#define SIFIVE_SERIAL_TXCTRL_TXEN_SHIFT		0
+#define SIFIVE_SERIAL_TXCTRL_TXEN_MASK		(1 << SIFIVE_SERIAL_TXCTRL_TXEN_SHIFT)
+
+/* RXCTRL */
+#define SIFIVE_SERIAL_RXCTRL_OFFS		0xC
+#define SIFIVE_SERIAL_RXCTRL_RXCNT_SHIFT	16
+#define SIFIVE_SERIAL_RXCTRL_RXCNT_MASK		(0x7 << SIFIVE_SERIAL_TXCTRL_TXCNT_SHIFT)
+#define SIFIVE_SERIAL_RXCTRL_RXEN_SHIFT		0
+#define SIFIVE_SERIAL_RXCTRL_RXEN_MASK		(1 << SIFIVE_SERIAL_RXCTRL_RXEN_SHIFT)
+
+/* IE */
+#define SIFIVE_SERIAL_IE_OFFS			0x10
+#define SIFIVE_SERIAL_IE_RXWM_SHIFT		1
+#define SIFIVE_SERIAL_IE_RXWM_MASK		(1 << SIFIVE_SERIAL_IE_RXWM_SHIFT)
+#define SIFIVE_SERIAL_IE_TXWM_SHIFT		0
+#define SIFIVE_SERIAL_IE_TXWM_MASK		(1 << SIFIVE_SERIAL_IE_TXWM_SHIFT)
+
+/* IP */
+#define SIFIVE_SERIAL_IP_OFFS			0x14
+#define SIFIVE_SERIAL_IP_RXWM_SHIFT		1
+#define SIFIVE_SERIAL_IP_RXWM_MASK		(1 << SIFIVE_SERIAL_IP_RXWM_SHIFT)
+#define SIFIVE_SERIAL_IP_TXWM_SHIFT		0
+#define SIFIVE_SERIAL_IP_TXWM_MASK		(1 << SIFIVE_SERIAL_IP_TXWM_SHIFT)
+
+/* DIV */
+#define SIFIVE_SERIAL_DIV_OFFS			0x18
+#define SIFIVE_SERIAL_DIV_DIV_SHIFT		0
+#define SIFIVE_SERIAL_DIV_DIV_MASK		(0xffff << SIFIVE_SERIAL_IP_DIV_SHIFT)
+
+/*
+ * Config macros
+ */
+
+/*
+ * SIFIVE_SERIAL_MAX_PORTS: maximum number of UARTs on a device that can
+ *                          host a serial console
+ */
+#define SIFIVE_SERIAL_MAX_PORTS			8
+
+/*
+ * SIFIVE_DEFAULT_BAUD_RATE: default baud rate that the driver should
+ *                           configure itself to use
+ */
+#define SIFIVE_DEFAULT_BAUD_RATE		115200
+
+/* SIFIVE_SERIAL_NAME: our driver's name that we pass to the operating system */
+#define SIFIVE_SERIAL_NAME			"sifive-serial"
+
+/* SIFIVE_TTY_PREFIX: tty name prefix for SiFive serial ports */
+#define SIFIVE_TTY_PREFIX			"ttySIF"
+
+/* SIFIVE_TX_FIFO_DEPTH: depth of the TX FIFO (in bytes) */
+#define SIFIVE_TX_FIFO_DEPTH			8
+
+/* SIFIVE_RX_FIFO_DEPTH: depth of the TX FIFO (in bytes) */
+#define SIFIVE_RX_FIFO_DEPTH			8
+
+#if (SIFIVE_TX_FIFO_DEPTH != SIFIVE_RX_FIFO_DEPTH)
+#error Driver does not support configurations with different TX, RX FIFO sizes
+#endif
+
+/*
+ *
+ */
+
+/**
+ * sifive_serial_port - driver-specific data extension to struct uart_port
+ * @port: struct uart_port embedded in this struct
+ * @dev: struct device *
+ * @ier: shadowed copy of the interrupt enable register
+ * @clkin_rate: input clock to the UART IP block.
+ * @baud_rate: UART serial line rate (e.g., 115200 baud)
+ * @clk_notifier: clock rate change notifier for upstream clock changes
+ *
+ * Configuration data specific to this SiFive UART.
+ */
+struct sifive_serial_port {
+	struct uart_port	port;
+	struct device		*dev;
+	unsigned char		ier;
+	unsigned long		clkin_rate;
+	unsigned long		baud_rate;
+	struct clk		*clk;
+	struct notifier_block	clk_notifier;
+};
+
+/*
+ * Structure container-of macros
+ */
+
+#define port_to_sifive_serial_port(p) (container_of((p), \
+						    struct sifive_serial_port, \
+						    port))
+
+#define notifier_to_sifive_serial_port(nb) (container_of((nb), \
+							 struct sifive_serial_port, \
+							 clk_notifier))
+
+/*
+ * Forward declarations
+ */
+static void sifive_serial_stop_tx(struct uart_port *port);
+
+/*
+ * Internal functions
+ */
+
+/**
+ * __ssp_early_writel() - write to a SiFive serial port register (early)
+ * @port: pointer to a struct uart_port record
+ * @offs: register address offset from the IP block base address
+ * @v: value to write to the register
+ *
+ * Given a pointer @port to a struct uart_port record, write the value
+ * @v to the IP block register address offset @offs.  This function is
+ * intended for early console use.
+ *
+ * Context: Intended to be used only by the earlyconsole code.
+ */
+static void __ssp_early_writel(u32 v, u16 offs, struct uart_port *port)
+{
+	writel_relaxed(v, port->membase + offs);
+}
+
+/**
+ * __ssp_early_readl() - read from a SiFive serial port register (early)
+ * @port: pointer to a struct uart_port record
+ * @offs: register address offset from the IP block base address
+ *
+ * Given a pointer @port to a struct uart_port record, read the
+ * contents of the IP block register located at offset @offs from the
+ * IP block base and return it.  This function is intended for early
+ * console use.
+ *
+ * Context: Intended to be called only by the earlyconsole code or by
+ *          __ssp_readl() or __ssp_writel() (in this driver)
+ *
+ * Returns: the register value read from the UART.
+ */
+static u32 __ssp_early_readl(struct uart_port *port, u16 offs)
+{
+	return readl_relaxed(port->membase + offs);
+}
+
+/**
+ * __ssp_writel() - write to a SiFive serial port register
+ * @v: value to write to the register
+ * @offs: register address offset from the IP block base address
+ * @ssp: pointer to a struct sifive_serial_port record
+ *
+ * Write the value @v to the IP block register located at offset @offs from the
+ * IP block base, given a pointer @ssp to a struct sifive_serial_port record.
+ *
+ * Context: Any context.
+ */
+static void __ssp_writel(u32 v, u16 offs, struct sifive_serial_port *ssp)
+{
+	__ssp_early_writel(v, offs, &ssp->port);
+}
+
+/**
+ * __ssp_readl() - read from a SiFive serial port register
+ * @ssp: pointer to a struct sifive_serial_port record
+ * @offs: register address offset from the IP block base address
+ *
+ * Read the contents of the IP block register located at offset @offs from the
+ * IP block base, given a pointer @ssp to a struct sifive_serial_port record.
+ *
+ * Context: Any context.
+ *
+ * Returns: the value of the UART register
+ */
+static u32 __ssp_readl(struct sifive_serial_port *ssp, u16 offs)
+{
+	return __ssp_early_readl(&ssp->port, offs);
+}
+
+/**
+ * sifive_serial_is_txfifo_full() - is the TXFIFO full?
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Read the transmit FIFO "full" bit, returning a non-zero value if the
+ * TX FIFO is full, or zero if space remains.  Intended to be used to prevent
+ * writes to the TX FIFO when it's full.
+ *
+ * Returns: SIFIVE_SERIAL_TXDATA_FULL_MASK (non-zero) if the transmit FIFO
+ * is full, or 0 if space remains.
+ */
+static int sifive_serial_is_txfifo_full(struct sifive_serial_port *ssp)
+{
+	return __ssp_readl(ssp, SIFIVE_SERIAL_TXDATA_OFFS) &
+		SIFIVE_SERIAL_TXDATA_FULL_MASK;
+}
+
+/**
+ * __ssp_transmit_char() - enqueue a byte to transmit onto the TX FIFO
+ * @ssp: pointer to a struct sifive_serial_port
+ * @ch: character to transmit
+ *
+ * Enqueue a byte @ch onto the transmit FIFO, given a pointer @ssp to the
+ * struct sifive_serial_port * to transmit on.  Caller should first check to
+ * ensure that the TXFIFO has space; see sifive_serial_is_txfifo_full().
+ *
+ * Context: Any context.
+ */
+static void __ssp_transmit_char(struct sifive_serial_port *ssp, int ch)
+{
+	__ssp_writel(ch, SIFIVE_SERIAL_TXDATA_OFFS, ssp);
+}
+
+/**
+ * __ssp_transmit_chars() - enqueue multiple bytes onto the TX FIFO
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Transfer up to a TX FIFO size's worth of characters from the Linux serial
+ * transmit buffer to the SiFive UART TX FIFO.
+ *
+ * Context: Any context.  Expects @ssp->port.lock to be held by caller.
+ */
+static void __ssp_transmit_chars(struct sifive_serial_port *ssp)
+{
+	struct circ_buf *xmit = &ssp->port.state->xmit;
+	int count;
+
+	if (ssp->port.x_char) {
+		__ssp_transmit_char(ssp, ssp->port.x_char);
+		ssp->port.icount.tx++;
+		ssp->port.x_char = 0;
+		return;
+	}
+	if (uart_circ_empty(xmit) || uart_tx_stopped(&ssp->port)) {
+		sifive_serial_stop_tx(&ssp->port);
+		return;
+	}
+	count = SIFIVE_TX_FIFO_DEPTH;
+	do {
+		__ssp_transmit_char(ssp, xmit->buf[xmit->tail]);
+		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+		ssp->port.icount.tx++;
+		if (uart_circ_empty(xmit))
+			break;
+	} while (--count > 0);
+
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(&ssp->port);
+
+	if (uart_circ_empty(xmit))
+		sifive_serial_stop_tx(&ssp->port);
+}
+
+/**
+ * __ssp_enable_txwm() - enable transmit watermark interrupts
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Enable interrupt generation when the transmit FIFO watermark is reached
+ * on the SiFive UART referred to by @ssp.
+ */
+static void __ssp_enable_txwm(struct sifive_serial_port *ssp)
+{
+	if (ssp->ier & SIFIVE_SERIAL_IE_TXWM_MASK)
+		return;
+
+	ssp->ier |= SIFIVE_SERIAL_IE_TXWM_MASK;
+	__ssp_writel(ssp->ier, SIFIVE_SERIAL_IE_OFFS, ssp);
+}
+
+/**
+ * __ssp_enable_rxwm() - enable receive watermark interrupts
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Enable interrupt generation when the receive FIFO watermark is reached
+ * on the SiFive UART referred to by @ssp.
+ */
+static void __ssp_enable_rxwm(struct sifive_serial_port *ssp)
+{
+	if (ssp->ier & SIFIVE_SERIAL_IE_RXWM_MASK)
+		return;
+
+	ssp->ier |= SIFIVE_SERIAL_IE_RXWM_MASK;
+	__ssp_writel(ssp->ier, SIFIVE_SERIAL_IE_OFFS, ssp);
+}
+
+/**
+ * __ssp_disable_txwm() - disable transmit watermark interrupts
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Disable interrupt generation when the transmit FIFO watermark is reached
+ * on the UART referred to by @ssp.
+ */
+static void __ssp_disable_txwm(struct sifive_serial_port *ssp)
+{
+	if (!(ssp->ier & SIFIVE_SERIAL_IE_TXWM_MASK))
+		return;
+
+	ssp->ier &= ~SIFIVE_SERIAL_IE_TXWM_MASK;
+	__ssp_writel(ssp->ier, SIFIVE_SERIAL_IE_OFFS, ssp);
+}
+
+/**
+ * __ssp_disable_rxwm() - disable receive watermark interrupts
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Disable interrupt generation when the receive FIFO watermark is reached
+ * on the UART referred to by @ssp.
+ */
+static void __ssp_disable_rxwm(struct sifive_serial_port *ssp)
+{
+	if (!(ssp->ier & SIFIVE_SERIAL_IE_RXWM_MASK))
+		return;
+
+	ssp->ier &= ~SIFIVE_SERIAL_IE_RXWM_MASK;
+	__ssp_writel(ssp->ier, SIFIVE_SERIAL_IE_OFFS, ssp);
+}
+
+/**
+ * __ssp_receive_char() - receive a byte from the UART
+ * @ssp: pointer to a struct sifive_serial_port
+ * @is_empty: char pointer to return whether the RX FIFO is empty
+ *
+ * Try to read a byte from the SiFive UART RX FIFO, referenced by
+ * @ssp, and to return it.  Also returns the RX FIFO empty bit in
+ * the char pointed to by @ch.  The caller must pass the byte back to the
+ * Linux serial layer if needed.
+ *
+ * Returns: the byte read from the UART RX FIFO.
+ */
+static char __ssp_receive_char(struct sifive_serial_port *ssp, char *is_empty)
+{
+	u32 v;
+	u8 ch;
+
+	v = __ssp_readl(ssp, SIFIVE_SERIAL_RXDATA_OFFS);
+
+	if (!is_empty)
+		WARN_ON(1);
+	else
+		*is_empty = (v & SIFIVE_SERIAL_RXDATA_EMPTY_MASK) >>
+			SIFIVE_SERIAL_RXDATA_EMPTY_SHIFT;
+
+	ch = (v & SIFIVE_SERIAL_RXDATA_DATA_MASK) >>
+		SIFIVE_SERIAL_RXDATA_DATA_SHIFT;
+
+	return ch;
+}
+
+/**
+ * __ssp_receive_chars() - receive multiple bytes from the UART
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Receive up to an RX FIFO's worth of bytes from the SiFive UART referred
+ * to by @ssp and pass them up to the Linux serial layer.
+ *
+ * Context: Expects ssp->port.lock to be held by caller.
+ */
+static void __ssp_receive_chars(struct sifive_serial_port *ssp)
+{
+	unsigned char ch;
+	char is_empty;
+	int c;
+
+	for (c = SIFIVE_RX_FIFO_DEPTH; c > 0; --c) {
+		ch = __ssp_receive_char(ssp, &is_empty);
+		if (is_empty)
+			break;
+
+		ssp->port.icount.rx++;
+		uart_insert_char(&ssp->port, 0, 0, ch, TTY_NORMAL);
+	}
+
+	spin_unlock(&ssp->port.lock);
+	tty_flip_buffer_push(&ssp->port.state->port);
+	spin_lock(&ssp->port.lock);
+}
+
+/**
+ * __ssp_update_div() - calculate the divisor setting by the line rate
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Calculate the appropriate value of the clock divisor for the UART
+ * and target line rate referred to by @ssp and write it into the
+ * hardware.
+ */
+static void __ssp_update_div(struct sifive_serial_port *ssp)
+{
+	u16 div;
+
+	div = DIV_ROUND_UP(ssp->clkin_rate, ssp->baud_rate) - 1;
+
+	__ssp_writel(div, SIFIVE_SERIAL_DIV_OFFS, ssp);
+}
+
+/**
+ * __ssp_update_baud_rate() - set the UART "baud rate"
+ * @ssp: pointer to a struct sifive_serial_port
+ * @rate: new target bit rate
+ *
+ * Calculate the UART divisor value for the target bit rate @rate for the
+ * SiFive UART described by @ssp and program it into the UART.  There may
+ * be some error between the target bit rate and the actual bit rate implemented
+ * by the UART due to clock ratio granularity.
+ */
+static void __ssp_update_baud_rate(struct sifive_serial_port *ssp,
+				   unsigned int rate)
+{
+	if (ssp->baud_rate == rate)
+		return;
+
+	ssp->baud_rate = rate;
+	__ssp_update_div(ssp);
+}
+
+/**
+ * __ssp_set_stop_bits() - set the number of stop bits
+ * @ssp: pointer to a struct sifive_serial_port
+ * @nstop: 1 or 2 (stop bits)
+ *
+ * Program the SiFive UART referred to by @ssp to use @nstop stop bits.
+ */
+static void __ssp_set_stop_bits(struct sifive_serial_port *ssp, char nstop)
+{
+	u32 v;
+
+	if (nstop < 1 || nstop > 2) {
+		WARN_ON(1);
+		return;
+	}
+
+	v = __ssp_readl(ssp, SIFIVE_SERIAL_TXCTRL_OFFS);
+	v &= ~SIFIVE_SERIAL_TXCTRL_NSTOP_MASK;
+	v |= (nstop - 1) << SIFIVE_SERIAL_TXCTRL_NSTOP_SHIFT;
+	__ssp_writel(v, SIFIVE_SERIAL_TXCTRL_OFFS, ssp);
+}
+
+/**
+ * __ssp_wait_for_xmitr() - wait for an empty slot on the TX FIFO
+ * @ssp: pointer to a struct sifive_serial_port
+ *
+ * Delay while the UART TX FIFO referred to by @ssp is marked as full.
+ *
+ * Context: Any context.
+ */
+static void __maybe_unused __ssp_wait_for_xmitr(struct sifive_serial_port *ssp)
+{
+	while (sifive_serial_is_txfifo_full(ssp))
+		udelay(1); /* XXX Could probably be more intelligent here */
+}
+
+/*
+ * Linux serial API functions
+ */
+
+static void sifive_serial_stop_tx(struct uart_port *port)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	__ssp_disable_txwm(ssp);
+}
+
+static void sifive_serial_stop_rx(struct uart_port *port)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	__ssp_disable_rxwm(ssp);
+}
+
+static void sifive_serial_start_tx(struct uart_port *port)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	__ssp_enable_txwm(ssp);
+}
+
+static irqreturn_t sifive_serial_irq(int irq, void *dev_id)
+{
+	struct sifive_serial_port *ssp = dev_id;
+	u32 ip;
+
+	spin_lock(&ssp->port.lock);
+
+	ip = __ssp_readl(ssp, SIFIVE_SERIAL_IP_OFFS);
+	if (!ip) {
+		spin_unlock(&ssp->port.lock);
+		return IRQ_NONE;
+	}
+
+	if (ip & SIFIVE_SERIAL_IP_RXWM_MASK)
+		__ssp_receive_chars(ssp);
+	if (ip & SIFIVE_SERIAL_IP_TXWM_MASK)
+		__ssp_transmit_chars(ssp);
+
+	spin_unlock(&ssp->port.lock);
+
+	return IRQ_HANDLED;
+}
+
+static unsigned int sifive_serial_tx_empty(struct uart_port *port)
+{
+	return TIOCSER_TEMT;
+}
+
+static unsigned int sifive_serial_get_mctrl(struct uart_port *port)
+{
+	return TIOCM_CAR | TIOCM_CTS | TIOCM_DSR;
+}
+
+static void sifive_serial_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+	/* IP block does not support these signals */
+}
+
+static void sifive_serial_break_ctl(struct uart_port *port, int break_state)
+{
+	/* IP block does not support sending a break */
+}
+
+static int sifive_serial_startup(struct uart_port *port)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	__ssp_enable_rxwm(ssp);
+
+	return 0;
+}
+
+static void sifive_serial_shutdown(struct uart_port *port)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	__ssp_disable_rxwm(ssp);
+	__ssp_disable_txwm(ssp);
+}
+
+/**
+ * sifive_serial_clk_notifier() - clock post-rate-change notifier
+ * @nb: pointer to the struct notifier_block, from the notifier code
+ * @event: event mask from the notifier code
+ * @data: pointer to the struct clk_notifier_data from the notifier code
+ *
+ * On the V0 SoC, the UART IP block is derived from the CPU clock source
+ * after a synchronous divide-by-two divider, so any CPU clock rate change
+ * requires the UART baud rate to be updated.  This presumably could corrupt any
+ * serial word currently being transmitted or received.  It would probably
+ * be better to stop receives and transmits, then complete the baud rate
+ * change, then re-enable them.
+ */
+static int sifive_serial_clk_notifier(struct notifier_block *nb,
+				      unsigned long event, void *data)
+{
+	struct clk_notifier_data *cnd = data;
+	struct sifive_serial_port *ssp = notifier_to_sifive_serial_port(nb);
+
+	if (event == POST_RATE_CHANGE && ssp->clkin_rate != cnd->new_rate) {
+		ssp->clkin_rate = cnd->new_rate;
+		__ssp_update_div(ssp);
+	}
+
+	return NOTIFY_OK;
+}
+
+static void sifive_serial_set_termios(struct uart_port *port,
+				      struct ktermios *termios,
+				      struct ktermios *old)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+	unsigned long flags;
+	u32 v, old_v;
+	int rate;
+	char nstop;
+
+	if ((termios->c_cflag & CSIZE) != CS8) {
+		dev_err(ssp->port.dev, "only 8-bit words supported\n");
+		return;
+	}
+
+	/* Set number of stop bits */
+	nstop = (termios->c_cflag & CSTOPB) ? 2 : 1;
+	__ssp_set_stop_bits(ssp, nstop);
+
+	/* Set line rate */
+	rate = uart_get_baud_rate(port, termios, old, 0, ssp->clkin_rate / 16);
+	__ssp_update_baud_rate(ssp, rate);
+
+	spin_lock_irqsave(&ssp->port.lock, flags);
+
+	/*
+	 * Update the per-port timeout.
+	 */
+	uart_update_timeout(port, termios->c_cflag, rate);
+
+	ssp->port.read_status_mask = 0;
+	if (termios->c_iflag & INPCK) {
+		dev_err(ssp->port.dev, "INPCK flag not supported\n");
+		goto ssst_out;
+	}
+	if (termios->c_iflag & (BRKINT | PARMRK)) {
+		dev_err(ssp->port.dev, "BRKINT/PARMRK flag not supported\n");
+		goto ssst_out;
+	}
+
+	/*
+	 * ignore all characters if CREAD is not set
+	 */
+	v = __ssp_readl(ssp, SIFIVE_SERIAL_RXCTRL_OFFS);
+	old_v = v;
+	if ((termios->c_cflag & CREAD) == 0)
+		v &= SIFIVE_SERIAL_RXCTRL_RXEN_MASK;
+	else
+		v |= SIFIVE_SERIAL_RXCTRL_RXEN_MASK;
+	if (v != old_v)
+		__ssp_writel(v, SIFIVE_SERIAL_RXCTRL_OFFS, ssp);
+
+ssst_out:
+	spin_unlock_irqrestore(&ssp->port.lock, flags);
+}
+
+static void sifive_serial_release_port(struct uart_port *port)
+{
+}
+
+static int sifive_serial_request_port(struct uart_port *port)
+{
+	return 0;
+}
+
+static void sifive_serial_config_port(struct uart_port *port, int flags)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	ssp->port.type = PORT_SIFIVE_V0;
+}
+
+static int sifive_serial_verify_port(struct uart_port *port,
+				     struct serial_struct *ser)
+{
+	return -EINVAL;
+}
+
+static const char *sifive_serial_type(struct uart_port *port)
+{
+	return port->type == PORT_SIFIVE_V0 ? "SiFive UART v0" : NULL;
+}
+
+/*
+ * Early console support
+ */
+
+#ifdef CONFIG_SERIAL_EARLYCON
+static void early_sifive_serial_putc(struct uart_port *port, int c)
+{
+	while (__ssp_early_readl(port, SIFIVE_SERIAL_TXDATA_OFFS) &
+	       SIFIVE_SERIAL_TXDATA_FULL_MASK)
+		cpu_relax();
+
+	__ssp_early_writel(c, SIFIVE_SERIAL_TXDATA_OFFS, port);
+}
+
+static void early_sifive_serial_write(struct console *con, const char *s,
+				      unsigned int n)
+{
+	struct earlycon_device *dev = con->data;
+	struct uart_port *port = &dev->port;
+
+	uart_console_write(port, s, n, early_sifive_serial_putc);
+}
+
+static int __init early_sifive_serial_setup(struct earlycon_device *dev,
+					    const char *options)
+{
+	struct uart_port *port = &dev->port;
+
+	if (!port->membase)
+		return -ENODEV;
+
+	dev->con->write = early_sifive_serial_write;
+
+	return 0;
+}
+
+OF_EARLYCON_DECLARE(sifive, "sifive,uart0", early_sifive_serial_setup);
+OF_EARLYCON_DECLARE(sifive, "sifive,fu540-c000-uart0",
+		    early_sifive_serial_setup);
+#endif /* CONFIG_SERIAL_EARLYCON */
+
+/*
+ * Linux console interface
+ */
+
+#ifdef CONFIG_SERIAL_SIFIVE_CONSOLE
+
+static struct sifive_serial_port *sifive_serial_console_ports[SIFIVE_SERIAL_MAX_PORTS];
+
+static void sifive_serial_console_putchar(struct uart_port *port, int ch)
+{
+	struct sifive_serial_port *ssp = port_to_sifive_serial_port(port);
+
+	__ssp_wait_for_xmitr(ssp);
+	__ssp_transmit_char(ssp, ch);
+}
+
+static void sifive_serial_console_write(struct console *co, const char *s,
+					unsigned int count)
+{
+	struct sifive_serial_port *ssp = sifive_serial_console_ports[co->index];
+	unsigned long flags;
+	unsigned int ier;
+	int locked = 1;
+
+	if (!ssp)
+		return;
+
+	local_irq_save(flags);
+	if (ssp->port.sysrq)
+		locked = 0;
+	else if (oops_in_progress)
+		locked = spin_trylock(&ssp->port.lock);
+	else
+		spin_lock(&ssp->port.lock);
+
+	ier = __ssp_readl(ssp, SIFIVE_SERIAL_IE_OFFS);
+	__ssp_writel(0, SIFIVE_SERIAL_IE_OFFS, ssp);
+
+	uart_console_write(&ssp->port, s, count, sifive_serial_console_putchar);
+
+	__ssp_writel(ier, SIFIVE_SERIAL_IE_OFFS, ssp);
+
+	if (locked)
+		spin_unlock(&ssp->port.lock);
+	local_irq_restore(flags);
+}
+
+static int __init sifive_serial_console_setup(struct console *co, char *options)
+{
+	struct sifive_serial_port *ssp;
+	int baud = SIFIVE_DEFAULT_BAUD_RATE;
+	int bits = 8;
+	int parity = 'n';
+	int flow = 'n';
+
+	if (co->index < 0 || co->index >= SIFIVE_SERIAL_MAX_PORTS)
+		return -ENODEV;
+
+	ssp = sifive_serial_console_ports[co->index];
+	if (!ssp)
+		return -ENODEV;
+
+	if (options)
+		uart_parse_options(options, &baud, &parity, &bits, &flow);
+
+	return uart_set_options(&ssp->port, co, baud, parity, bits, flow);
+}
+
+static struct uart_driver sifive_serial_uart_driver;
+
+static struct console sifive_serial_console = {
+	.name		= SIFIVE_TTY_PREFIX,
+	.write		= sifive_serial_console_write,
+	.device		= uart_console_device,
+	.setup		= sifive_serial_console_setup,
+	.flags		= CON_PRINTBUFFER,
+	.index		= -1,
+	.data		= &sifive_serial_uart_driver,
+};
+
+static int __init sifive_console_init(void)
+{
+	register_console(&sifive_serial_console);
+	return 0;
+}
+
+console_initcall(sifive_console_init);
+
+static void __ssp_add_console_port(struct sifive_serial_port *ssp)
+{
+	sifive_serial_console_ports[ssp->port.line] = ssp;
+}
+
+static void __ssp_remove_console_port(struct sifive_serial_port *ssp)
+{
+	sifive_serial_console_ports[ssp->port.line] = 0;
+}
+
+#define SIFIVE_SERIAL_CONSOLE	(&sifive_serial_console)
+
+#else
+
+#define SIFIVE_SERIAL_CONSOLE	NULL
+
+static void __ssp_add_console_port(struct sifive_serial_port *ssp)
+{}
+static void __ssp_remove_console_port(struct sifive_serial_port *ssp)
+{}
+
+#endif
+
+static const struct uart_ops sifive_serial_uops = {
+	.tx_empty	= sifive_serial_tx_empty,
+	.set_mctrl	= sifive_serial_set_mctrl,
+	.get_mctrl	= sifive_serial_get_mctrl,
+	.stop_tx	= sifive_serial_stop_tx,
+	.start_tx	= sifive_serial_start_tx,
+	.stop_rx	= sifive_serial_stop_rx,
+	.break_ctl	= sifive_serial_break_ctl,
+	.startup	= sifive_serial_startup,
+	.shutdown	= sifive_serial_shutdown,
+	.set_termios	= sifive_serial_set_termios,
+	.type		= sifive_serial_type,
+	.release_port	= sifive_serial_release_port,
+	.request_port	= sifive_serial_request_port,
+	.config_port	= sifive_serial_config_port,
+	.verify_port	= sifive_serial_verify_port,
+};
+
+static struct uart_driver sifive_serial_uart_driver = {
+	.owner		= THIS_MODULE,
+	.driver_name	= SIFIVE_SERIAL_NAME,
+	.dev_name	= SIFIVE_TTY_PREFIX,
+	.nr		= SIFIVE_SERIAL_MAX_PORTS,
+	.cons		= SIFIVE_SERIAL_CONSOLE,
+};
+
+static int sifive_serial_probe(struct platform_device *pdev)
+{
+	struct sifive_serial_port *ssp;
+	struct resource *mem;
+	struct clk *clk;
+	void __iomem *base;
+	int irq, id, r;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "could not acquire interrupt\n");
+		return -EPROBE_DEFER;
+	}
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	base = devm_ioremap_resource(&pdev->dev, mem);
+	if (IS_ERR(base)) {
+		dev_err(&pdev->dev, "could not acquire device memory\n");
+		return PTR_ERR(base);
+	}
+
+	clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(clk)) {
+		dev_err(&pdev->dev, "unable to find controller clock\n");
+		return PTR_ERR(clk);
+	}
+
+	id = of_alias_get_id(pdev->dev.of_node, "serial");
+	if (id < 0) {
+		dev_err(&pdev->dev, "missing aliases entry\n");
+		return id;
+	}
+
+#ifdef CONFIG_SERIAL_SIFIVE_CONSOLE
+	if (id > SIFIVE_SERIAL_MAX_PORTS) {
+		dev_err(&pdev->dev, "too many UARTs (%d)\n", id);
+		return -EINVAL;
+	}
+#endif
+
+	ssp = devm_kzalloc(&pdev->dev, sizeof(*ssp), GFP_KERNEL);
+	if (!ssp)
+		return -ENOMEM;
+
+	ssp->port.dev = &pdev->dev;
+	ssp->port.type = PORT_SIFIVE_V0;
+	ssp->port.iotype = UPIO_MEM;
+	ssp->port.irq = irq;
+	ssp->port.fifosize = SIFIVE_TX_FIFO_DEPTH;
+	ssp->port.ops = &sifive_serial_uops;
+	ssp->port.line = id;
+	ssp->port.mapbase = mem->start;
+	ssp->port.membase = base;
+	ssp->dev = &pdev->dev;
+	ssp->clk = clk;
+	ssp->clk_notifier.notifier_call = sifive_serial_clk_notifier;
+
+	r = clk_notifier_register(ssp->clk, &ssp->clk_notifier);
+	if (r) {
+		dev_err(&pdev->dev, "could not register clock notifier: %d\n",
+			r);
+		goto probe_out1;
+	}
+
+	/* Set up clock divider */
+	ssp->clkin_rate = clk_get_rate(ssp->clk);
+	ssp->baud_rate = SIFIVE_DEFAULT_BAUD_RATE;
+	__ssp_update_div(ssp);
+
+	platform_set_drvdata(pdev, ssp);
+
+	/* Enable transmits and set the watermark level to 1 */
+	__ssp_writel((1 << SIFIVE_SERIAL_TXCTRL_TXCNT_SHIFT) |
+		     SIFIVE_SERIAL_TXCTRL_TXEN_MASK,
+		     SIFIVE_SERIAL_TXCTRL_OFFS, ssp);
+
+	/* Enable receives and set the watermark level to 0 */
+	__ssp_writel((0 << SIFIVE_SERIAL_RXCTRL_RXCNT_SHIFT) |
+		     SIFIVE_SERIAL_RXCTRL_RXEN_MASK,
+		     SIFIVE_SERIAL_RXCTRL_OFFS, ssp);
+
+	r = request_irq(ssp->port.irq, sifive_serial_irq, ssp->port.irqflags,
+			dev_name(&pdev->dev), ssp);
+	if (r) {
+		dev_err(&pdev->dev, "could not attach interrupt: %d\n", r);
+		goto probe_out2;
+	}
+
+	__ssp_add_console_port(ssp);
+
+	r = uart_add_one_port(&sifive_serial_uart_driver, &ssp->port);
+	if (r != 0) {
+		dev_err(&pdev->dev, "could not add uart: %d\n", r);
+		goto probe_out3;
+	}
+
+	return 0;
+
+probe_out3:
+	__ssp_remove_console_port(ssp);
+	free_irq(ssp->port.irq, ssp);
+probe_out2:
+	clk_notifier_unregister(ssp->clk, &ssp->clk_notifier);
+probe_out1:
+	return r;
+}
+
+static int sifive_serial_remove(struct platform_device *dev)
+{
+	struct sifive_serial_port *ssp = platform_get_drvdata(dev);
+
+	__ssp_remove_console_port(ssp);
+	uart_remove_one_port(&sifive_serial_uart_driver, &ssp->port);
+	free_irq(ssp->port.irq, ssp);
+	clk_notifier_unregister(ssp->clk, &ssp->clk_notifier);
+
+	return 0;
+}
+
+static const struct of_device_id sifive_serial_of_match[] = {
+	{ .compatible = "sifive,fu540-c000-uart0" },
+	{ .compatible = "sifive,uart0" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, sifive_serial_of_match);
+
+static struct platform_driver sifive_serial_platform_driver = {
+	.probe		= sifive_serial_probe,
+	.remove		= sifive_serial_remove,
+	.driver		= {
+		.name	= SIFIVE_SERIAL_NAME,
+		.of_match_table = of_match_ptr(sifive_serial_of_match),
+	},
+};
+
+static int __init sifive_serial_init(void)
+{
+	int r;
+
+	r = uart_register_driver(&sifive_serial_uart_driver);
+	if (r)
+		goto init_out1;
+
+	r = platform_driver_register(&sifive_serial_platform_driver);
+	if (r)
+		goto init_out2;
+
+	return 0;
+
+init_out2:
+	uart_unregister_driver(&sifive_serial_uart_driver);
+init_out1:
+	return r;
+}
+
+static void __exit sifive_serial_exit(void)
+{
+	platform_driver_unregister(&sifive_serial_platform_driver);
+	uart_unregister_driver(&sifive_serial_uart_driver);
+}
+
+module_init(sifive_serial_init);
+module_exit(sifive_serial_exit);
+
+MODULE_DESCRIPTION("SiFive UART serial driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Paul Walmsley <paul@pwsan.com>");
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 6009ee2c2e99..2191fa691770 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -287,4 +287,7 @@
 /* RDA UART */
 #define PORT_RDA	118
 
+/* SiFive UART */
+#define PORT_SIFIVE_V0	119
+
 #endif /* _UAPILINUX_SERIAL_CORE_H */
-- 
2.20.1

^ permalink raw reply related

* [PATCH v4 1/2] dt-bindings: serial: add documentation for the SiFive UART driver
From: Paul Walmsley @ 2019-04-11  9:00 UTC (permalink / raw)
  To: linux-kernel, linux-riscv, linux-serial, gregkh
  Cc: Paul Walmsley, Paul Walmsley, devicetree, Rob Herring,
	Mark Rutland, Palmer Dabbelt
In-Reply-To: <20190411090027.8670-1-paul.walmsley@sifive.com>

Add DT binding documentation for the Linux driver for the SiFive
asynchronous serial IP block.

This revision incorporates changes based on feedback from Rob
Herring <robh@kernel.org>.

Signed-off-by: Paul Walmsley <paul.walmsley@sifive.com>
Signed-off-by: Paul Walmsley <paul@pwsan.com>
Cc: linux-serial@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-riscv@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Palmer Dabbelt <palmer@sifive.com>
---
 .../bindings/serial/sifive-serial.txt         | 33 +++++++++++++++++++
 1 file changed, 33 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serial/sifive-serial.txt

diff --git a/Documentation/devicetree/bindings/serial/sifive-serial.txt b/Documentation/devicetree/bindings/serial/sifive-serial.txt
new file mode 100644
index 000000000000..c86b1e524159
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/sifive-serial.txt
@@ -0,0 +1,33 @@
+SiFive asynchronous serial interface (UART)
+
+Required properties:
+
+- compatible: should be something similar to
+	      "sifive,<chip>-uart" for the UART as integrated
+	      on a particular chip, and "sifive,uart<version>" for the
+	      general UART IP block programming model.	Supported
+	      compatible strings as of the date of this writing are:
+	      "sifive,fu540-c000-uart" for the SiFive UART v0 as
+	      integrated onto the SiFive FU540 chip, or "sifive,uart0"
+	      for the SiFive UART v0 IP block with no chip integration
+	      tweaks (if any)
+- reg: address and length of the register space
+- interrupts: Should contain the UART interrupt identifier
+- clocks: Should contain a clock identifier for the UART's parent clock
+
+
+UART HDL that corresponds to the IP block version numbers can be found
+here:
+
+https://github.com/sifive/sifive-blocks/tree/master/src/main/scala/devices/uart
+
+
+Example:
+
+uart0: serial@10010000 {
+	compatible = "sifive,fu540-c000-uart", "sifive,uart0";
+	interrupt-parent = <&plic0>;
+	interrupts = <80>;
+	reg = <0x0 0x10010000 0x0 0x1000>;
+	clocks = <&prci PRCI_CLK_TLCLK>;
+};
-- 
2.20.1

^ permalink raw reply related

* [PATCH v4 0/2] tty: serial: add DT bindings and serial driver for the SiFive FU540 UART
From: Paul Walmsley @ 2019-04-11  9:00 UTC (permalink / raw)
  To: linux-kernel, linux-riscv, linux-serial, gregkh; +Cc: Paul Walmsley

This series adds a serial driver, with console support, for the
UART IP block present on the SiFive FU540 SoC.  The programming
model is straightforward, but unique.

Boot-tested on a SiFive FU540 HiFive-U board, using BBL and the
open-source FSBL (with appropriate patches to the DT data).

This fourth version incorporates a few more comments from the lists.

The patches in this series can also be found, with the PRCI patches,
DT patches, and DT prerequisite patch, at:

https://github.com/sifive/riscv-linux/tree/dev/paulw/serial-v5.1-rc4


- Paul


Paul Walmsley (2):
  dt-bindings: serial: add documentation for the SiFive UART driver
  tty: serial: add driver for the SiFive UART

 .../bindings/serial/sifive-serial.txt         |   33 +
 drivers/tty/serial/Kconfig                    |   24 +
 drivers/tty/serial/Makefile                   |    1 +
 drivers/tty/serial/sifive.c                   | 1067 +++++++++++++++++
 include/uapi/linux/serial_core.h              |    3 +
 5 files changed, 1128 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serial/sifive-serial.txt
 create mode 100644 drivers/tty/serial/sifive.c

-- 
2.20.1

^ permalink raw reply

* Re: [PATCH v3 0/2] tty: serial: add DT bindings and serial driver for the SiFive FU540 UART
From: Paul Walmsley @ 2019-04-11  8:54 UTC (permalink / raw)
  To: Andreas Schwab
  Cc: Paul Walmsley, linux-serial, Greg Kroah-Hartman, Jiri Slaby,
	Palmer Dabbelt, Wesley Terpstra, linux-riscv, linux-kernel,
	devicetree
In-Reply-To: <mvmr2bkxntv.fsf@suse.de>

On Wed, 6 Mar 2019, Andreas Schwab wrote:

> Trying to log in on the serial console causes the system to freeze.  The
> last message is:
> 
> [  115.597858] sifive-serial 10010000.serial: BRKINT/PARMRK flag not supported

Have not seen that problem.

If you're still seeing this, could you follow up with me off-list so we 
can synchronize configurations, both with the kernel and with non-kernel 
boot & M-mode components?


- Paul

^ permalink raw reply

* Re: [PATCH v11 1/4] dmaengine: 8250_mtk_dma: add MediaTek uart DMA support
From: Long Cheng @ 2019-04-11  8:20 UTC (permalink / raw)
  To: Sean Wang
  Cc: Vinod Koul, Randy Dunlap, Rob Herring, Mark Rutland, Ryder Lee,
	Nicolas Boichat, Matthias Brugger, Dan Williams,
	Greg Kroah-Hartman, Jiri Slaby, Sean Wang, dmaengine, devicetree,
	linux-arm-kernel, linux-mediatek, linux-kernel, linux-serial,
	srv_heupstream, Yingjoe Chen, YT Shen, Zhenbao
In-Reply-To: <CAGp9Lzos-6mLxDAgVJ783Z=SZTnxnKH+UDOLzEDvK4tng8P8dw@mail.gmail.com>

On Sun, 2019-03-10 at 17:31 -0700, Sean Wang wrote:
> Hi, Long
> 
> List some comments as the below and this week I will find a board to
> test and then improve the driver.
> 
>          Sean
> 
> On Wed, Mar 6, 2019 at 5:45 PM Long Cheng <long.cheng@mediatek.com> wrote:
> >
> > In DMA engine framework, add 8250 uart dma to support MediaTek uart.
> > If MediaTek uart enabled(SERIAL_8250_MT6577), and want to improve
> > the performance, can enable the function.
> >
> > Signed-off-by: Long Cheng <long.cheng@mediatek.com>
> > ---
> >  drivers/dma/mediatek/Kconfig          |   11 +
> >  drivers/dma/mediatek/Makefile         |    1 +
> >  drivers/dma/mediatek/mtk-uart-apdma.c |  660 +++++++++++++++++++++++++++++++++
> >  3 files changed, 672 insertions(+)
> >  create mode 100644 drivers/dma/mediatek/mtk-uart-apdma.c
> >
> > diff --git a/drivers/dma/mediatek/Kconfig b/drivers/dma/mediatek/Kconfig
> > index 680fc05..ac49eb6 100644
> > --- a/drivers/dma/mediatek/Kconfig
> > +++ b/drivers/dma/mediatek/Kconfig
> > @@ -24,3 +24,14 @@ config MTK_CQDMA
> >
> >           This controller provides the channels which is dedicated to
> >           memory-to-memory transfer to offload from CPU.
> > +
> > +config MTK_UART_APDMA
> > +       tristate "MediaTek SoCs APDMA support for UART"
> > +       depends on OF && SERIAL_8250_MT6577
> > +       select DMA_ENGINE
> > +       select DMA_VIRTUAL_CHANNELS
> > +       help
> > +         Support for the UART DMA engine found on MediaTek MTK SoCs.
> > +         When SERIAL_8250_MT6577 is enabled, and if you want to use DMA,
> > +         you can enable the config. The DMA engine can only be used
> > +         with MediaTek SoCs.
> > diff --git a/drivers/dma/mediatek/Makefile b/drivers/dma/mediatek/Makefile
> > index 41bb381..61a6d29 100644
> > --- a/drivers/dma/mediatek/Makefile
> > +++ b/drivers/dma/mediatek/Makefile
> > @@ -1,2 +1,3 @@
> > +obj-$(CONFIG_MTK_UART_APDMA) += mtk-uart-apdma.o
> >  obj-$(CONFIG_MTK_HSDMA) += mtk-hsdma.o
> >  obj-$(CONFIG_MTK_CQDMA) += mtk-cqdma.o
> > diff --git a/drivers/dma/mediatek/mtk-uart-apdma.c b/drivers/dma/mediatek/mtk-uart-apdma.c
> > new file mode 100644
> > index 0000000..9ed7a49
> > --- /dev/null
> > +++ b/drivers/dma/mediatek/mtk-uart-apdma.c
> > @@ -0,0 +1,660 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * MediaTek Uart APDMA driver.
> > + *
> > + * Copyright (c) 2018 MediaTek Inc.
> > + * Author: Long Cheng <long.cheng@mediatek.com>
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/dmaengine.h>
> > +#include <linux/dma-mapping.h>
> > +#include <linux/err.h>
> > +#include <linux/init.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/iopoll.h>
> > +#include <linux/kernel.h>
> > +#include <linux/list.h>
> > +#include <linux/module.h>
> > +#include <linux/of_device.h>
> > +#include <linux/of_dma.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/slab.h>
> > +#include <linux/spinlock.h>
> > +
> > +#include "../virt-dma.h"
> > +
> > +/* The default number of virtual channel */
> > +#define MTK_UART_APDMA_NR_VCHANS       8
> > +
> > +#define VFF_EN_B               BIT(0)
> > +#define VFF_STOP_B             BIT(0)
> > +#define VFF_FLUSH_B            BIT(0)
> > +#define VFF_4G_SUPPORT_B       BIT(0)
> > +#define VFF_RX_INT_EN0_B       BIT(0)  /* rx valid size >=  vff thre */
> > +#define VFF_RX_INT_EN1_B       BIT(1)
> > +#define VFF_TX_INT_EN_B                BIT(0)  /* tx left size >= vff thre */
> > +#define VFF_WARM_RST_B         BIT(0)
> > +#define VFF_RX_INT_CLR_B       (BIT(0) | BIT(1))
> > +#define VFF_TX_INT_CLR_B       0
> > +#define VFF_STOP_CLR_B         0
> > +#define VFF_INT_EN_CLR_B       0
> > +#define VFF_4G_SUPPORT_CLR_B   0
> > +
> > +/* interrupt trigger level for tx */
> > +#define VFF_TX_THRE(n)         ((n) * 7 / 8)
> > +/* interrupt trigger level for rx */
> > +#define VFF_RX_THRE(n)         ((n) * 3 / 4)
> > +
> > +#define VFF_RING_SIZE  0xffffU
> > +/* invert this bit when wrap ring head again */
> > +#define VFF_RING_WRAP  0x10000U
> > +
> > +#define VFF_INT_FLAG           0x00
> > +#define VFF_INT_EN             0x04
> > +#define VFF_EN                 0x08
> > +#define VFF_RST                        0x0c
> > +#define VFF_STOP               0x10
> > +#define VFF_FLUSH              0x14
> > +#define VFF_ADDR               0x1c
> > +#define VFF_LEN                        0x24
> > +#define VFF_THRE               0x28
> > +#define VFF_WPT                        0x2c
> > +#define VFF_RPT                        0x30
> > +/* TX: the buffer size HW can read. RX: the buffer size SW can read. */
> > +#define VFF_VALID_SIZE         0x3c
> > +/* TX: the buffer size SW can write. RX: the buffer size HW can write. */
> > +#define VFF_LEFT_SIZE          0x40
> > +#define VFF_DEBUG_STATUS       0x50
> > +#define VFF_4G_SUPPORT         0x54
> > +
> > +struct mtk_uart_apdmadev {
> > +       struct dma_device ddev;
> > +       struct clk *clk;
> > +       bool support_33bits;
> > +       unsigned int dma_requests;
> > +       unsigned int *dma_irq;
> > +};
> > +
> > +struct mtk_uart_apdma_desc {
> > +       struct virt_dma_desc vd;
> > +
> > +       unsigned int avail_len;
> > +};
> > +
> > +struct mtk_chan {
> > +       struct virt_dma_chan vc;
> > +       struct dma_slave_config cfg;
> > +       void __iomem *base;
> > +       struct mtk_uart_apdma_desc *desc;
> > +
> > +       enum dma_transfer_direction dir;
> > +
> > +       bool requested;
> > +
> > +       unsigned int rx_status;
> > +};
> > +
> > +static inline struct mtk_uart_apdmadev *
> > +to_mtk_uart_apdma_dev(struct dma_device *d)
> > +{
> > +       return container_of(d, struct mtk_uart_apdmadev, ddev);
> > +}
> > +
> > +static inline struct mtk_chan *to_mtk_uart_apdma_chan(struct dma_chan *c)
> > +{
> > +       return container_of(c, struct mtk_chan, vc.chan);
> > +}
> > +
> > +static inline struct mtk_uart_apdma_desc *to_mtk_uart_apdma_desc
> > +       (struct dma_async_tx_descriptor *t)
> > +{
> > +       return container_of(t, struct mtk_uart_apdma_desc, vd.tx);
> > +}
> > +
> > +static void mtk_uart_apdma_write(struct mtk_chan *c,
> > +                              unsigned int reg, unsigned int val)
> > +{
> > +       writel(val, c->base + reg);
> > +}
> > +
> > +static unsigned int mtk_uart_apdma_read(struct mtk_chan *c, unsigned int reg)
> > +{
> > +       return readl(c->base + reg);
> > +}
> > +
> > +static void mtk_uart_apdma_desc_free(struct virt_dma_desc *vd)
> > +{
> > +       struct dma_chan *chan = vd->tx.chan;
> > +       struct mtk_chan *c = to_mtk_uart_apdma_chan(chan);
> > +
> > +       kfree(c->desc);
> > +}
> > +
> > +static void mtk_uart_apdma_start_tx(struct mtk_chan *c)
> > +{
> > +       unsigned int len, send, left, wpt, d_wpt, tmp;
> > +       int ret;
> > +
> > +       left = mtk_uart_apdma_read(c, VFF_LEFT_SIZE);
> > +       if (!left) {
> > +               mtk_uart_apdma_write(c, VFF_INT_EN, VFF_TX_INT_EN_B);
> > +               return;
> > +       }
> > +
> > +       /* Wait 1sec for flush, can't sleep */
> > +       ret = readx_poll_timeout(readl, c->base + VFF_FLUSH, tmp,
> > +                       tmp != VFF_FLUSH_B, 0, 1000000);
> 
> It is really not a good idea that polling up to 1 second in an
> interrupt context.
> 

I will modify it in next version.

> > +       if (ret)
> > +               dev_warn(c->vc.chan.device->dev, "tx: fail, debug=0x%x\n",
> > +                       mtk_uart_apdma_read(c, VFF_DEBUG_STATUS));
> > +
> > +       send = min_t(unsigned int, left, c->desc->avail_len);
> > +       wpt = mtk_uart_apdma_read(c, VFF_WPT);
> > +       len = c->cfg.dst_port_window_size;
> > +
> > +       d_wpt = wpt + send;
> > +       if ((d_wpt & VFF_RING_SIZE) >= len) {
> 
> I am confused what size of VFF is.  Either VFF_RING_SIZE or
> c->cfg.dst_port_window_size?
> 

VFF_RRING_SIZE is max length that HW can support.The
c->cfg.dst_port_window_size is actual length. 

> > +               d_wpt = d_wpt - len;
> > +               d_wpt = d_wpt ^ VFF_RING_WRAP;
> > +       }
> > +       mtk_uart_apdma_write(c, VFF_WPT, d_wpt);
> > +
> > +       c->desc->avail_len -= send;
> > +
> > +       mtk_uart_apdma_write(c, VFF_INT_EN, VFF_TX_INT_EN_B);
> 
> Why should we need to program interrupt enabled bit again?

HW request. the step is must.

> 
> > +       if (mtk_uart_apdma_read(c, VFF_FLUSH) == 0U)
> > +               mtk_uart_apdma_write(c, VFF_FLUSH, VFF_FLUSH_B);
> > +}
> > +
> > +static void mtk_uart_apdma_start_rx(struct mtk_chan *c)
> > +{
> > +       struct mtk_uart_apdma_desc *d = c->desc;
> > +       unsigned int len, wg, rg;
> > +       int cnt;
> > +
> > +       if ((mtk_uart_apdma_read(c, VFF_VALID_SIZE) == 0U) ||
> > +               !d || !vchan_next_desc(&c->vc))
> > +               return;
> 
> If the current descriptor is not available, the hardware should be
> idle or stopped. so I think the condition can be removed or there is
> somewhere your handle descriptors incorrectly.

I will modify it in next version.

> 
> > +
> > +       len = c->cfg.src_port_window_size;
> > +       rg = mtk_uart_apdma_read(c, VFF_RPT);
> > +       wg = mtk_uart_apdma_read(c, VFF_WPT);
> > +       cnt = (wg & VFF_RING_SIZE) - (rg & VFF_RING_SIZE);
> 
> Is it possible that rg and wg would be greater than VFF_RING_SIZE?
> 

No.

> > +       /*
> > +        * The buffer is ring buffer. If wrap bit different,
> > +        * represents the start of the next cycle for WPT
> > +        */
> > +       if ((rg ^ wg) & VFF_RING_WRAP)
> > +               cnt += len;
> 
> Again, I am confused what size of VFF is.  Either VFF_RING_SIZE or
> c->cfg.dst_port_window_size?
> 
> > +
> > +       c->rx_status = d->avail_len - cnt;
> > +       mtk_uart_apdma_write(c, VFF_RPT, wg);
> > +
> > +       list_del(&d->vd.node);
> > +       vchan_cookie_complete(&d->vd);
> > +}
> > +
> > +static irqreturn_t mtk_uart_apdma_irq_handler(int irq, void *dev_id)
> > +{
> > +       struct dma_chan *chan = (struct dma_chan *)dev_id;
> > +       struct mtk_chan *c = to_mtk_uart_apdma_chan(chan);
> > +       struct mtk_uart_apdma_desc *d;
> > +       unsigned long flags;
> > +
> > +       spin_lock_irqsave(&c->vc.lock, flags);
> > +       if (c->dir == DMA_DEV_TO_MEM) {
> > +               mtk_uart_apdma_write(c, VFF_INT_FLAG, VFF_RX_INT_CLR_B);
> > +               mtk_uart_apdma_start_rx(c);
> > +       } else if (c->dir == DMA_MEM_TO_DEV) {
> > +               d = c->desc;
> > +
> > +               mtk_uart_apdma_write(c, VFF_INT_FLAG, VFF_TX_INT_CLR_B);
> > +
> > +               if (d->avail_len != 0U) {
> > +                       mtk_uart_apdma_start_tx(c);
> > +               } else {
> > +                       list_del(&d->vd.node);
> > +                       vchan_cookie_complete(&d->vd);
> > +               }
> > +       }
> > +       spin_unlock_irqrestore(&c->vc.lock, flags);
> > +
> > +       return IRQ_HANDLED;
> > +}
> > +
> > +static int mtk_uart_apdma_alloc_chan_resources(struct dma_chan *chan)
> > +{
> > +       struct mtk_uart_apdmadev *mtkd = to_mtk_uart_apdma_dev(chan->device);
> > +       struct mtk_chan *c = to_mtk_uart_apdma_chan(chan);
> > +       unsigned int tmp;
> > +       int ret;
> > +
> > +       pm_runtime_get_sync(mtkd->ddev.dev);
> 
> Add an error handling, something like
> 
> err = pm_runtime_get_sync(mtkd->ddev.dev);
> if (err < 0) {
> pm_runtime_put_noidle(dev);
> ...
> }
> 

I will modify it in next version.

> > +
> > +       mtk_uart_apdma_write(c, VFF_ADDR, 0);
> > +       mtk_uart_apdma_write(c, VFF_THRE, 0);
> > +       mtk_uart_apdma_write(c, VFF_LEN, 0);
> > +       mtk_uart_apdma_write(c, VFF_RST, VFF_WARM_RST_B);
> > +
> > +       ret = readx_poll_timeout(readl, c->base + VFF_EN, tmp, !tmp, 10, 100);
> > +       if (ret) {
> > +               dev_err(chan->device->dev, "dma reset: fail, timeout\n");
> > +               return ret;
> > +       }
> > +
> > +       if (!c->requested) {
> > +               c->requested = true;
> 
> The variable c->requested can be saved since the same channel
> shouldn't be requested more one time
> 
I will modify it in next version.

> > +               ret = request_irq(mtkd->dma_irq[chan->chan_id],
> > +                                 mtk_uart_apdma_irq_handler, IRQF_TRIGGER_NONE,
> > +                                 KBUILD_MODNAME, chan);
> > +               if (ret < 0) {
> > +                       dev_err(chan->device->dev, "Can't request dma IRQ\n");
> > +                       return -EINVAL;
> > +               }
> > +       }
> > +
> > +       if (mtkd->support_33bits)
> > +               mtk_uart_apdma_write(c, VFF_4G_SUPPORT, VFF_4G_SUPPORT_CLR_B);
> > +
> > +       return ret;
> > +}
> > +
> > +static void mtk_uart_apdma_free_chan_resources(struct dma_chan *chan)
> > +{
> > +       struct mtk_uart_apdmadev *mtkd = to_mtk_uart_apdma_dev(chan->device);
> > +       struct mtk_chan *c = to_mtk_uart_apdma_chan(chan);
> > +
> > +       if (c->requested) {
> 
> ditto as the above

I will modify it in next version.
> 
> > +               c->requested = false;
> > +               free_irq(mtkd->dma_irq[chan->chan_id], chan);
> > +       }
> > +
> > +       tasklet_kill(&c->vc.task);
> > +
> > +       vchan_free_chan_resources(&c->vc);
> > +
> > +       pm_runtime_put_sync(mtkd->ddev.dev);
> > +}
> > +
> > +static enum dma_status mtk_uart_apdma_tx_status(struct dma_chan *chan,
> > +                                        dma_cookie_t cookie,
> > +                                        struct dma_tx_state *txstate)
> > +{
> > +       struct mtk_chan *c = to_mtk_uart_apdma_chan(chan);
> > +       enum dma_status ret;
> > +
> > +       ret = dma_cookie_status(chan, cookie, txstate);
> > +
> > +       dma_set_residue(txstate, c->rx_status);
> > +
> 
> The handling is not enough. You should get the descriptor
> corresponding to the cookie and then calculate and return the
> ->tx_status by the descriptor
> 

Because UART can't get any interrupt except DMA interrupt. So in APDMA,
need notify UART to get data. And then descriptor will be released. So
Need keep the solution.

> > +       return ret;
> > +}
> > +
> > +static void mtk_uart_apdma_config_write(struct dma_chan *chan,
> > +                              struct dma_slave_config *cfg,
> > +                              enum dma_transfer_direction dir)
> > +{
> > +       struct mtk_chan *c = to_mtk_uart_apdma_chan(chan);
> > +       struct mtk_uart_apdmadev *mtkd =
> > +                               to_mtk_uart_apdma_dev(c->vc.chan.device);
> > +       unsigned int tmp;
> > +
> > +       if (mtk_uart_apdma_read(c, VFF_EN) == VFF_EN_B)
> > +               return;
> > +
> > +       c->dir = dir;
> 
> The direction is fixed by the device, I don't think it is required to
> keep it in a software state.

Need save it. Because RX and TX isn't all same.

> 
> > +
> > +       if (dir == DMA_DEV_TO_MEM) {
> > +               tmp = cfg->src_port_window_size;
> > +
> > +               mtk_uart_apdma_write(c, VFF_ADDR, cfg->src_addr);
> 
> That is wrong. ->src_addr is the physical address where DMA slave data
> should be read (RX),  not the memory address.
> 
> You should program the register VFF_ADDR and VFF_LEN by sg address and
> length from device_prep_slave_sg.

I will modify it in next version.
> 
> > +               mtk_uart_apdma_write(c, VFF_LEN, tmp);
> > +               mtk_uart_apdma_write(c, VFF_THRE, VFF_RX_THRE(tmp));
> > +               mtk_uart_apdma_write(c, VFF_INT_EN,
> > +                               VFF_RX_INT_EN0_B | VFF_RX_INT_EN1_B);
> > +               mtk_uart_apdma_write(c, VFF_RPT, 0);
> > +               mtk_uart_apdma_write(c, VFF_INT_FLAG, VFF_RX_INT_CLR_B);
> > +       } else if (dir == DMA_MEM_TO_DEV)       {
> > +               tmp = cfg->dst_port_window_size;
> > +
> > +               mtk_uart_apdma_write(c, VFF_ADDR, cfg->dst_addr);
> 
> That is also wrong. st_addr: this is the physical address where DMA
> slave data should be written (TX), not the memory address similar to
> the above explanation.

I will modify it in next version.
> 
> > +               mtk_uart_apdma_write(c, VFF_LEN, tmp);
> > +               mtk_uart_apdma_write(c, VFF_THRE, VFF_TX_THRE(tmp));
> > +               mtk_uart_apdma_write(c, VFF_WPT, 0);
> > +               mtk_uart_apdma_write(c, VFF_INT_FLAG, VFF_TX_INT_CLR_B);
> > +       }
> > +
> > +       mtk_uart_apdma_write(c, VFF_EN, VFF_EN_B);
> > +
> > +       if (mtkd->support_33bits)
> > +               mtk_uart_apdma_write(c, VFF_4G_SUPPORT, VFF_4G_SUPPORT_B);
> > +
> > +       if (mtk_uart_apdma_read(c, VFF_EN) != VFF_EN_B)
> > +               dev_err(chan->device->dev, "dir[%d] fail\n", dir);
> > +}
> > +
> > +/*
> > + * dmaengine_prep_slave_single will call the function. and sglen is 1.
> > + * 8250 uart using one ring buffer, and deal with one sg.
> > + */
> > +static struct dma_async_tx_descriptor *mtk_uart_apdma_prep_slave_sg
> > +       (struct dma_chan *chan, struct scatterlist *sgl,
> > +       unsigned int sglen, enum dma_transfer_direction dir,
> > +       unsigned long tx_flags, void *context)
> > +{
> > +       struct mtk_chan *c = to_mtk_uart_apdma_chan(chan);
> > +       struct mtk_uart_apdma_desc *d;
> > +
> > +       if (!is_slave_direction(dir))
> > +               return NULL;
> > +
> > +       mtk_uart_apdma_config_write(chan, &c->cfg, dir);
> > +
> > +       /* Now allocate and setup the descriptor */
> > +       d = kzalloc(sizeof(*d), GFP_ATOMIC);
> > +       if (!d)
> > +               return NULL;
> > +
> > +       /* sglen is 1 */
> > +       d->avail_len = sg_dma_len(sgl);
> > +       c->rx_status = d->avail_len;
> > +
> > +       return vchan_tx_prep(&c->vc, &d->vd, tx_flags);
> > +}
> > +
> > +static void mtk_uart_apdma_issue_pending(struct dma_chan *chan)
> > +{
> > +       struct mtk_chan *c = to_mtk_uart_apdma_chan(chan);
> > +       struct virt_dma_desc *vd;
> > +       unsigned long flags;
> > +
> > +       spin_lock_irqsave(&c->vc.lock, flags);
> > +       if (vchan_issue_pending(&c->vc)) {
> > +               vd = vchan_next_desc(&c->vc);
> > +               c->desc = to_mtk_uart_apdma_desc(&vd->tx);
> > +       }
> > +
> > +       if (c->dir == DMA_DEV_TO_MEM)
> > +               mtk_uart_apdma_start_rx(c);
> > +       else if (c->dir == DMA_MEM_TO_DEV)
> > +               mtk_uart_apdma_start_tx(c);
> > +
> > +       spin_unlock_irqrestore(&c->vc.lock, flags);
> > +}
> > +
> > +static int mtk_uart_apdma_slave_config(struct dma_chan *chan,
> > +                                  struct dma_slave_config *config)
> > +{
> > +       struct mtk_chan *c = to_mtk_uart_apdma_chan(chan);
> > +
> > +       memcpy(&c->cfg, config, sizeof(*config));
> > +
> > +       return 0;
> > +}
> > +
> > +static int mtk_uart_apdma_terminate_all(struct dma_chan *chan)
> > +{
> > +       struct mtk_chan *c = to_mtk_uart_apdma_chan(chan);
> > +       unsigned long flags;
> > +       unsigned int tmp;
> > +       int ret;
> > +
> > +       spin_lock_irqsave(&c->vc.lock, flags);
> > +
> > +       mtk_uart_apdma_write(c, VFF_FLUSH, VFF_FLUSH_B);
> > +       /* Wait 1sec for flush, can't sleep */
> > +       ret = readx_poll_timeout(readl, c->base + VFF_FLUSH, tmp,
> > +                       tmp != VFF_FLUSH_B, 0, 1000000);
> 
> It is extremely bad pending so long is in the spin_lock_irqsave

I will modify it in next version.
> 
> > +       if (ret)
> > +               dev_err(c->vc.chan.device->dev, "flush: fail, debug=0x%x\n",
> > +                       mtk_uart_apdma_read(c, VFF_DEBUG_STATUS));
> > +
> > +       /* set stop as 1 -> wait until en is 0 -> set stop as 0 */
> > +       mtk_uart_apdma_write(c, VFF_STOP, VFF_STOP_B);
> > +       ret = readx_poll_timeout(readl, c->base + VFF_EN, tmp, !tmp, 10, 100);
> > +       if (ret)
> > +               dev_err(c->vc.chan.device->dev, "stop: fail, debug=0x%x\n",
> > +                       mtk_uart_apdma_read(c, VFF_DEBUG_STATUS));
> > +
> > +       mtk_uart_apdma_write(c, VFF_STOP, VFF_STOP_CLR_B);
> > +       mtk_uart_apdma_write(c, VFF_INT_EN, VFF_INT_EN_CLR_B);
> > +
> > +       if (c->dir == DMA_DEV_TO_MEM)
> > +               mtk_uart_apdma_write(c, VFF_INT_FLAG, VFF_RX_INT_CLR_B);
> > +       else if (c->dir == DMA_MEM_TO_DEV)
> > +               mtk_uart_apdma_write(c, VFF_INT_FLAG, VFF_TX_INT_CLR_B);
> > +
> > +       spin_unlock_irqrestore(&c->vc.lock, flags);
> > +
> > +       return 0;
> > +}
> > +
> > +static int mtk_uart_apdma_device_pause(struct dma_chan *chan)
> > +{
> > +       /* just for check caps pass */
> > +       dev_err(chan->device->dev, "Pause can't support\n");
> > +
> 
> If the device can't support hardware pause, we can do it as a software
> pause in an implementation based on vdesc.
> 

ok, i will try it.

> > +       return 0;
> > +}
> > +
> > +static void mtk_uart_apdma_free(struct mtk_uart_apdmadev *mtkd)
> > +{
> > +       while (!list_empty(&mtkd->ddev.channels)) {
> > +               struct mtk_chan *c = list_first_entry(&mtkd->ddev.channels,
> > +                       struct mtk_chan, vc.chan.device_node);
> > +
> > +               list_del(&c->vc.chan.device_node);
> > +               tasklet_kill(&c->vc.task);
> > +       }
> > +}
> > +
> > +static const struct of_device_id mtk_uart_apdma_match[] = {
> > +       { .compatible = "mediatek,mt6577-uart-dma", },
> > +       { /* sentinel */ },
> > +};
> > +MODULE_DEVICE_TABLE(of, mtk_uart_apdma_match);
> > +
> > +static int mtk_uart_apdma_probe(struct platform_device *pdev)
> > +{
> > +       struct device_node *np = pdev->dev.of_node;
> > +       struct mtk_uart_apdmadev *mtkd;
> > +       struct resource *res;
> > +       struct mtk_chan *c;
> > +       int bit_mask = 32, rc;
> > +       unsigned int i;
> > +
> > +       mtkd = devm_kzalloc(&pdev->dev, sizeof(*mtkd), GFP_KERNEL);
> > +       if (!mtkd)
> > +               return -ENOMEM;
> > +
> > +       mtkd->clk = devm_clk_get(&pdev->dev, NULL);
> > +       if (IS_ERR(mtkd->clk)) {
> > +               dev_err(&pdev->dev, "No clock specified\n");
> > +               rc = PTR_ERR(mtkd->clk);
> > +               return rc;
> > +       }
> > +
> > +       if (of_property_read_bool(np, "mediatek,dma-33bits"))
> > +               mtkd->support_33bits = true;
> > +
> > +       if (mtkd->support_33bits)
> > +               bit_mask = 33;
> > +
> > +       rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(bit_mask));
> > +       if (rc)
> > +               return rc;
> > +
> > +       dma_cap_set(DMA_SLAVE, mtkd->ddev.cap_mask);
> > +       mtkd->ddev.device_alloc_chan_resources =
> > +                               mtk_uart_apdma_alloc_chan_resources;
> > +       mtkd->ddev.device_free_chan_resources =
> > +                               mtk_uart_apdma_free_chan_resources;
> > +       mtkd->ddev.device_tx_status = mtk_uart_apdma_tx_status;
> > +       mtkd->ddev.device_issue_pending = mtk_uart_apdma_issue_pending;
> > +       mtkd->ddev.device_prep_slave_sg = mtk_uart_apdma_prep_slave_sg;
> > +       mtkd->ddev.device_config = mtk_uart_apdma_slave_config;
> > +       mtkd->ddev.device_pause = mtk_uart_apdma_device_pause;
> > +       mtkd->ddev.device_terminate_all = mtk_uart_apdma_terminate_all;
> > +       mtkd->ddev.src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE);
> > +       mtkd->ddev.dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE);
> > +       mtkd->ddev.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> > +       mtkd->ddev.residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;
> > +       mtkd->ddev.dev = &pdev->dev;
> > +       INIT_LIST_HEAD(&mtkd->ddev.channels);
> > +
> > +       mtkd->dma_requests = MTK_UART_APDMA_NR_VCHANS;
> > +       if (of_property_read_u32(np, "dma-requests", &mtkd->dma_requests)) {
> > +               dev_info(&pdev->dev,
> > +                        "Using %u as missing dma-requests property\n",
> > +                        MTK_UART_APDMA_NR_VCHANS);
> > +       }
> > +
> > +       mtkd->dma_irq = devm_kcalloc(&pdev->dev, mtkd->dma_requests,
> > +                                sizeof(*mtkd->dma_irq), GFP_KERNEL);
> > +       if (!mtkd->dma_irq)
> > +               return -ENOMEM;
> > +
> > +       for (i = 0; i < mtkd->dma_requests; i++) {
> > +               c = devm_kzalloc(mtkd->ddev.dev, sizeof(*c), GFP_KERNEL);
> > +               if (!c) {
> > +                       rc = -ENODEV;
> > +                       goto err_no_dma;
> > +               }
> > +
> > +               res = platform_get_resource(pdev, IORESOURCE_MEM, i);
> > +               if (!res) {
> > +                       rc = -ENODEV;
> > +                       goto err_no_dma;
> > +               }
> > +
> > +               c->base = devm_ioremap_resource(&pdev->dev, res);
> > +               if (IS_ERR(c->base)) {
> > +                       rc = PTR_ERR(c->base);
> > +                       goto err_no_dma;
> > +               }
> > +               c->requested = false;
> > +               c->vc.desc_free = mtk_uart_apdma_desc_free;
> > +               vchan_init(&c->vc, &mtkd->ddev);
> > +
> > +               mtkd->dma_irq[i] = platform_get_irq(pdev, i);
> > +               if ((int)mtkd->dma_irq[i] < 0) {
> > +                       dev_err(&pdev->dev, "failed to get IRQ[%d]\n", i);
> > +                       rc = -EINVAL;
> > +                       goto err_no_dma;
> > +               }
> > +       }
> > +
> > +       pm_runtime_enable(&pdev->dev);
> > +       pm_runtime_set_active(&pdev->dev);
> > +
> > +       rc = dma_async_device_register(&mtkd->ddev);
> > +       if (rc)
> > +               goto rpm_disable;
> > +
> > +       platform_set_drvdata(pdev, mtkd);
> > +
> > +       /* Device-tree DMA controller registration */
> > +       rc = of_dma_controller_register(np, of_dma_xlate_by_chan_id, mtkd);
> > +       if (rc)
> > +               goto dma_remove;
> > +
> > +       return rc;
> > +
> > +dma_remove:
> > +       dma_async_device_unregister(&mtkd->ddev);
> > +rpm_disable:
> > +       pm_runtime_disable(&pdev->dev);
> > +err_no_dma:
> > +       mtk_uart_apdma_free(mtkd);
> > +       return rc;
> > +}
> > +
> > +static int mtk_uart_apdma_remove(struct platform_device *pdev)
> > +{
> > +       struct mtk_uart_apdmadev *mtkd = platform_get_drvdata(pdev);
> > +
> > +       if (pdev->dev.of_node)
> > +               of_dma_controller_free(pdev->dev.of_node);
> > +
> > +       pm_runtime_disable(&pdev->dev);
> > +       pm_runtime_put_noidle(&pdev->dev);
> 
> That pm_runtime_put_noidle should be removed or it causes an
> inconsistency with the probe handler.
> 

Ok, I will remove it.

> > +
> > +       dma_async_device_unregister(&mtkd->ddev);
> > +       mtk_uart_apdma_free(mtkd);
> > +
> > +       return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM_SLEEP
> > +static int mtk_uart_apdma_suspend(struct device *dev)
> > +{
> > +       struct mtk_uart_apdmadev *mtkd = dev_get_drvdata(dev);
> > +
> > +       if (!pm_runtime_suspended(dev))
> > +               clk_disable_unprepare(mtkd->clk);
> > +
> > +       return 0;
> > +}
> > +
> > +static int mtk_uart_apdma_resume(struct device *dev)
> > +{
> > +       int ret;
> > +       struct mtk_uart_apdmadev *mtkd = dev_get_drvdata(dev);
> > +
> > +       if (!pm_runtime_suspended(dev)) {
> > +               ret = clk_prepare_enable(mtkd->clk);
> > +               if (ret)
> > +                       return ret;
> > +       }
> > +
> > +       return 0;
> > +}
> > +#endif /* CONFIG_PM_SLEEP */
> > +
> > +#ifdef CONFIG_PM
> > +static int mtk_uart_apdma_runtime_suspend(struct device *dev)
> > +{
> > +       struct mtk_uart_apdmadev *mtkd = dev_get_drvdata(dev);
> > +
> > +       clk_disable_unprepare(mtkd->clk);
> > +
> > +       return 0;
> > +}
> > +
> > +static int mtk_uart_apdma_runtime_resume(struct device *dev)
> > +{
> > +       int ret;
> > +       struct mtk_uart_apdmadev *mtkd = dev_get_drvdata(dev);
> > +
> > +       ret = clk_prepare_enable(mtkd->clk);
> > +       if (ret)
> > +               return ret;
> > +
> > +       return 0;
> > +}
> > +#endif /* CONFIG_PM */
> > +
> > +static const struct dev_pm_ops mtk_uart_apdma_pm_ops = {
> > +       SET_SYSTEM_SLEEP_PM_OPS(mtk_uart_apdma_suspend, mtk_uart_apdma_resume)
> > +       SET_RUNTIME_PM_OPS(mtk_uart_apdma_runtime_suspend,
> > +                          mtk_uart_apdma_runtime_resume, NULL)
> > +};
> 
> It probably causes a build error when CONFIG_PM is not enabled.
> and use a UNIVERSAL_DEV_PM_OPS because the runtime suspend/resume and
> system suspend/resume for the dma are
> almost the same.
> 

I remember that these had test. It's build pass.

> > +
> > +static struct platform_driver mtk_uart_apdma_driver = {
> > +       .probe  = mtk_uart_apdma_probe,
> > +       .remove = mtk_uart_apdma_remove,
> > +       .driver = {
> > +               .name           = KBUILD_MODNAME,
> > +               .pm             = &mtk_uart_apdma_pm_ops,
> > +               .of_match_table = of_match_ptr(mtk_uart_apdma_match),
> > +       },
> > +};
> > +
> > +module_platform_driver(mtk_uart_apdma_driver);
> > +
> > +MODULE_DESCRIPTION("MediaTek UART APDMA Controller Driver");
> > +MODULE_AUTHOR("Long Cheng <long.cheng@mediatek.com>");
> > +MODULE_LICENSE("GPL v2");
> > +
> > --
> > 1.7.9.5
> >

^ permalink raw reply

* Re: [PATCH] PM / core: Propagate dev->power.wakeup_path when no callbacks
From: Alexandre Torgue @ 2019-04-10 15:28 UTC (permalink / raw)
  To: Ulf Hansson, Rafael J . Wysocki, linux-pm
  Cc: Geert Uytterhoeven, Loic Pallardy, Linus Walleij, Rob Herring,
	Greg Kroah-Hartman, Johan Hovold, linux-gpio, linux-serial,
	linux-kernel
In-Reply-To: <20190410095516.6170-1-ulf.hansson@linaro.org>

Hi Ulf

On 4/10/19 11:55 AM, Ulf Hansson wrote:
> The dev->power.direct_complete flag may become set in device_prepare() in
> case the device don't have any PM callbacks (dev->power.no_pm_callbacks is
> set). This leads to a broken behaviour, when there is child having wakeup
> enabled and relies on its parent to be used in the wakeup path.
> 
> More precisely, when the direct complete path becomes selected for the
> child in __device_suspend(), the propagation of the dev->power.wakeup_path
> becomes skipped as well.
> 
> Let's address this problem, by checking if the device is a part the wakeup
> path or has wakeup enabled, then prevent the direct complete path from
> being used.
> 
> Reported-by: Loic Pallardy <loic.pallardy@st.com>
> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
> ---
> 

Thanks Ulf for this patch. It'll avoid to have dirty hack in serial 
suspend callback (at least for stm32). I just tested it on stm32 kernel 
v4.19 (which embeds all our genpd based power features). Replacing
device_may_wakeup(dev) by
device_may_wakeup(dev) || dev->power.wakeup_path) then dirty hack to 
test ttydev wakeup flag in stm32 usart driver is no more needed.

So you can add my Tested-by

Regards
Alex


> More background:
> 
> This problem was reported by Loic Pallardy, offlist, while he was working
> on enabling wakeup for a tty serial console driver.
> 
> When I looked more closely, I noticed that uart_suspend_port() calls
> device_may_wakeup() for the tty child devices, and then also the used serial
> driver check its device (parent) for device_may_wakeup(). To me this looks like
> workarounds to fix a behaviour that really should be dealt with from the PM
> core, no matter of whether the child have PM callbacks assigned or not.
> 
> In other words, it seems like the serial driver(s) should be checking the
> wakeup_path flag for the parent, solely, instead.
> 
> I haven't digested further behaviours for other subsystem, but recently
> reviewed a patch for a gpio driver [1], that seems to be suffering from the
> similar problems.
> 
> Kind regards
> Ulf Hansson
> 
> [1]
> https://lkml.org/lkml/2019/4/4/1283
> 
> ---
>   drivers/base/power/main.c | 4 ++++
>   1 file changed, 4 insertions(+)
> 
> diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
> index 41eba82ee7b9..f9cfdeee8288 100644
> --- a/drivers/base/power/main.c
> +++ b/drivers/base/power/main.c
> @@ -1747,6 +1747,10 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
>   	if (dev->power.syscore)
>   		goto Complete;
>   
> +	/* Avoid direct_complete, to let wakeup_path being propagated. */
> +	if (device_may_wakeup(dev) || dev->power.wakeup_path)
> +		dev->power.direct_complete = false;
> +
>   	if (dev->power.direct_complete) {
>   		if (pm_runtime_status_suspended(dev)) {
>   			pm_runtime_disable(dev);
> 

^ permalink raw reply

* [PATCH] PM / core: Propagate dev->power.wakeup_path when no callbacks
From: Ulf Hansson @ 2019-04-10  9:55 UTC (permalink / raw)
  To: Rafael J . Wysocki, linux-pm
  Cc: Geert Uytterhoeven, Loic Pallardy, Linus Walleij,
	Alexandre Torgue, Rob Herring, Greg Kroah-Hartman, Johan Hovold,
	linux-gpio, linux-serial, linux-kernel, Ulf Hansson

The dev->power.direct_complete flag may become set in device_prepare() in
case the device don't have any PM callbacks (dev->power.no_pm_callbacks is
set). This leads to a broken behaviour, when there is child having wakeup
enabled and relies on its parent to be used in the wakeup path.

More precisely, when the direct complete path becomes selected for the
child in __device_suspend(), the propagation of the dev->power.wakeup_path
becomes skipped as well.

Let's address this problem, by checking if the device is a part the wakeup
path or has wakeup enabled, then prevent the direct complete path from
being used.

Reported-by: Loic Pallardy <loic.pallardy@st.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
---

More background:

This problem was reported by Loic Pallardy, offlist, while he was working
on enabling wakeup for a tty serial console driver.

When I looked more closely, I noticed that uart_suspend_port() calls
device_may_wakeup() for the tty child devices, and then also the used serial
driver check its device (parent) for device_may_wakeup(). To me this looks like
workarounds to fix a behaviour that really should be dealt with from the PM
core, no matter of whether the child have PM callbacks assigned or not.

In other words, it seems like the serial driver(s) should be checking the
wakeup_path flag for the parent, solely, instead.

I haven't digested further behaviours for other subsystem, but recently
reviewed a patch for a gpio driver [1], that seems to be suffering from the
similar problems.

Kind regards
Ulf Hansson

[1]
https://lkml.org/lkml/2019/4/4/1283

---
 drivers/base/power/main.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 41eba82ee7b9..f9cfdeee8288 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -1747,6 +1747,10 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
 	if (dev->power.syscore)
 		goto Complete;
 
+	/* Avoid direct_complete, to let wakeup_path being propagated. */
+	if (device_may_wakeup(dev) || dev->power.wakeup_path)
+		dev->power.direct_complete = false;
+
 	if (dev->power.direct_complete) {
 		if (pm_runtime_status_suspended(dev)) {
 			pm_runtime_disable(dev);
-- 
2.17.1

^ permalink raw reply related

* Re: [RFC v2 00/11] DVFS in the OPP core
From: Viresh Kumar @ 2019-04-10  3:51 UTC (permalink / raw)
  To: Rajendra Nayak
  Cc: linux-kernel, linux-arm-msm, linux-pm, linux-serial, linux-spi,
	dri-devel, linux-scsi, swboyd, ulf.hansson, dianders, rafael
In-Reply-To: <20190320094918.20234-1-rnayak@codeaurora.org>

On 20-03-19, 15:19, Rajendra Nayak wrote:
> This is a v2 of the RFC posted earlier by Stephen Boyd [1]
> 
> As part of v2 I still follow the same approach of dev_pm_opp_set_rate()
> API using clk framework to round the frequency passed and making it
> accept 0 as a valid frequency indicating the frequency isn't required
> anymore. It just has a few more drivers converted to use this approach
> like dsi/dpu and ufs.
> ufs demonstrates the case of having to handle multiple power domains, one
> of which is scalable.

I though we discussed about enabling/disabling clk as well from OPP core to
simply driver ? I was expecting that to be part of this version :(

-- 
viresh

^ permalink raw reply

* Re: [RFC v2 09/11] drm/msm/dpu: Use OPP API to set clk/perf state
From: Viresh Kumar @ 2019-04-10  3:49 UTC (permalink / raw)
  To: Rajendra Nayak
  Cc: linux-kernel, linux-arm-msm, linux-pm, linux-serial, linux-spi,
	dri-devel, linux-scsi, swboyd, ulf.hansson, dianders, rafael
In-Reply-To: <20190320094918.20234-10-rnayak@codeaurora.org>

On 20-03-19, 15:19, Rajendra Nayak wrote:
> On some qualcomm platforms DPU needs to express a perforamnce state
> requirement on a power domain depennding on the clock rates.
> Use OPP table from DT to register with OPP framework and use
> dev_pm_opp_set_rate() to set the clk/perf state.
> 
> Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
> ---
>  drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c | 7 ++++++-
>  drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c       | 9 +++++++++
>  2 files changed, 15 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c
> index 9f20f397f77d..db21a86b242b 100644
> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c
> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c
> @@ -15,6 +15,7 @@
>  #include <linux/debugfs.h>
>  #include <linux/errno.h>
>  #include <linux/mutex.h>
> +#include <linux/pm_opp.h>
>  #include <linux/sort.h>
>  #include <linux/clk.h>
>  #include <linux/bitmap.h>
> @@ -298,7 +299,11 @@ static int _dpu_core_perf_set_core_clk_rate(struct dpu_kms *kms, u64 rate)
>  		rate = core_clk->max_rate;
>  
>  	core_clk->rate = rate;
> -	return msm_dss_clk_set_rate(core_clk, 1);
> +
> +	if (dev_pm_opp_get_opp_table(&kms->pdev->dev))

This takes a reference of the OPP table, you need to call put thing as well to
balance it off.

> +		return dev_pm_opp_set_rate(&kms->pdev->dev, core_clk->rate);
> +	else
> +		return msm_dss_clk_set_rate(core_clk, 1);
>  }
-- 
viresh

^ permalink raw reply

* [PATCH v2 4/4] Input: add IOC3 serio driver
From: Thomas Bogendoerfer @ 2019-04-09 15:46 UTC (permalink / raw)
  To: Ralf Baechle, Paul Burton, James Hogan, Dmitry Torokhov,
	Lee Jones, David S. Miller, Alessandro Zummo, Alexandre Belloni,
	Greg Kroah-Hartman, Jiri Slaby, linux-mips, linux-kernel,
	linux-input, netdev, linux-rtc, linux-serial
In-Reply-To: <20190409154610.6735-1-tbogendoerfer@suse.de>

This patch adds a platform driver for supporting keyboard and mouse
interface of SGI IOC3 chips.

Signed-off-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
---
 drivers/input/serio/Kconfig   |  10 +++
 drivers/input/serio/Makefile  |   1 +
 drivers/input/serio/ioc3kbd.c | 158 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 169 insertions(+)
 create mode 100644 drivers/input/serio/ioc3kbd.c

diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index c9c7224d5ae0..2db47c9ff9c2 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -164,6 +164,16 @@ config SERIO_MACEPS2
 	  To compile this driver as a module, choose M here: the
 	  module will be called maceps2.
 
+config SERIO_SGI_IOC3
+	tristate "SGI IOC3 PS/2 controller"
+	depends on SGI_MFD_IOC3
+	help
+	  Say Y here if you have an SGI Onyx2, SGI Octane or IOC3 PCI card
+	  and you want to attach and use a keyboard, mouse, or both.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ioc3kbd.
+
 config SERIO_LIBPS2
 	tristate "PS/2 driver library"
 	depends on SERIO_I8042 || SERIO_I8042=n
diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
index 67950a5ccb3f..6d97bad7b844 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_HIL_MLC)		+= hp_sdc_mlc.o hil_mlc.o
 obj-$(CONFIG_SERIO_PCIPS2)	+= pcips2.o
 obj-$(CONFIG_SERIO_PS2MULT)	+= ps2mult.o
 obj-$(CONFIG_SERIO_MACEPS2)	+= maceps2.o
+obj-$(CONFIG_SERIO_SGI_IOC3)	+= ioc3kbd.o
 obj-$(CONFIG_SERIO_LIBPS2)	+= libps2.o
 obj-$(CONFIG_SERIO_RAW)		+= serio_raw.o
 obj-$(CONFIG_SERIO_AMS_DELTA)	+= ams_delta_serio.o
diff --git a/drivers/input/serio/ioc3kbd.c b/drivers/input/serio/ioc3kbd.c
new file mode 100644
index 000000000000..26fcf57465d6
--- /dev/null
+++ b/drivers/input/serio/ioc3kbd.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SGI IOC3 PS/2 controller driver for linux
+ *
+ * Copyright (C) 2019 Thomas Bogendoerfer <tbogendoerfer@suse.de>
+ *
+ * Based on code Copyright (C) 2005 Stanislaw Skowronek <skylark@unaligned.org>
+ *               Copyright (C) 2009 Johannes Dickgreber <tanzy@gmx.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/serio.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <asm/sn/ioc3.h>
+
+struct ioc3kbd_data {
+	struct ioc3_serioregs __iomem *regs;
+	struct serio *kbd, *aux;
+};
+
+static int ioc3kbd_write(struct serio *dev, u8 val)
+{
+	struct ioc3kbd_data *d = dev->port_data;
+	unsigned long timeout = 0;
+	u32 mask;
+
+	mask = (dev == d->aux) ? KM_CSR_M_WRT_PEND : KM_CSR_K_WRT_PEND;
+	while ((readl(&d->regs->km_csr) & mask) && (timeout < 1000)) {
+		udelay(100);
+		timeout++;
+	}
+
+	if (timeout >= 1000)
+		return -1;
+
+	writel(val, dev == d->aux ?  &d->regs->m_wd : &d->regs->k_wd);
+
+	return 0;
+}
+
+static irqreturn_t ioc3kbd_intr(int itq, void *dev_id)
+{
+	struct ioc3kbd_data *d = dev_id;
+	u32 data_k, data_m;
+
+	data_k = readl(&d->regs->k_rd);
+	data_m = readl(&d->regs->m_rd);
+
+	if (data_k & KM_RD_VALID_0)
+		serio_interrupt(d->kbd,
+		(data_k >> KM_RD_DATA_0_SHIFT) & 0xff, 0);
+	if (data_k & KM_RD_VALID_1)
+		serio_interrupt(d->kbd,
+		(data_k >> KM_RD_DATA_1_SHIFT) & 0xff, 0);
+	if (data_k & KM_RD_VALID_2)
+		serio_interrupt(d->kbd,
+		(data_k >> KM_RD_DATA_2_SHIFT) & 0xff, 0);
+	if (data_m & KM_RD_VALID_0)
+		serio_interrupt(d->aux,
+		(data_m >> KM_RD_DATA_0_SHIFT) & 0xff, 0);
+	if (data_m & KM_RD_VALID_1)
+		serio_interrupt(d->aux,
+		(data_m >> KM_RD_DATA_1_SHIFT) & 0xff, 0);
+	if (data_m & KM_RD_VALID_2)
+		serio_interrupt(d->aux,
+		(data_m >> KM_RD_DATA_2_SHIFT) & 0xff, 0);
+
+	return 0;
+}
+
+static int ioc3kbd_probe(struct platform_device *pdev)
+{
+	struct ioc3_serioregs __iomem *regs;
+	struct device *dev = &pdev->dev;
+	struct ioc3kbd_data *d;
+	struct serio *sk, *sa;
+	struct resource *mem;
+	int irq, ret;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regs = devm_ioremap_resource(&pdev->dev, mem);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return -ENXIO;
+
+	d = devm_kzalloc(&pdev->dev, sizeof(struct ioc3kbd_data), GFP_KERNEL);
+	if (!d)
+		return -ENOMEM;
+
+	ret = devm_request_irq(&pdev->dev, irq, ioc3kbd_intr, IRQF_SHARED,
+			       "ioc3-kbd", d);
+	if (ret) {
+		dev_err(&pdev->dev, "could not request IRQ %d\n", irq);
+		return ret;
+	}
+
+	sk = kzalloc(sizeof(struct serio), GFP_KERNEL);
+	if (!sk)
+		return -ENOMEM;
+
+	sa = kzalloc(sizeof(struct serio), GFP_KERNEL);
+	if (!sa) {
+		kfree(sk);
+		return -ENOMEM;
+	}
+
+	sk->id.type = SERIO_8042;
+	sk->write = ioc3kbd_write;
+	snprintf(sk->name, sizeof(sk->name), "IOC3 keyboard %d", pdev->id);
+	snprintf(sk->phys, sizeof(sk->phys), "ioc3/serio%dkbd", pdev->id);
+	sk->port_data = d;
+	sk->dev.parent = &pdev->dev;
+
+	sa->id.type = SERIO_8042;
+	sa->write = ioc3kbd_write;
+	snprintf(sa->name, sizeof(sa->name), "IOC3 auxiliary %d", pdev->id);
+	snprintf(sa->phys, sizeof(sa->phys), "ioc3/serio%daux", pdev->id);
+	sa->port_data = d;
+	sa->dev.parent = dev;
+
+	d->regs = regs;
+	d->kbd = sk;
+	d->aux = sa;
+
+	platform_set_drvdata(pdev, d);
+	serio_register_port(d->kbd);
+	serio_register_port(d->aux);
+	return 0;
+}
+
+static int ioc3kbd_remove(struct platform_device *pdev)
+{
+	struct ioc3kbd_data *d = platform_get_drvdata(pdev);
+
+	serio_unregister_port(d->kbd);
+	serio_unregister_port(d->aux);
+	return 0;
+}
+
+static struct platform_driver ioc3kbd_driver = {
+	.probe          = ioc3kbd_probe,
+	.remove         = ioc3kbd_remove,
+	.driver = {
+		.name = "ioc3-kbd",
+	},
+};
+module_platform_driver(ioc3kbd_driver);
+
+MODULE_AUTHOR("Thomas Bogendoerfer <tbogendoerfer@suse.de>");
+MODULE_DESCRIPTION("SGI IOC3 serio driver");
+MODULE_LICENSE("GPL");
-- 
2.13.7

^ permalink raw reply related

* [PATCH v2 3/4] MIPS: SGI-IP27: fix readb/writeb addressing
From: Thomas Bogendoerfer @ 2019-04-09 15:46 UTC (permalink / raw)
  To: Ralf Baechle, Paul Burton, James Hogan, Dmitry Torokhov,
	Lee Jones, David S. Miller, Alessandro Zummo, Alexandre Belloni,
	Greg Kroah-Hartman, Jiri Slaby, linux-mips, linux-kernel,
	linux-input, netdev, linux-rtc, linux-serial
In-Reply-To: <20190409154610.6735-1-tbogendoerfer@suse.de>

Our chosen byte swapping, which is what firmware already uses, is to
do readl/writel by normal lw/sw intructions (data invariance). This
also means we need to mangle addresses for u8 and u16 accesses. The
mangling for 16bit has been done aready, but 8bit one was missing.
Correcting this causes different addresses for accesses to the
SuperIO and local bus of the IOC3 chip. This is fixed by changing
byte order in ioc3 and m48rtc_rtc structs.

Signed-off-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
---
 arch/mips/include/asm/mach-ip27/mangle-port.h |   2 +-
 arch/mips/include/asm/sn/ioc3.h               | 198 +++++++++++++-------------
 arch/mips/sgi-ip27/ip27-console.c             |   5 +-
 drivers/rtc/rtc-m48t35.c                      |  11 ++
 drivers/tty/serial/8250/8250_ioc3.c           |   4 +-
 5 files changed, 114 insertions(+), 106 deletions(-)

diff --git a/arch/mips/include/asm/mach-ip27/mangle-port.h b/arch/mips/include/asm/mach-ip27/mangle-port.h
index f6e4912ea062..7771ae0f3971 100644
--- a/arch/mips/include/asm/mach-ip27/mangle-port.h
+++ b/arch/mips/include/asm/mach-ip27/mangle-port.h
@@ -8,7 +8,7 @@
 #ifndef __ASM_MACH_IP27_MANGLE_PORT_H
 #define __ASM_MACH_IP27_MANGLE_PORT_H
 
-#define __swizzle_addr_b(port)	(port)
+#define __swizzle_addr_b(port)	((port) ^ 3)
 #define __swizzle_addr_w(port)	((port) ^ 2)
 #define __swizzle_addr_l(port)	(port)
 #define __swizzle_addr_q(port)	(port)
diff --git a/arch/mips/include/asm/sn/ioc3.h b/arch/mips/include/asm/sn/ioc3.h
index 69069f420930..059885d736de 100644
--- a/arch/mips/include/asm/sn/ioc3.h
+++ b/arch/mips/include/asm/sn/ioc3.h
@@ -10,35 +10,35 @@
 
 /* serial port register map */
 struct ioc3_serialregs {
-	uint32_t	sscr;
-	uint32_t	stpir;
-	uint32_t	stcir;
-	uint32_t	srpir;
-	uint32_t	srcir;
-	uint32_t	srtr;
-	uint32_t	shadow;
+	u32	sscr;
+	u32	stpir;
+	u32	stcir;
+	u32	srpir;
+	u32	srcir;
+	u32	srtr;
+	u32	shadow;
 };
 
 /* SUPERIO uart register map */
 struct ioc3_uartregs {
+	u8	iu_lcr;
 	union {
-		char	rbr;	/* read only, DLAB == 0 */
-		char	thr;	/* write only, DLAB == 0 */
-		char	dll;	/* DLAB == 1 */
-	} u1;
+		u8	iir;	/* read only */
+		u8	fcr;	/* write only */
+	};
 	union {
-		char	ier;	/* DLAB == 0 */
-		char	dlm;	/* DLAB == 1 */
-	} u2;
+		u8	ier;	/* DLAB == 0 */
+		u8	dlm;	/* DLAB == 1 */
+	};
 	union {
-		char	iir;	/* read only */
-		char	fcr;	/* write only */
-	} u3;
-	char	iu_lcr;
-	char	iu_mcr;
-	char	iu_lsr;
-	char	iu_msr;
-	char	iu_scr;
+		u8	rbr;	/* read only, DLAB == 0 */
+		u8	thr;	/* write only, DLAB == 0 */
+		u8	dll;	/* DLAB == 1 */
+	} u1;
+	u8	iu_scr;
+	u8	iu_msr;
+	u8	iu_lsr;
+	u8	iu_mcr;
 };
 
 #define iu_rbr u1.rbr
@@ -50,122 +50,122 @@ struct ioc3_uartregs {
 #define iu_fcr u3.fcr
 
 struct ioc3_sioregs {
-	char	fill[0x141];	/* starts at 0x141 */
+	u8	fill[0x141];	/* starts at 0x141 */
 
-	char	uartc;
-	char	kbdcg;
+	u8	kbdcg;
+	u8	uartc;
 
-	char	fill0[0x150 - 0x142 - 1];
+	u8	fill0[0x151 - 0x142 - 1];
 
-	char	pp_data;
-	char	pp_dsr;
-	char	pp_dcr;
+	u8	pp_dcr;
+	u8	pp_dsr;
+	u8	pp_data;
 
-	char	fill1[0x158 - 0x152 - 1];
+	u8	fill1[0x159 - 0x153 - 1];
 
-	char	pp_fifa;
-	char	pp_cfgb;
-	char	pp_ecr;
+	u8	pp_ecr;
+	u8	pp_cfgb;
+	u8	pp_fifa;
 
-	char	fill2[0x168 - 0x15a - 1];
+	u8	fill2[0x16a - 0x15b - 1];
 
-	char	rtcad;
-	char	rtcdat;
+	u8	rtcdat;
+	u8	rtcad;
 
-	char	fill3[0x170 - 0x169 - 1];
+	u8	fill3[0x170 - 0x16b - 1];
 
 	struct ioc3_uartregs	uartb;	/* 0x20170  */
 	struct ioc3_uartregs	uarta;	/* 0x20178  */
 };
 
 struct ioc3_ethregs {
-	uint32_t	emcr;		/* 0x000f0  */
-	uint32_t	eisr;		/* 0x000f4  */
-	uint32_t	eier;		/* 0x000f8  */
-	uint32_t	ercsr;		/* 0x000fc  */
-	uint32_t	erbr_h;		/* 0x00100  */
-	uint32_t	erbr_l;		/* 0x00104  */
-	uint32_t	erbar;		/* 0x00108  */
-	uint32_t	ercir;		/* 0x0010c  */
-	uint32_t	erpir;		/* 0x00110  */
-	uint32_t	ertr;		/* 0x00114  */
-	uint32_t	etcsr;		/* 0x00118  */
-	uint32_t	ersr;		/* 0x0011c  */
-	uint32_t	etcdc;		/* 0x00120  */
-	uint32_t	ebir;		/* 0x00124  */
-	uint32_t	etbr_h;		/* 0x00128  */
-	uint32_t	etbr_l;		/* 0x0012c  */
-	uint32_t	etcir;		/* 0x00130  */
-	uint32_t	etpir;		/* 0x00134  */
-	uint32_t	emar_h;		/* 0x00138  */
-	uint32_t	emar_l;		/* 0x0013c  */
-	uint32_t	ehar_h;		/* 0x00140  */
-	uint32_t	ehar_l;		/* 0x00144  */
-	uint32_t	micr;		/* 0x00148  */
-	uint32_t	midr_r;		/* 0x0014c  */
-	uint32_t	midr_w;		/* 0x00150  */
+	u32	emcr;		/* 0x000f0  */
+	u32	eisr;		/* 0x000f4  */
+	u32	eier;		/* 0x000f8  */
+	u32	ercsr;		/* 0x000fc  */
+	u32	erbr_h;		/* 0x00100  */
+	u32	erbr_l;		/* 0x00104  */
+	u32	erbar;		/* 0x00108  */
+	u32	ercir;		/* 0x0010c  */
+	u32	erpir;		/* 0x00110  */
+	u32	ertr;		/* 0x00114  */
+	u32	etcsr;		/* 0x00118  */
+	u32	ersr;		/* 0x0011c  */
+	u32	etcdc;		/* 0x00120  */
+	u32	ebir;		/* 0x00124  */
+	u32	etbr_h;		/* 0x00128  */
+	u32	etbr_l;		/* 0x0012c  */
+	u32	etcir;		/* 0x00130  */
+	u32	etpir;		/* 0x00134  */
+	u32	emar_h;		/* 0x00138  */
+	u32	emar_l;		/* 0x0013c  */
+	u32	ehar_h;		/* 0x00140  */
+	u32	ehar_l;		/* 0x00144  */
+	u32	micr;		/* 0x00148  */
+	u32	midr_r;		/* 0x0014c  */
+	u32	midr_w;		/* 0x00150  */
 };
 
 struct ioc3_serioregs {
-	uint32_t	km_csr;		/* 0x0009c  */
-	uint32_t	k_rd;		/* 0x000a0  */
-	uint32_t	m_rd;		/* 0x000a4  */
-	uint32_t	k_wd;		/* 0x000a8  */
-	uint32_t	m_wd;		/* 0x000ac  */
+	u32	km_csr;		/* 0x0009c  */
+	u32	k_rd;		/* 0x000a0  */
+	u32	m_rd;		/* 0x000a4  */
+	u32	k_wd;		/* 0x000a8  */
+	u32	m_wd;		/* 0x000ac  */
 };
 
 /* Register layout of IOC3 in configuration space.  */
 struct ioc3 {
 	/* PCI Config Space registers  */
-	uint32_t	pci_id;		/* 0x00000  */
-	uint32_t	pci_scr;	/* 0x00004  */
-	uint32_t	pci_rev;	/* 0x00008  */
-	uint32_t	pci_lat;	/* 0x0000c  */
-	uint32_t	pci_addr;	/* 0x00010  */
-	uint32_t	pci_err_addr_l;	/* 0x00014  */
-	uint32_t	pci_err_addr_h;	/* 0x00018  */
-
-	uint32_t	sio_ir;		/* 0x0001c  */
-	uint32_t	sio_ies;	/* 0x00020  */
-	uint32_t	sio_iec;	/* 0x00024  */
-	uint32_t	sio_cr;		/* 0x00028  */
-	uint32_t	int_out;	/* 0x0002c  */
-	uint32_t	mcr;		/* 0x00030  */
+	u32	pci_id;		/* 0x00000  */
+	u32	pci_scr;	/* 0x00004  */
+	u32	pci_rev;	/* 0x00008  */
+	u32	pci_lat;	/* 0x0000c  */
+	u32	pci_addr;	/* 0x00010  */
+	u32	pci_err_addr_l;	/* 0x00014  */
+	u32	pci_err_addr_h;	/* 0x00018  */
+
+	u32	sio_ir;		/* 0x0001c  */
+	u32	sio_ies;	/* 0x00020  */
+	u32	sio_iec;	/* 0x00024  */
+	u32	sio_cr;		/* 0x00028  */
+	u32	int_out;	/* 0x0002c  */
+	u32	mcr;		/* 0x00030  */
 
 	/* General Purpose I/O registers  */
-	uint32_t	gpcr_s;		/* 0x00034  */
-	uint32_t	gpcr_c;		/* 0x00038  */
-	uint32_t	gpdr;		/* 0x0003c  */
-	uint32_t	gppr[16];	/* 0x00040  */
+	u32	gpcr_s;		/* 0x00034  */
+	u32	gpcr_c;		/* 0x00038  */
+	u32	gpdr;		/* 0x0003c  */
+	u32	gppr[16];	/* 0x00040  */
 
 	/* Parallel Port Registers  */
-	uint32_t	ppbr_h_a;	/* 0x00080  */
-	uint32_t	ppbr_l_a;	/* 0x00084  */
-	uint32_t	ppcr_a;		/* 0x00088  */
-	uint32_t	ppcr;		/* 0x0008c  */
-	uint32_t	ppbr_h_b;	/* 0x00090  */
-	uint32_t	ppbr_l_b;	/* 0x00094  */
-	uint32_t	ppcr_b;		/* 0x00098  */
+	u32	ppbr_h_a;	/* 0x00080  */
+	u32	ppbr_l_a;	/* 0x00084  */
+	u32	ppcr_a;		/* 0x00088  */
+	u32	ppcr;		/* 0x0008c  */
+	u32	ppbr_h_b;	/* 0x00090  */
+	u32	ppbr_l_b;	/* 0x00094  */
+	u32	ppcr_b;		/* 0x00098  */
 
 	/* Keyboard and Mouse Registers	 */
 	struct ioc3_serioregs	serio;
 
 	/* Serial Port Registers  */
-	uint32_t	sbbr_h;		/* 0x000b0  */
-	uint32_t	sbbr_l;		/* 0x000b4  */
+	u32	sbbr_h;		/* 0x000b0  */
+	u32	sbbr_l;		/* 0x000b4  */
 	struct ioc3_serialregs	port_a;
 	struct ioc3_serialregs	port_b;
 
 	/* Ethernet Registers */
 	struct ioc3_ethregs	eth;
-	uint32_t	pad1[(0x20000 - 0x00154) / 4];
+	u32	pad1[(0x20000 - 0x00154) / 4];
 
 	/* SuperIO Registers  XXX */
 	struct ioc3_sioregs	sregs;	/* 0x20000 */
-	uint32_t	pad2[(0x40000 - 0x20180) / 4];
+	u32	pad2[(0x40000 - 0x20180) / 4];
 
 	/* SSRAM Diagnostic Access */
-	uint32_t	ssram[(0x80000 - 0x40000) / 4];
+	u32	ssram[(0x80000 - 0x40000) / 4];
 
 	/* Bytebus device offsets
 	   0x80000 -   Access to the generic devices selected with   DEV0
@@ -598,8 +598,4 @@ struct ioc3_etxd {
 
 #define MIDR_DATA_MASK		0x0000ffff
 
-#if defined(CONFIG_SGI_IP27) || defined(CONFIG_SGI_IP30)
-extern int bridge_alloc_irq(struct pci_dev *dev);
-#endif
-
 #endif /* MIPS_SN_IOC3_H */
diff --git a/arch/mips/sgi-ip27/ip27-console.c b/arch/mips/sgi-ip27/ip27-console.c
index 6bdb48d41276..5886bee89d06 100644
--- a/arch/mips/sgi-ip27/ip27-console.c
+++ b/arch/mips/sgi-ip27/ip27-console.c
@@ -35,6 +35,7 @@ void prom_putchar(char c)
 {
 	struct ioc3_uartregs *uart = console_uart();
 
-	while ((uart->iu_lsr & 0x20) == 0);
-	uart->iu_thr = c;
+	while ((readb(&uart->iu_lsr) & 0x20) == 0)
+		;
+	writeb(c, &uart->iu_thr);
 }
diff --git a/drivers/rtc/rtc-m48t35.c b/drivers/rtc/rtc-m48t35.c
index 0cf6507de3c7..05f0d91366af 100644
--- a/drivers/rtc/rtc-m48t35.c
+++ b/drivers/rtc/rtc-m48t35.c
@@ -24,6 +24,16 @@
 
 struct m48t35_rtc {
 	u8	pad[0x7ff8];    /* starts at 0x7ff8 */
+#ifdef CONFIG_SGI_IP27
+	u8	hour;
+	u8	min;
+	u8	sec;
+	u8	control;
+	u8	year;
+	u8	month;
+	u8	date;
+	u8	day;
+#else
 	u8	control;
 	u8	sec;
 	u8	min;
@@ -32,6 +42,7 @@ struct m48t35_rtc {
 	u8	date;
 	u8	month;
 	u8	year;
+#endif
 };
 
 #define M48T35_RTC_SET		0x80
diff --git a/drivers/tty/serial/8250/8250_ioc3.c b/drivers/tty/serial/8250/8250_ioc3.c
index 2be6ed2967e0..4c405f1b9c67 100644
--- a/drivers/tty/serial/8250/8250_ioc3.c
+++ b/drivers/tty/serial/8250/8250_ioc3.c
@@ -23,12 +23,12 @@ struct ioc3_8250_data {
 
 static unsigned int ioc3_serial_in(struct uart_port *p, int offset)
 {
-	return readb(p->membase + offset);
+	return readb(p->membase + (offset ^ 3));
 }
 
 static void ioc3_serial_out(struct uart_port *p, int offset, int value)
 {
-	writeb(value, p->membase + offset);
+	writeb(value, p->membase + (offset ^ 3));
 }
 
 static int serial8250_ioc3_probe(struct platform_device *pdev)
-- 
2.13.7

^ permalink raw reply related

* [PATCH v2 2/4] mfd: ioc3: Add driver for SGI IOC3 chip
From: Thomas Bogendoerfer @ 2019-04-09 15:46 UTC (permalink / raw)
  To: Ralf Baechle, Paul Burton, James Hogan, Dmitry Torokhov,
	Lee Jones, David S. Miller, Alessandro Zummo, Alexandre Belloni,
	Greg Kroah-Hartman, Jiri Slaby, linux-mips, linux-kernel,
	linux-input, netdev, linux-rtc, linux-serial
In-Reply-To: <20190409154610.6735-1-tbogendoerfer@suse.de>

SGI IOC3 chip has integrated ethernet, keyboard and mouse interface.
It also supports connecting a SuperIO chip for serial and parallel
interfaces. IOC3 is used inside various SGI systemboards and add-on
cards with different equipped external interfaces.

Support for ethernet and serial interfaces were implemented inside
the network driver. This patchset moves out the not network related
parts to a new MFD driver, which takes care of card detection,
setup of platform devices and interrupt distribution for the subdevices.

Signed-off-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
---
 arch/mips/include/asm/sn/ioc3.h       |  345 +++---
 arch/mips/sgi-ip27/ip27-timer.c       |   20 -
 drivers/mfd/Kconfig                   |   13 +
 drivers/mfd/Makefile                  |    1 +
 drivers/mfd/ioc3.c                    |  802 ++++++++++++++
 drivers/net/ethernet/sgi/Kconfig      |    4 +-
 drivers/net/ethernet/sgi/ioc3-eth.c   | 1867 ++++++++++++---------------------
 drivers/tty/serial/8250/8250_ioc3.c   |   98 ++
 drivers/tty/serial/8250/Kconfig       |   11 +
 drivers/tty/serial/8250/Makefile      |    1 +
 include/linux/platform_data/ioc3eth.h |   15 +
 11 files changed, 1762 insertions(+), 1415 deletions(-)
 create mode 100644 drivers/mfd/ioc3.c
 create mode 100644 drivers/tty/serial/8250/8250_ioc3.c
 create mode 100644 include/linux/platform_data/ioc3eth.h

diff --git a/arch/mips/include/asm/sn/ioc3.h b/arch/mips/include/asm/sn/ioc3.h
index 25c8dccab51f..69069f420930 100644
--- a/arch/mips/include/asm/sn/ioc3.h
+++ b/arch/mips/include/asm/sn/ioc3.h
@@ -3,32 +3,43 @@
  * Copyright (C) 1999, 2000 Ralf Baechle
  * Copyright (C) 1999, 2000 Silicon Graphics, Inc.
  */
-#ifndef _IOC3_H
-#define _IOC3_H
+#ifndef MIPS_SN_IOC3_H
+#define MIPS_SN_IOC3_H
 
 #include <linux/types.h>
 
+/* serial port register map */
+struct ioc3_serialregs {
+	uint32_t	sscr;
+	uint32_t	stpir;
+	uint32_t	stcir;
+	uint32_t	srpir;
+	uint32_t	srcir;
+	uint32_t	srtr;
+	uint32_t	shadow;
+};
+
 /* SUPERIO uart register map */
-typedef volatile struct ioc3_uartregs {
+struct ioc3_uartregs {
 	union {
-		volatile u8	rbr;	/* read only, DLAB == 0 */
-		volatile u8	thr;	/* write only, DLAB == 0 */
-		volatile u8	dll;	/* DLAB == 1 */
+		char	rbr;	/* read only, DLAB == 0 */
+		char	thr;	/* write only, DLAB == 0 */
+		char	dll;	/* DLAB == 1 */
 	} u1;
 	union {
-		volatile u8	ier;	/* DLAB == 0 */
-		volatile u8	dlm;	/* DLAB == 1 */
+		char	ier;	/* DLAB == 0 */
+		char	dlm;	/* DLAB == 1 */
 	} u2;
 	union {
-		volatile u8	iir;	/* read only */
-		volatile u8	fcr;	/* write only */
+		char	iir;	/* read only */
+		char	fcr;	/* write only */
 	} u3;
-	volatile u8	    iu_lcr;
-	volatile u8	    iu_mcr;
-	volatile u8	    iu_lsr;
-	volatile u8	    iu_msr;
-	volatile u8	    iu_scr;
-} ioc3_uregs_t;
+	char	iu_lcr;
+	char	iu_mcr;
+	char	iu_lsr;
+	char	iu_msr;
+	char	iu_scr;
+};
 
 #define iu_rbr u1.rbr
 #define iu_thr u1.thr
@@ -39,133 +50,122 @@ typedef volatile struct ioc3_uartregs {
 #define iu_fcr u3.fcr
 
 struct ioc3_sioregs {
-	volatile u8		fill[0x141];	/* starts at 0x141 */
+	char	fill[0x141];	/* starts at 0x141 */
 
-	volatile u8		uartc;
-	volatile u8		kbdcg;
+	char	uartc;
+	char	kbdcg;
 
-	volatile u8		fill0[0x150 - 0x142 - 1];
+	char	fill0[0x150 - 0x142 - 1];
 
-	volatile u8		pp_data;
-	volatile u8		pp_dsr;
-	volatile u8		pp_dcr;
+	char	pp_data;
+	char	pp_dsr;
+	char	pp_dcr;
 
-	volatile u8		fill1[0x158 - 0x152 - 1];
+	char	fill1[0x158 - 0x152 - 1];
 
-	volatile u8		pp_fifa;
-	volatile u8		pp_cfgb;
-	volatile u8		pp_ecr;
+	char	pp_fifa;
+	char	pp_cfgb;
+	char	pp_ecr;
 
-	volatile u8		fill2[0x168 - 0x15a - 1];
+	char	fill2[0x168 - 0x15a - 1];
 
-	volatile u8		rtcad;
-	volatile u8		rtcdat;
+	char	rtcad;
+	char	rtcdat;
 
-	volatile u8		fill3[0x170 - 0x169 - 1];
+	char	fill3[0x170 - 0x169 - 1];
 
 	struct ioc3_uartregs	uartb;	/* 0x20170  */
 	struct ioc3_uartregs	uarta;	/* 0x20178  */
 };
 
+struct ioc3_ethregs {
+	uint32_t	emcr;		/* 0x000f0  */
+	uint32_t	eisr;		/* 0x000f4  */
+	uint32_t	eier;		/* 0x000f8  */
+	uint32_t	ercsr;		/* 0x000fc  */
+	uint32_t	erbr_h;		/* 0x00100  */
+	uint32_t	erbr_l;		/* 0x00104  */
+	uint32_t	erbar;		/* 0x00108  */
+	uint32_t	ercir;		/* 0x0010c  */
+	uint32_t	erpir;		/* 0x00110  */
+	uint32_t	ertr;		/* 0x00114  */
+	uint32_t	etcsr;		/* 0x00118  */
+	uint32_t	ersr;		/* 0x0011c  */
+	uint32_t	etcdc;		/* 0x00120  */
+	uint32_t	ebir;		/* 0x00124  */
+	uint32_t	etbr_h;		/* 0x00128  */
+	uint32_t	etbr_l;		/* 0x0012c  */
+	uint32_t	etcir;		/* 0x00130  */
+	uint32_t	etpir;		/* 0x00134  */
+	uint32_t	emar_h;		/* 0x00138  */
+	uint32_t	emar_l;		/* 0x0013c  */
+	uint32_t	ehar_h;		/* 0x00140  */
+	uint32_t	ehar_l;		/* 0x00144  */
+	uint32_t	micr;		/* 0x00148  */
+	uint32_t	midr_r;		/* 0x0014c  */
+	uint32_t	midr_w;		/* 0x00150  */
+};
+
+struct ioc3_serioregs {
+	uint32_t	km_csr;		/* 0x0009c  */
+	uint32_t	k_rd;		/* 0x000a0  */
+	uint32_t	m_rd;		/* 0x000a4  */
+	uint32_t	k_wd;		/* 0x000a8  */
+	uint32_t	m_wd;		/* 0x000ac  */
+};
+
 /* Register layout of IOC3 in configuration space.  */
 struct ioc3 {
-	volatile u32	pad0[7];	/* 0x00000  */
-	volatile u32	sio_ir;		/* 0x0001c  */
-	volatile u32	sio_ies;	/* 0x00020  */
-	volatile u32	sio_iec;	/* 0x00024  */
-	volatile u32	sio_cr;		/* 0x00028  */
-	volatile u32	int_out;	/* 0x0002c  */
-	volatile u32	mcr;		/* 0x00030  */
+	/* PCI Config Space registers  */
+	uint32_t	pci_id;		/* 0x00000  */
+	uint32_t	pci_scr;	/* 0x00004  */
+	uint32_t	pci_rev;	/* 0x00008  */
+	uint32_t	pci_lat;	/* 0x0000c  */
+	uint32_t	pci_addr;	/* 0x00010  */
+	uint32_t	pci_err_addr_l;	/* 0x00014  */
+	uint32_t	pci_err_addr_h;	/* 0x00018  */
+
+	uint32_t	sio_ir;		/* 0x0001c  */
+	uint32_t	sio_ies;	/* 0x00020  */
+	uint32_t	sio_iec;	/* 0x00024  */
+	uint32_t	sio_cr;		/* 0x00028  */
+	uint32_t	int_out;	/* 0x0002c  */
+	uint32_t	mcr;		/* 0x00030  */
 
 	/* General Purpose I/O registers  */
-	volatile u32	gpcr_s;		/* 0x00034  */
-	volatile u32	gpcr_c;		/* 0x00038  */
-	volatile u32	gpdr;		/* 0x0003c  */
-	volatile u32	gppr_0;		/* 0x00040  */
-	volatile u32	gppr_1;		/* 0x00044  */
-	volatile u32	gppr_2;		/* 0x00048  */
-	volatile u32	gppr_3;		/* 0x0004c  */
-	volatile u32	gppr_4;		/* 0x00050  */
-	volatile u32	gppr_5;		/* 0x00054  */
-	volatile u32	gppr_6;		/* 0x00058  */
-	volatile u32	gppr_7;		/* 0x0005c  */
-	volatile u32	gppr_8;		/* 0x00060  */
-	volatile u32	gppr_9;		/* 0x00064  */
-	volatile u32	gppr_10;	/* 0x00068  */
-	volatile u32	gppr_11;	/* 0x0006c  */
-	volatile u32	gppr_12;	/* 0x00070  */
-	volatile u32	gppr_13;	/* 0x00074  */
-	volatile u32	gppr_14;	/* 0x00078  */
-	volatile u32	gppr_15;	/* 0x0007c  */
+	uint32_t	gpcr_s;		/* 0x00034  */
+	uint32_t	gpcr_c;		/* 0x00038  */
+	uint32_t	gpdr;		/* 0x0003c  */
+	uint32_t	gppr[16];	/* 0x00040  */
 
 	/* Parallel Port Registers  */
-	volatile u32	ppbr_h_a;	/* 0x00080  */
-	volatile u32	ppbr_l_a;	/* 0x00084  */
-	volatile u32	ppcr_a;		/* 0x00088  */
-	volatile u32	ppcr;		/* 0x0008c  */
-	volatile u32	ppbr_h_b;	/* 0x00090  */
-	volatile u32	ppbr_l_b;	/* 0x00094  */
-	volatile u32	ppcr_b;		/* 0x00098  */
+	uint32_t	ppbr_h_a;	/* 0x00080  */
+	uint32_t	ppbr_l_a;	/* 0x00084  */
+	uint32_t	ppcr_a;		/* 0x00088  */
+	uint32_t	ppcr;		/* 0x0008c  */
+	uint32_t	ppbr_h_b;	/* 0x00090  */
+	uint32_t	ppbr_l_b;	/* 0x00094  */
+	uint32_t	ppcr_b;		/* 0x00098  */
 
 	/* Keyboard and Mouse Registers	 */
-	volatile u32	km_csr;		/* 0x0009c  */
-	volatile u32	k_rd;		/* 0x000a0  */
-	volatile u32	m_rd;		/* 0x000a4  */
-	volatile u32	k_wd;		/* 0x000a8  */
-	volatile u32	m_wd;		/* 0x000ac  */
+	struct ioc3_serioregs	serio;
 
 	/* Serial Port Registers  */
-	volatile u32	sbbr_h;		/* 0x000b0  */
-	volatile u32	sbbr_l;		/* 0x000b4  */
-	volatile u32	sscr_a;		/* 0x000b8  */
-	volatile u32	stpir_a;	/* 0x000bc  */
-	volatile u32	stcir_a;	/* 0x000c0  */
-	volatile u32	srpir_a;	/* 0x000c4  */
-	volatile u32	srcir_a;	/* 0x000c8  */
-	volatile u32	srtr_a;		/* 0x000cc  */
-	volatile u32	shadow_a;	/* 0x000d0  */
-	volatile u32	sscr_b;		/* 0x000d4  */
-	volatile u32	stpir_b;	/* 0x000d8  */
-	volatile u32	stcir_b;	/* 0x000dc  */
-	volatile u32	srpir_b;	/* 0x000e0  */
-	volatile u32	srcir_b;	/* 0x000e4  */
-	volatile u32	srtr_b;		/* 0x000e8  */
-	volatile u32	shadow_b;	/* 0x000ec  */
-
-	/* Ethernet Registers  */
-	volatile u32	emcr;		/* 0x000f0  */
-	volatile u32	eisr;		/* 0x000f4  */
-	volatile u32	eier;		/* 0x000f8  */
-	volatile u32	ercsr;		/* 0x000fc  */
-	volatile u32	erbr_h;		/* 0x00100  */
-	volatile u32	erbr_l;		/* 0x00104  */
-	volatile u32	erbar;		/* 0x00108  */
-	volatile u32	ercir;		/* 0x0010c  */
-	volatile u32	erpir;		/* 0x00110  */
-	volatile u32	ertr;		/* 0x00114  */
-	volatile u32	etcsr;		/* 0x00118  */
-	volatile u32	ersr;		/* 0x0011c  */
-	volatile u32	etcdc;		/* 0x00120  */
-	volatile u32	ebir;		/* 0x00124  */
-	volatile u32	etbr_h;		/* 0x00128  */
-	volatile u32	etbr_l;		/* 0x0012c  */
-	volatile u32	etcir;		/* 0x00130  */
-	volatile u32	etpir;		/* 0x00134  */
-	volatile u32	emar_h;		/* 0x00138  */
-	volatile u32	emar_l;		/* 0x0013c  */
-	volatile u32	ehar_h;		/* 0x00140  */
-	volatile u32	ehar_l;		/* 0x00144  */
-	volatile u32	micr;		/* 0x00148  */
-	volatile u32	midr_r;		/* 0x0014c  */
-	volatile u32	midr_w;		/* 0x00150  */
-	volatile u32	pad1[(0x20000 - 0x00154) / 4];
+	uint32_t	sbbr_h;		/* 0x000b0  */
+	uint32_t	sbbr_l;		/* 0x000b4  */
+	struct ioc3_serialregs	port_a;
+	struct ioc3_serialregs	port_b;
+
+	/* Ethernet Registers */
+	struct ioc3_ethregs	eth;
+	uint32_t	pad1[(0x20000 - 0x00154) / 4];
 
 	/* SuperIO Registers  XXX */
 	struct ioc3_sioregs	sregs;	/* 0x20000 */
-	volatile u32	pad2[(0x40000 - 0x20180) / 4];
+	uint32_t	pad2[(0x40000 - 0x20180) / 4];
 
 	/* SSRAM Diagnostic Access */
-	volatile u32	ssram[(0x80000 - 0x40000) / 4];
+	uint32_t	ssram[(0x80000 - 0x40000) / 4];
 
 	/* Bytebus device offsets
 	   0x80000 -   Access to the generic devices selected with   DEV0
@@ -178,6 +178,20 @@ struct ioc3 {
 	   0xFFFFF     bytebus DEV_SEL_3  */
 };
 
+
+#define PCI_LAT			0xc		/* Latency Timer */
+#define PCI_SCR_DROP_MODE_EN	0x00008000	/* drop pios on parity err */
+#define UARTA_BASE		0x178
+#define UARTB_BASE		0x170
+
+/*
+ * Bytebus device space
+ */
+#define IOC3_BYTEBUS_DEV0	0x80000L
+#define IOC3_BYTEBUS_DEV1	0xa0000L
+#define IOC3_BYTEBUS_DEV2	0xc0000L
+#define IOC3_BYTEBUS_DEV3	0xe0000L
+
 /*
  * Ethernet RX Buffer
  */
@@ -233,28 +247,20 @@ struct ioc3_etxd {
 #define ETXD_B2CNT_MASK		0x7ff00000
 #define ETXD_B2CNT_SHIFT	20
 
-/*
- * Bytebus device space
- */
-#define IOC3_BYTEBUS_DEV0	0x80000L
-#define IOC3_BYTEBUS_DEV1	0xa0000L
-#define IOC3_BYTEBUS_DEV2	0xc0000L
-#define IOC3_BYTEBUS_DEV3	0xe0000L
-
 /* ------------------------------------------------------------------------- */
 
 /* Superio Registers (PIO Access) */
 #define IOC3_SIO_BASE		0x20000
 #define IOC3_SIO_UARTC		(IOC3_SIO_BASE+0x141)	/* UART Config */
 #define IOC3_SIO_KBDCG		(IOC3_SIO_BASE+0x142)	/* KBD Config */
-#define IOC3_SIO_PP_BASE	(IOC3_SIO_BASE+PP_BASE)		/* Parallel Port */
+#define IOC3_SIO_PP_BASE	(IOC3_SIO_BASE+PP_BASE)	/* Parallel Port */
 #define IOC3_SIO_RTC_BASE	(IOC3_SIO_BASE+0x168)	/* Real Time Clock */
 #define IOC3_SIO_UB_BASE	(IOC3_SIO_BASE+UARTB_BASE)	/* UART B */
 #define IOC3_SIO_UA_BASE	(IOC3_SIO_BASE+UARTA_BASE)	/* UART A */
 
 /* SSRAM Diagnostic Access */
 #define IOC3_SSRAM	IOC3_RAM_OFF	/* base of SSRAM diagnostic access */
-#define IOC3_SSRAM_LEN	0x40000 /* 256kb (address space size, may not be fully populated) */
+#define IOC3_SSRAM_LEN	0x40000	/* 256kb (addrspc sz, may not be populated) */
 #define IOC3_SSRAM_DM	0x0000ffff	/* data mask */
 #define IOC3_SSRAM_PM	0x00010000	/* parity mask */
 
@@ -294,10 +300,10 @@ struct ioc3_etxd {
 					   SIO_IR to assert */
 #define KM_CSR_M_TO_EN	  0x00080000	/* KM_CSR_M_TO + KM_CSR_M_TO_EN = cause
 					   SIO_IR to assert */
-#define KM_CSR_K_CLAMP_ONE	0x00100000	/* Pull K_CLK low after rec. one char */
-#define KM_CSR_M_CLAMP_ONE	0x00200000	/* Pull M_CLK low after rec. one char */
-#define KM_CSR_K_CLAMP_THREE	0x00400000	/* Pull K_CLK low after rec. three chars */
-#define KM_CSR_M_CLAMP_THREE	0x00800000	/* Pull M_CLK low after rec. three char */
+#define KM_CSR_K_CLAMP_1  0x00100000	/* Pull K_CLK low aft recv 1 char */
+#define KM_CSR_M_CLAMP_1  0x00200000	/* Pull M_CLK low aft recv 1 char */
+#define KM_CSR_K_CLAMP_3  0x00400000	/* Pull K_CLK low aft recv 3 chars */
+#define KM_CSR_M_CLAMP_3  0x00800000	/* Pull M_CLK low aft recv 3 chars */
 
 /* bitmasks for IOC3_K_RD and IOC3_M_RD */
 #define KM_RD_DATA_2	0x000000ff	/* 3rd char recvd since last read */
@@ -440,10 +446,6 @@ struct ioc3_etxd {
 				 SIO_IR_PP_INTB | SIO_IR_PP_MEMERR)
 #define SIO_IR_RT		(SIO_IR_RT_INT | SIO_IR_GEN_INT1)
 
-/* macro to load pending interrupts */
-#define IOC3_PENDING_INTRS(mem) (PCI_INW(&((mem)->sio_ir)) & \
-				 PCI_INW(&((mem)->sio_ies_ro)))
-
 /* bitmasks for SIO_CR */
 #define SIO_CR_SIO_RESET	0x00000001	/* reset the SIO */
 #define SIO_CR_SER_A_BASE	0x000000fe	/* DMA poll addr port A */
@@ -500,10 +502,11 @@ struct ioc3_etxd {
 #define GPCR_UARTB_MODESEL	0x40	/* pin is output to port B mode sel */
 #define GPCR_UARTA_MODESEL	0x80	/* pin is output to port A mode sel */
 
-#define GPPR_PHY_RESET_PIN	5	/* GIO pin controlling phy reset */
-#define GPPR_UARTB_MODESEL_PIN	6	/* GIO pin controlling uart b mode select */
-#define GPPR_UARTA_MODESEL_PIN	7	/* GIO pin controlling uart a mode select */
+#define GPPR_PHY_RESET_PIN	5	/* GIO pin cntrlling phy reset */
+#define GPPR_UARTB_MODESEL_PIN	6	/* GIO pin cntrlling uart b mode sel */
+#define GPPR_UARTA_MODESEL_PIN	7	/* GIO pin cntrlling uart a mode sel */
 
+/* ethernet */
 #define EMCR_DUPLEX		0x00000001
 #define EMCR_PROMISC		0x00000002
 #define EMCR_PADEN		0x00000004
@@ -595,70 +598,8 @@ struct ioc3_etxd {
 
 #define MIDR_DATA_MASK		0x0000ffff
 
-#define ERXBUF_IPCKSUM_MASK	0x0000ffff
-#define ERXBUF_BYTECNT_MASK	0x07ff0000
-#define ERXBUF_BYTECNT_SHIFT	16
-#define ERXBUF_V		0x80000000
-
-#define ERXBUF_CRCERR		0x00000001	/* aka RSV15 */
-#define ERXBUF_FRAMERR		0x00000002	/* aka RSV14 */
-#define ERXBUF_CODERR		0x00000004	/* aka RSV13 */
-#define ERXBUF_INVPREAMB	0x00000008	/* aka RSV18 */
-#define ERXBUF_LOLEN		0x00007000	/* aka RSV2_0 */
-#define ERXBUF_HILEN		0x03ff0000	/* aka RSV12_3 */
-#define ERXBUF_MULTICAST	0x04000000	/* aka RSV16 */
-#define ERXBUF_BROADCAST	0x08000000	/* aka RSV17 */
-#define ERXBUF_LONGEVENT	0x10000000	/* aka RSV19 */
-#define ERXBUF_BADPKT		0x20000000	/* aka RSV20 */
-#define ERXBUF_GOODPKT		0x40000000	/* aka RSV21 */
-#define ERXBUF_CARRIER		0x80000000	/* aka RSV22 */
-
-#define ETXD_BYTECNT_MASK	0x000007ff	/* total byte count */
-#define ETXD_INTWHENDONE	0x00001000	/* intr when done */
-#define ETXD_D0V		0x00010000	/* data 0 valid */
-#define ETXD_B1V		0x00020000	/* buf 1 valid */
-#define ETXD_B2V		0x00040000	/* buf 2 valid */
-#define ETXD_DOCHECKSUM		0x00080000	/* insert ip cksum */
-#define ETXD_CHKOFF_MASK	0x07f00000	/* cksum byte offset */
-#define ETXD_CHKOFF_SHIFT	20
-
-#define ETXD_D0CNT_MASK		0x0000007f
-#define ETXD_B1CNT_MASK		0x0007ff00
-#define ETXD_B1CNT_SHIFT	8
-#define ETXD_B2CNT_MASK		0x7ff00000
-#define ETXD_B2CNT_SHIFT	20
-
-typedef enum ioc3_subdevs_e {
-    ioc3_subdev_ether,
-    ioc3_subdev_generic,
-    ioc3_subdev_nic,
-    ioc3_subdev_kbms,
-    ioc3_subdev_ttya,
-    ioc3_subdev_ttyb,
-    ioc3_subdev_ecpp,
-    ioc3_subdev_rt,
-    ioc3_nsubdevs
-} ioc3_subdev_t;
-
-/* subdevice disable bits,
- * from the standard INFO_LBL_SUBDEVS
- */
-#define IOC3_SDB_ETHER		(1<<ioc3_subdev_ether)
-#define IOC3_SDB_GENERIC	(1<<ioc3_subdev_generic)
-#define IOC3_SDB_NIC		(1<<ioc3_subdev_nic)
-#define IOC3_SDB_KBMS		(1<<ioc3_subdev_kbms)
-#define IOC3_SDB_TTYA		(1<<ioc3_subdev_ttya)
-#define IOC3_SDB_TTYB		(1<<ioc3_subdev_ttyb)
-#define IOC3_SDB_ECPP		(1<<ioc3_subdev_ecpp)
-#define IOC3_SDB_RT		(1<<ioc3_subdev_rt)
-
-#define IOC3_ALL_SUBDEVS	((1<<ioc3_nsubdevs)-1)
-
-#define IOC3_SDB_SERIAL		(IOC3_SDB_TTYA|IOC3_SDB_TTYB)
-
-#define IOC3_STD_SUBDEVS	IOC3_ALL_SUBDEVS
-
-#define IOC3_INTA_SUBDEVS	IOC3_SDB_ETHER
-#define IOC3_INTB_SUBDEVS	(IOC3_SDB_GENERIC|IOC3_SDB_KBMS|IOC3_SDB_SERIAL|IOC3_SDB_ECPP|IOC3_SDB_RT)
+#if defined(CONFIG_SGI_IP27) || defined(CONFIG_SGI_IP30)
+extern int bridge_alloc_irq(struct pci_dev *dev);
+#endif
 
-#endif /* _IOC3_H */
+#endif /* MIPS_SN_IOC3_H */
diff --git a/arch/mips/sgi-ip27/ip27-timer.c b/arch/mips/sgi-ip27/ip27-timer.c
index 9b4b9ac621a3..5631e93ea350 100644
--- a/arch/mips/sgi-ip27/ip27-timer.c
+++ b/arch/mips/sgi-ip27/ip27-timer.c
@@ -188,23 +188,3 @@ void hub_rtc_init(cnodeid_t cnode)
 		LOCAL_HUB_S(PI_RT_PEND_B, 0);
 	}
 }
-
-static int __init sgi_ip27_rtc_devinit(void)
-{
-	struct resource res;
-
-	memset(&res, 0, sizeof(res));
-	res.start = XPHYSADDR(KL_CONFIG_CH_CONS_INFO(master_nasid)->memory_base +
-			      IOC3_BYTEBUS_DEV0);
-	res.end = res.start + 32767;
-	res.flags = IORESOURCE_MEM;
-
-	return IS_ERR(platform_device_register_simple("rtc-m48t35", -1,
-						      &res, 1));
-}
-
-/*
- * kludge make this a device_initcall after ioc3 resource conflicts
- * are resolved
- */
-late_initcall(sgi_ip27_rtc_devinit);
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 0ce2d8dfc5f1..871d66a86c86 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1951,5 +1951,18 @@ config RAVE_SP_CORE
 	  Select this to get support for the Supervisory Processor
 	  device found on several devices in RAVE line of hardware.
 
+config SGI_MFD_IOC3
+	tristate "SGI IOC3 core driver"
+	depends on PCI && MIPS
+	select MFD_CORE
+	help
+	  This option enables basic support for the SGI IOC3-based
+	  controller cards.  This option does not enable any specific
+	  functions on such a card, but provides necessary infrastructure
+	  for other drivers to utilize.
+
+	  If you have an SGI Origin, Octane, or a PCI IOC3 card,
+	  then say Y. Otherwise say N.
+
 endmenu
 endif
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index b4569ed7f3f3..07255e499129 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -246,4 +246,5 @@ obj-$(CONFIG_MFD_MXS_LRADC)     += mxs-lradc.o
 obj-$(CONFIG_MFD_SC27XX_PMIC)	+= sprd-sc27xx-spi.o
 obj-$(CONFIG_RAVE_SP_CORE)	+= rave-sp.o
 obj-$(CONFIG_MFD_ROHM_BD718XX)	+= rohm-bd718x7.o
+obj-$(CONFIG_SGI_MFD_IOC3)	+= ioc3.o
 
diff --git a/drivers/mfd/ioc3.c b/drivers/mfd/ioc3.c
new file mode 100644
index 000000000000..ad715805b16e
--- /dev/null
+++ b/drivers/mfd/ioc3.c
@@ -0,0 +1,802 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SGI IOC3 multifunction device driver
+ *
+ * Copyright (C) 2018, 2019 Thomas Bogendoerfer <tbogendoerfer@suse.de>
+ *
+ * Based on work by:
+ *   Stanislaw Skowronek <skylark@unaligned.org>
+ *   Joshua Kinard <kumba@gentoo.org>
+ *   Brent Casavant <bcasavan@sgi.com> - IOC4 master driver
+ *   Pat Gefre <pfg@sgi.com> - IOC3 serial port IRQ demuxer
+ */
+
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/ioc3eth.h>
+
+#include <asm/sn/ioc3.h>
+#include <asm/pci/bridge.h>
+
+#define IOC3_ETH	BIT(0)
+#define IOC3_SER	BIT(1)
+#define IOC3_PAR	BIT(2)
+#define IOC3_KBD	BIT(3)
+#define IOC3_M48T35	BIT(4)
+
+static int ioc3_serial_id = 1;
+static int ioc3_eth_id = 1;
+static int ioc3_kbd_id = 1;
+static struct mfd_cell ioc3_mfd_cells[5];
+
+struct ioc3_board_info {
+	const char *name;
+	int irq_offset;
+	int funcs;
+};
+
+struct ioc3_priv_data {
+	struct ioc3_board_info *info;
+	struct irq_domain *domain;
+	struct ioc3 __iomem *regs;
+	struct pci_dev *pdev;
+	char nic_part[32];
+	char nic_mac[6];
+	int irq_io;
+};
+
+#define MCR_PACK(pulse, sample)	(((pulse) << 10) | ((sample) << 2))
+
+static int ioc3_nic_wait(u32 __iomem *mcr)
+{
+	u32 mcr_val;
+
+	do {
+		mcr_val = readl(mcr);
+	} while (!(mcr_val & 2));
+
+	return (mcr_val & 1);
+}
+
+static int ioc3_nic_reset(u32 __iomem *mcr)
+{
+	int presence;
+	unsigned long flags;
+
+	local_irq_save(flags);
+	writel(MCR_PACK(520, 65), mcr);
+	presence = ioc3_nic_wait(mcr);
+	local_irq_restore(flags);
+
+	udelay(500);
+
+	return presence;
+}
+
+static int ioc3_nic_read_bit(u32 __iomem *mcr)
+{
+	int result;
+	unsigned long flags;
+
+	local_irq_save(flags);
+	writel(MCR_PACK(6, 13), mcr);
+	result = ioc3_nic_wait(mcr);
+	local_irq_restore(flags);
+
+	udelay(100);
+
+	return result;
+}
+
+static u32 ioc3_nic_read_byte(u32 __iomem *mcr)
+{
+	u32 result = 0;
+	int i;
+
+	for (i = 0; i < 8; i++)
+		result = ((result >> 1) | (ioc3_nic_read_bit(mcr) << 7));
+
+	return result;
+}
+
+static void ioc3_nic_write_bit(u32 __iomem *mcr, int bit)
+{
+	if (bit)
+		writel(MCR_PACK(6, 110), mcr);
+	else
+		writel(MCR_PACK(80, 30), mcr);
+
+	ioc3_nic_wait(mcr);
+}
+
+static void ioc3_nic_write_byte(u32 __iomem *mcr, int byte)
+{
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		ioc3_nic_write_bit(mcr, byte & 1);
+		byte >>= 1;
+	}
+}
+
+static u64 ioc3_nic_find(u32 __iomem *mcr, int *last, u64 addr)
+{
+	int a, b, index, disc;
+
+	ioc3_nic_reset(mcr);
+
+	/* Search ROM.  */
+	ioc3_nic_write_byte(mcr, 0xf0);
+
+	/* Algorithm from ``Book of iButton Standards''.  */
+	for (index = 0, disc = 0; index < 64; index++) {
+		a = ioc3_nic_read_bit(mcr);
+		b = ioc3_nic_read_bit(mcr);
+
+		if (unlikely(a && b)) {
+			pr_warn("ioc3: NIC search failed.\n");
+			*last = 0;
+			return 0;
+		}
+
+		if (!a && !b) {
+			if (index == *last)
+				addr |= 1UL << index;
+			else if (index > *last) {
+				addr &= ~(1UL << index);
+				disc = index;
+			} else if ((addr & (1UL << index)) == 0)
+				disc = index;
+			ioc3_nic_write_bit(mcr, (addr >> index) & 1);
+			continue;
+		} else {
+			if (a)
+				addr |= (1UL << index);
+			else
+				addr &= ~(1UL << index);
+			ioc3_nic_write_bit(mcr, a);
+			continue;
+		}
+	}
+	*last = disc;
+	return addr;
+}
+
+static void ioc3_nic_addr(u32 __iomem *mcr, u64 addr)
+{
+	int index;
+
+	ioc3_nic_reset(mcr);
+	ioc3_nic_write_byte(mcr, 0xf0);
+
+	for (index = 0; index < 64; index++) {
+		ioc3_nic_read_bit(mcr);
+		ioc3_nic_read_bit(mcr);
+		ioc3_nic_write_bit(mcr, ((addr >> index) & 1));
+	}
+}
+
+static void crc16_byte(u32 *crc, u8 db)
+{
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		*crc <<= 1;
+		if ((db ^ (*crc >> 16)) & 1)
+			*crc ^= 0x8005;
+		db >>= 1;
+	}
+	*crc &= 0xffff;
+}
+
+static u32 crc16_area(u8 *dbs, int size, u32 crc)
+{
+	while (size--)
+		crc16_byte(&crc, *(dbs++));
+
+	return crc;
+}
+
+static void crc8_byte(u32 *crc, u8 db)
+{
+	int i, f;
+
+	for (i = 0; i < 8; i++) {
+		f = ((*crc ^ db) & 1);
+		*crc >>= 1;
+		db >>= 1;
+		if (f)
+			*crc ^= 0x8c;
+	}
+	*crc &= 0xff;
+}
+
+static u32 crc8_addr(u64 addr)
+{
+	u32 crc = 0;
+	int i;
+
+	for (i = 0; i < 64; i += 8)
+		crc8_byte(&crc, addr >> i);
+	return crc;
+}
+
+static void ioc3_read_redir_page(u32 __iomem *mcr, u64 addr, int page,
+				 u8 *redir, u8 *data)
+{
+	int loops = 16, i;
+
+	while (redir[page] != 0xff) {
+		page = (redir[page] ^ 0xff);
+		loops--;
+		if (unlikely(loops < 0)) {
+			pr_err("ioc3: NIC circular redirection\n");
+			return;
+		}
+	}
+
+	loops = 3;
+	while (loops > 0) {
+		ioc3_nic_addr(mcr, addr);
+		ioc3_nic_write_byte(mcr, 0xf0);
+		ioc3_nic_write_byte(mcr, (page << 5) & 0xe0);
+		ioc3_nic_write_byte(mcr, (page >> 3) & 0x1f);
+
+		for (i = 0; i < 0x20; i++)
+			data[i] = ioc3_nic_read_byte(mcr);
+
+		if (crc16_area(data, 0x20, 0) == 0x800d)
+			return;
+
+		loops--;
+	}
+
+	pr_err("ioc3: CRC error in data page\n");
+	for (i = 0; i < 0x20; i++)
+		data[i] = 0x00;
+}
+
+static void ioc3_read_redir_map(u32 __iomem *mcr, u64 addr, u8 *redir)
+{
+	int i, j, crc_ok, loops = 3;
+	u32 crc;
+
+	while (loops > 0) {
+		crc_ok = 1;
+		ioc3_nic_addr(mcr, addr);
+		ioc3_nic_write_byte(mcr, 0xaa);
+		ioc3_nic_write_byte(mcr, 0x00);
+		ioc3_nic_write_byte(mcr, 0x01);
+
+		for (i = 0; i < 64; i += 8) {
+			for (j = 0; j < 8; j++)
+				redir[i + j] = ioc3_nic_read_byte(mcr);
+
+			crc = crc16_area(redir + i, 8, i == 0 ? 0x8707 : 0);
+
+			crc16_byte(&crc, ioc3_nic_read_byte(mcr));
+			crc16_byte(&crc, ioc3_nic_read_byte(mcr));
+
+			if (crc != 0x800d)
+				crc_ok = 0;
+		}
+		if (crc_ok)
+			return;
+		loops--;
+	}
+	pr_err("ioc3: CRC error in redirection page\n");
+	for (i = 0; i < 64; i++)
+		redir[i] = 0xff;
+}
+
+static void ioc3_read_nic(struct ioc3_priv_data *ipd, u32 __iomem *mcr,
+			  u64 addr)
+{
+	u8 redir[64];
+	u8 data[64], part[32];
+	int i, j;
+
+	/* Read redirections */
+	ioc3_read_redir_map(mcr, addr, redir);
+
+	/* Read data pages */
+	ioc3_read_redir_page(mcr, addr, 0, redir, data);
+	ioc3_read_redir_page(mcr, addr, 1, redir, (data + 32));
+
+	/* Assemble the part # */
+	j = 0;
+	for (i = 0; i < 19; i++)
+		if (data[i + 11] != ' ')
+			part[j++] = data[i + 11];
+
+	for (i = 0; i < 6; i++)
+		if (data[i + 32] != ' ')
+			part[j++] = data[i + 32];
+
+	part[j] = 0;
+
+	/* Skip Octane (IP30) power supplies */
+	if (!(strncmp(part, "060-0035-", 9)) ||
+	    !(strncmp(part, "060-0038-", 9)) ||
+	    !(strncmp(part, "060-0028-", 9)))
+		return;
+
+	strlcpy(ipd->nic_part, part, sizeof(ipd->nic_part));
+}
+
+static void ioc3_read_mac(struct ioc3_priv_data *ipd, u64 addr)
+{
+	int i, loops = 3;
+	u8 data[13];
+	u32 __iomem *mcr = &ipd->regs->mcr;
+
+	while (loops > 0) {
+		ioc3_nic_addr(mcr, addr);
+		ioc3_nic_write_byte(mcr, 0xf0);
+		ioc3_nic_write_byte(mcr, 0x00);
+		ioc3_nic_write_byte(mcr, 0x00);
+		ioc3_nic_read_byte(mcr);
+
+		for (i = 0; i < 13; i++)
+			data[i] = ioc3_nic_read_byte(mcr);
+
+		if (crc16_area(data, 13, 0) == 0x800d) {
+			for (i = 10; i > 4; i--)
+				ipd->nic_mac[10 - i] = data[i];
+			return;
+		}
+		loops--;
+	}
+
+	pr_err("ioc3: CRC error in MAC address\n");
+	for (i = 0; i < 6; i++)
+		ipd->nic_mac[i] = 0x00;
+}
+
+static void ioc3_probe_nic(struct ioc3_priv_data *ipd, u32 __iomem *mcr)
+{
+	int save = 0, loops = 3;
+	u64 first, addr;
+
+	while (loops > 0) {
+		ipd->nic_part[0] = 0;
+		first = ioc3_nic_find(mcr, &save, 0);
+		addr = first;
+
+		if (unlikely(!first))
+			return;
+
+		while (1) {
+			if (crc8_addr(addr))
+				break;
+
+			switch (addr & 0xff) {
+			case 0x0b:
+				ioc3_read_nic(ipd, mcr, addr);
+				break;
+			case 0x09:
+			case 0x89:
+			case 0x91:
+				ioc3_read_mac(ipd, addr);
+				break;
+			}
+
+			addr = ioc3_nic_find(mcr, &save, addr);
+			if (addr == first)
+				return;
+		}
+		loops--;
+	}
+	pr_err("ioc3: CRC error in NIC address\n");
+}
+
+static void ioc3_irq_ack(struct irq_data *d)
+{
+	struct ioc3_priv_data *ipd = irq_data_get_irq_chip_data(d);
+	unsigned int hwirq = irqd_to_hwirq(d);
+
+	writel(BIT(hwirq), &ipd->regs->sio_ir);
+}
+
+static void ioc3_irq_mask(struct irq_data *d)
+{
+	struct ioc3_priv_data *ipd = irq_data_get_irq_chip_data(d);
+	unsigned int hwirq = irqd_to_hwirq(d);
+
+	writel(BIT(hwirq), &ipd->regs->sio_iec);
+}
+
+static void ioc3_irq_unmask(struct irq_data *d)
+{
+	struct ioc3_priv_data *ipd = irq_data_get_irq_chip_data(d);
+	unsigned int hwirq = irqd_to_hwirq(d);
+
+	writel(BIT(hwirq), &ipd->regs->sio_ies);
+}
+
+static struct irq_chip ioc3_irq_chip = {
+	.name		= "IOC3",
+	.irq_ack	= ioc3_irq_ack,
+	.irq_mask	= ioc3_irq_mask,
+	.irq_unmask	= ioc3_irq_unmask,
+};
+
+#define IOC3_LVL_MASK	(BIT(0) | BIT(2) | BIT(6) | BIT(9) | BIT(11) | BIT(15))
+
+static int ioc3_irq_domain_map(struct irq_domain *d, unsigned int irq,
+			      irq_hw_number_t hwirq)
+{
+	if (BIT(hwirq) & IOC3_LVL_MASK)
+		irq_set_chip_and_handler(irq, &ioc3_irq_chip, handle_level_irq);
+	else
+		irq_set_chip_and_handler(irq, &ioc3_irq_chip, handle_edge_irq);
+
+	irq_set_chip_data(irq, d->host_data);
+	return 0;
+}
+
+
+static const struct irq_domain_ops ioc3_irq_domain_ops = {
+	.map = ioc3_irq_domain_map,
+};
+
+static void ioc3_irq_handler(struct irq_desc *desc)
+{
+	struct irq_domain *domain = irq_desc_get_handler_data(desc);
+	struct ioc3_priv_data *ipd = domain->host_data;
+	struct ioc3 __iomem *regs = ipd->regs;
+	unsigned int irq = 0;
+	u32 pending;
+
+	pending = readl(&regs->sio_ir);
+	pending &= readl(&regs->sio_ies);
+	if (pending)
+		irq = irq_find_mapping(domain, __ffs(pending));
+	else if (!ipd->info->irq_offset &&
+		 (readl(&regs->eth.eisr) & readl(&regs->eth.eier)))
+		irq = irq_find_mapping(domain, 23);
+
+	if (irq)
+		generic_handle_irq(irq);
+	else
+		spurious_interrupt();
+}
+
+static struct resource ioc3_uarta_resources[] = {
+	DEFINE_RES_MEM(offsetof(struct ioc3, sregs.uarta),
+		       sizeof_field(struct ioc3, sregs.uarta)),
+	DEFINE_RES_IRQ(6)
+};
+
+static struct resource ioc3_uartb_resources[] = {
+	DEFINE_RES_MEM(offsetof(struct ioc3, sregs.uartb),
+		       sizeof_field(struct ioc3, sregs.uartb)),
+	DEFINE_RES_IRQ(15)
+};
+
+static struct resource ioc3_kbd_resources[] = {
+	DEFINE_RES_MEM(offsetof(struct ioc3, serio),
+		       sizeof_field(struct ioc3, serio)),
+	DEFINE_RES_IRQ(22)
+};
+
+static struct resource ioc3_eth_resources[] = {
+	DEFINE_RES_MEM(offsetof(struct ioc3, eth),
+		       sizeof_field(struct ioc3, eth)),
+	DEFINE_RES_MEM(offsetof(struct ioc3, ssram),
+		       sizeof_field(struct ioc3, ssram)),
+	DEFINE_RES_IRQ(0)
+};
+
+static struct ioc3eth_platform_data ioc3_eth_platform_data;
+
+#ifdef CONFIG_SGI_IP27
+
+static struct resource ioc3_rtc_resources[] = {
+	DEFINE_RES_MEM(IOC3_BYTEBUS_DEV0, 32768)
+};
+
+static struct ioc3_board_info ip27_baseio_info = {
+	.name = "IP27 BaseIO",
+	.funcs = IOC3_ETH | IOC3_SER | IOC3_M48T35,
+	.irq_offset = 2
+};
+
+static struct ioc3_board_info ip27_baseio6g_info = {
+	.name = "IP27 BaseIO6G",
+	.funcs = IOC3_ETH | IOC3_SER | IOC3_KBD | IOC3_M48T35,
+	.irq_offset = 2
+};
+
+static struct ioc3_board_info ip27_mio_info = {
+	.name = "MIO",
+	.funcs = IOC3_SER | IOC3_PAR | IOC3_KBD,
+	.irq_offset = 0
+};
+
+static struct ioc3_board_info ip29_baseio_info = {
+	.name = "IP29 System Board",
+	.funcs = IOC3_ETH | IOC3_SER | IOC3_PAR | IOC3_KBD | IOC3_M48T35,
+	.irq_offset = 1
+};
+
+#endif /* CONFIG_SGI_IP27 */
+
+static struct ioc3_board_info ioc3_menet_info = {
+	.name = "MENET",
+	.funcs = IOC3_ETH | IOC3_SER,
+	.irq_offset = 4
+};
+
+static struct ioc3_board_info ioc3_cad_duo_info = {
+	.name = "CAD DUO",
+	.funcs = IOC3_ETH | IOC3_KBD,
+	.irq_offset = 0
+};
+
+#define IOC3_BOARD(_partno, _info) {   .info = _info, .match = _partno }
+
+static struct {
+	struct ioc3_board_info *info;
+	const char *match;
+} ioc3_boards[] = {
+#ifdef CONFIG_SGI_IP27
+	IOC3_BOARD("030-0734-", &ip27_baseio6g_info),
+	IOC3_BOARD("030-1023-", &ip27_baseio_info),
+	IOC3_BOARD("030-1124-", &ip27_baseio_info),
+	IOC3_BOARD("030-1025-", &ip29_baseio_info),
+	IOC3_BOARD("030-1244-", &ip29_baseio_info),
+	IOC3_BOARD("030-1389-", &ip29_baseio_info),
+	IOC3_BOARD("030-0880-", &ip27_mio_info),
+#endif
+	IOC3_BOARD("030-0873-", &ioc3_menet_info),
+	IOC3_BOARD("030-1155-", &ioc3_cad_duo_info),
+};
+
+static int ioc3_identify(struct ioc3_priv_data *idp)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ioc3_boards); i++)
+		if (!strncmp(idp->nic_part, ioc3_boards[i].match,
+			     strlen(ioc3_boards[i].match))) {
+			idp->info = ioc3_boards[i].info;
+			return 0;
+		}
+
+	return -1;
+}
+
+static void ioc3_create_devices(struct ioc3_priv_data *ipd)
+{
+	struct mfd_cell *cell;
+
+	memset(ioc3_mfd_cells, 0, sizeof(ioc3_mfd_cells));
+	cell = ioc3_mfd_cells;
+
+	if (ipd->info->funcs & IOC3_ETH) {
+		memcpy(ioc3_eth_platform_data.mac_addr, ipd->nic_mac,
+		       sizeof(ioc3_eth_platform_data.mac_addr));
+		cell->name = "ioc3-eth";
+		cell->id = ioc3_eth_id++;
+		cell->resources = ioc3_eth_resources;
+		cell->num_resources = ARRAY_SIZE(ioc3_eth_resources);
+		cell->platform_data = &ioc3_eth_platform_data;
+		cell->pdata_size = sizeof(ioc3_eth_platform_data);
+		if (ipd->info->irq_offset) {
+			/*
+			 * Ethernet interrupt is on an extra interrupt
+			 * not inside the irq domain, so we need an
+			 * extra mfd_add_devices without the domain
+			 * argument
+			 */
+			ioc3_eth_resources[2].start = ipd->pdev->irq;
+			ioc3_eth_resources[2].end = ipd->pdev->irq;
+			mfd_add_devices(&ipd->pdev->dev, -1, cell, 1,
+					&ipd->pdev->resource[0], 0, NULL);
+			memset(cell, 0, sizeof(*cell));
+		} else {
+			/* fake hwirq in domain */
+			ioc3_eth_resources[2].start = 23;
+			ioc3_eth_resources[2].end = 23;
+			cell++;
+		}
+	}
+	if (ipd->info->funcs & IOC3_SER) {
+		writel(GPCR_UARTA_MODESEL | GPCR_UARTB_MODESEL,
+			&ipd->regs->gpcr_s);
+		writel(0, &ipd->regs->gppr[6]);
+		writel(0, &ipd->regs->gppr[7]);
+		udelay(100);
+		writel(readl(&ipd->regs->port_a.sscr) & ~SSCR_DMA_EN,
+		       &ipd->regs->port_a.sscr);
+		writel(readl(&ipd->regs->port_b.sscr) & ~SSCR_DMA_EN,
+		       &ipd->regs->port_b.sscr);
+		udelay(1000);
+		cell->name = "ioc3-serial8250";
+		cell->id = ioc3_serial_id++;
+		cell->resources = ioc3_uarta_resources;
+		cell->num_resources = ARRAY_SIZE(ioc3_uarta_resources);
+		cell++;
+		cell->name = "ioc3-serial8250";
+		cell->id = ioc3_serial_id++;
+		cell->resources = ioc3_uartb_resources;
+		cell->num_resources = ARRAY_SIZE(ioc3_uartb_resources);
+		cell++;
+	}
+	if (ipd->info->funcs & IOC3_KBD) {
+		cell->name = "ioc3-kbd",
+		cell->id = ioc3_kbd_id++;
+		cell->resources = ioc3_kbd_resources,
+		cell->num_resources = ARRAY_SIZE(ioc3_kbd_resources),
+		cell++;
+	}
+#if defined(CONFIG_SGI_IP27)
+	if (ipd->info->funcs & IOC3_M48T35) {
+		cell->name = "rtc-m48t35";
+		cell->id = -1;
+		cell->resources = ioc3_rtc_resources;
+		cell->num_resources = ARRAY_SIZE(ioc3_rtc_resources);
+		cell++;
+	}
+#endif
+	mfd_add_devices(&ipd->pdev->dev, -1, ioc3_mfd_cells,
+			cell - ioc3_mfd_cells, &ipd->pdev->resource[0],
+			0, ipd->domain);
+}
+
+static int ioc3_mfd_probe(struct pci_dev *pdev,
+			  const struct pci_device_id *pci_id)
+{
+	struct ioc3_priv_data *ipd;
+	int err, ret = -ENODEV, io_irqno;
+	struct ioc3 __iomem *regs;
+	struct irq_domain *domain;
+	struct fwnode_handle *fn;
+
+	err = pci_enable_device(pdev);
+	if (err)
+		return err;
+
+	pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 64);
+	pci_set_master(pdev);
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+	if (ret) {
+		dev_warn(&pdev->dev, "Warning: couldn_t set 64-bit DMA mask\n");
+		ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+		if (ret) {
+			dev_err(&pdev->dev, "Can't set DMA mask, aborting\n");
+			return ret;
+		}
+	}
+
+	/* Set up per-IOC3 data */
+	ipd = kzalloc(sizeof(struct ioc3_priv_data), GFP_KERNEL);
+	if (!ipd) {
+		ret = -ENOMEM;
+		goto out_disable_device;
+	}
+	ipd->pdev = pdev;
+
+	/*
+	 * Map all IOC3 registers.  These are shared between subdevices
+	 * so the main IOC3 module manages them.
+	 */
+	regs = pci_ioremap_bar(pdev, 0);
+	if (!regs) {
+		pr_warn("ioc3: Unable to remap PCI BAR for %s.\n",
+			pci_name(pdev));
+		goto out_free_ipd;
+	}
+	ipd->regs = regs;
+
+	/* Track PCI-device specific data */
+	pci_set_drvdata(pdev, ipd);
+
+	writel(GPCR_MLAN_EN, &ipd->regs->gpcr_s);
+	ioc3_probe_nic(ipd, &regs->mcr);
+
+#ifdef CONFIG_SGI_IP27
+	/* BaseIO NIC is attached to bridge */
+	if (!ipd->nic_part[0]) {
+		struct bridge_controller *bc = BRIDGE_CONTROLLER(pdev->bus);
+
+		ioc3_probe_nic(ipd, &bc->base->b_nic);
+	}
+#endif
+
+	if (ioc3_identify(ipd)) {
+		pr_err("ioc3: part: [%s] unknown card\n", ipd->nic_part);
+		goto out_iounmap;
+	}
+
+	pr_info("ioc3: part: [%s] %s\n", ipd->nic_part, ipd->info->name);
+
+	/* Clear IRQs */
+	writel(~0, &regs->sio_iec);
+	writel(~0, &regs->sio_ir);
+	writel(0, &regs->eth.eier);
+	writel(~0, &regs->eth.eisr);
+
+	if (ipd->info->irq_offset) {
+		struct pci_host_bridge *hbrg = pci_find_host_bridge(pdev->bus);
+
+		io_irqno = hbrg->map_irq(pdev,
+			PCI_SLOT(pdev->devfn) + ipd->info->irq_offset, 0);
+	} else {
+		io_irqno = pdev->irq;
+	}
+	ipd->irq_io = io_irqno;
+
+	fn = irq_domain_alloc_named_fwnode("IOC3");
+	if (!fn)
+		goto out_iounmap;
+
+	domain = irq_domain_create_linear(fn, 24, &ioc3_irq_domain_ops, ipd);
+	irq_domain_free_fwnode(fn);
+	if (!domain)
+		goto out_iounmap;
+	ipd->domain = domain;
+
+	irq_set_chained_handler_and_data(io_irqno, ioc3_irq_handler, domain);
+
+	ioc3_create_devices(ipd);
+
+	return 0;
+
+out_iounmap:
+	iounmap(ipd->regs);
+
+out_free_ipd:
+	kfree(ipd);
+
+out_disable_device:
+	pci_disable_device(pdev);
+	return ret;
+}
+
+static void ioc3_mfd_remove(struct pci_dev *pdev)
+{
+	struct ioc3_priv_data *ipd;
+
+	ipd = pci_get_drvdata(pdev);
+
+	/* Clear and disable all IRQs */
+	writel(~0, &ipd->regs->sio_iec);
+	writel(~0, &ipd->regs->sio_ir);
+
+	/* Release resources */
+	irq_domain_remove(ipd->domain);
+	free_irq(ipd->irq_io, (void *)ipd);
+	iounmap(ipd->regs);
+
+	pci_disable_device(pdev);
+
+	kfree(ipd);
+}
+
+static struct pci_device_id ioc3_mfd_id_table[] = {
+	{ PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_IOC3, PCI_ANY_ID, PCI_ANY_ID },
+	{ 0, },
+};
+MODULE_DEVICE_TABLE(pci, ioc3_mfd_id_table);
+
+static struct pci_driver ioc3_mfd_driver = {
+	.name = "IOC3",
+	.id_table = ioc3_mfd_id_table,
+	.probe = ioc3_mfd_probe,
+	.remove = ioc3_mfd_remove,
+};
+
+module_pci_driver(ioc3_mfd_driver);
+
+MODULE_AUTHOR("Thomas Bogendoerfer <tbogendoerfer@suse.de>");
+MODULE_DESCRIPTION("SGI IOC3 MFD driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/net/ethernet/sgi/Kconfig b/drivers/net/ethernet/sgi/Kconfig
index fbbb21c13e95..814f793cf6fe 100644
--- a/drivers/net/ethernet/sgi/Kconfig
+++ b/drivers/net/ethernet/sgi/Kconfig
@@ -5,7 +5,7 @@
 config NET_VENDOR_SGI
 	bool "SGI devices"
 	default y
-	depends on (PCI && SGI_IP27) || SGI_IP32
+	depends on (PCI && SGI_MFD_IOC3) ||  SGI_IP32
 	---help---
 	  If you have a network (Ethernet) card belonging to this class, say Y.
 
@@ -18,7 +18,7 @@ if NET_VENDOR_SGI
 
 config SGI_IOC3_ETH
 	bool "SGI IOC3 Ethernet"
-	depends on PCI && SGI_IP27
+	depends on PCI && SGI_MFD_IOC3
 	select CRC32
 	select MII
 	---help---
diff --git a/drivers/net/ethernet/sgi/ioc3-eth.c b/drivers/net/ethernet/sgi/ioc3-eth.c
index 358e66b81926..21fe722ebcd8 100644
--- a/drivers/net/ethernet/sgi/ioc3-eth.c
+++ b/drivers/net/ethernet/sgi/ioc3-eth.c
@@ -7,6 +7,8 @@
  *
  * Copyright (C) 1999, 2000, 01, 03, 06 Ralf Baechle
  * Copyright (C) 1995, 1999, 2000, 2001 by Silicon Graphics, Inc.
+ * Copyright (C) 2005 Stanislaw Skowronek (port to meta-driver)
+ *               2009 Johannes Dickgreber <tanzy@gmx.de>
  *
  * References:
  *  o IOC3 ASIC specification 4.51, 1996-04-18
@@ -20,8 +22,6 @@
  *  o Use prefetching for large packets.  What is a good lower limit for
  *    prefetching?
  *  o We're probably allocating a bit too much memory.
- *  o Use hardware checksums.
- *  o Convert to using a IOC3 meta driver.
  *  o Which PHYs might possibly be attached to the IOC3 in real live,
  *    which workarounds are required for them?  Do we ever have Lucent's?
  *  o For the 2.5 branch kill the mii-tool ioctls.
@@ -30,484 +30,220 @@
 #define IOC3_NAME	"ioc3-eth"
 #define IOC3_VERSION	"2.6.3-4"
 
+#include <net/ip.h>
+
+#include <linux/crc32.h>
 #include <linux/delay.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
 #include <linux/kernel.h>
-#include <linux/mm.h>
-#include <linux/errno.h>
-#include <linux/module.h>
-#include <linux/pci.h>
-#include <linux/crc32.h>
 #include <linux/mii.h>
-#include <linux/in.h>
-#include <linux/ip.h>
+#include <linux/netdevice.h>
 #include <linux/tcp.h>
 #include <linux/udp.h>
-#include <linux/dma-mapping.h>
+#include <linux/module.h>
 #include <linux/gfp.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/ioc3eth.h>
 
-#ifdef CONFIG_SERIAL_8250
-#include <linux/serial_core.h>
-#include <linux/serial_8250.h>
-#include <linux/serial_reg.h>
-#endif
-
-#include <linux/netdevice.h>
-#include <linux/etherdevice.h>
-#include <linux/ethtool.h>
-#include <linux/skbuff.h>
-#include <net/ip.h>
-
-#include <asm/byteorder.h>
-#include <asm/io.h>
-#include <asm/pgtable.h>
-#include <linux/uaccess.h>
-#include <asm/sn/types.h>
 #include <asm/sn/ioc3.h>
 #include <asm/pci/bridge.h>
 
+#define IOC3_CACHELINE	128UL
+
 /*
  * 64 RX buffers.  This is tunable in the range of 16 <= x < 512.  The
  * value must be a power of two.
  */
-#define RX_BUFFS 64
+#define RX_BUFFS 512
+#define RX_MASK (RX_BUFFS - 1)
 
-#define ETCSR_FD	((17<<ETCSR_IPGR2_SHIFT) | (11<<ETCSR_IPGR1_SHIFT) | 21)
-#define ETCSR_HD	((21<<ETCSR_IPGR2_SHIFT) | (21<<ETCSR_IPGR1_SHIFT) | 21)
+/* 128 TX buffers (not tunable) */
+#define TX_BUFFS 128
+#define TX_MASK (TX_BUFFS - 1)
+
+/* BEWARE: The IOC3 documentation documents the size of rx buffers as
+ * 1644 while it's actually 1664.  This one was nasty to track down...
+ */
+#define RX_OFFSET		18
+#define RX_BUF_SIZE		1664
+#define RX_BUF_ALLOC_SIZE	(RX_BUF_SIZE + RX_OFFSET + IOC3_CACHELINE)
 
-/* Private per NIC data of the driver.  */
+/* Private per NIC data of the driver. */
 struct ioc3_private {
-	struct ioc3 *regs;
+	struct ioc3_ethregs *regs;
+	u32 *ssram;
 	unsigned long *rxr;		/* pointer to receiver ring */
 	struct ioc3_etxd *txr;
-	struct sk_buff *rx_skbs[512];
-	struct sk_buff *tx_skbs[128];
+	struct sk_buff *rx_skbs[RX_BUFFS];
+	struct sk_buff *tx_skbs[TX_BUFFS];
 	int rx_ci;			/* RX consumer index */
 	int rx_pi;			/* RX producer index */
 	int tx_ci;			/* TX consumer index */
 	int tx_pi;			/* TX producer index */
-	int txqlen;
+	int txbfree;
 	u32 emcr, ehar_h, ehar_l;
 	spinlock_t ioc3_lock;
 	struct mii_if_info mii;
+	unsigned long flags;
+ #define IOC3_FLAG_RX_CHECKSUMS 1
 
-	struct net_device *dev;
-	struct pci_dev *pdev;
-
-	/* Members used by autonegotiation  */
 	struct timer_list ioc3_timer;
 };
 
-static int ioc3_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
-static void ioc3_set_multicast_list(struct net_device *dev);
-static netdev_tx_t ioc3_start_xmit(struct sk_buff *skb, struct net_device *dev);
-static void ioc3_timeout(struct net_device *dev);
-static inline unsigned int ioc3_hash(const unsigned char *addr);
-static inline void ioc3_stop(struct ioc3_private *ip);
-static void ioc3_init(struct net_device *dev);
-
-static const char ioc3_str[] = "IOC3 Ethernet";
-static const struct ethtool_ops ioc3_ethtool_ops;
-
 /* We use this to acquire receive skb's that we can DMA directly into. */
-
-#define IOC3_CACHELINE	128UL
-
 static inline unsigned long aligned_rx_skb_addr(unsigned long addr)
 {
-	return (~addr + 1) & (IOC3_CACHELINE - 1UL);
+	return ((addr + 127) & ~127UL) - addr;
 }
 
-static inline struct sk_buff * ioc3_alloc_skb(unsigned long length,
-	unsigned int gfp_mask)
+static struct sk_buff *ioc3_alloc_skb(struct net_device *dev)
 {
 	struct sk_buff *skb;
 
-	skb = alloc_skb(length + IOC3_CACHELINE - 1, gfp_mask);
+	skb = alloc_skb(RX_BUF_ALLOC_SIZE + IOC3_CACHELINE - 1, GFP_ATOMIC);
 	if (likely(skb)) {
 		int offset = aligned_rx_skb_addr((unsigned long) skb->data);
 		if (offset)
 			skb_reserve(skb, offset);
+		skb->dev = dev;
 	}
 
 	return skb;
 }
 
-static inline unsigned long ioc3_map(void *ptr, unsigned long vdev)
+static unsigned long ioc3_map(void *ptr, unsigned long dma_attr)
 {
 #ifdef CONFIG_SGI_IP27
-	vdev <<= 57;   /* Shift to PCI64_ATTR_VIRTUAL */
-
-	return vdev | (0xaUL << PCI64_ATTR_TARG_SHFT) | PCI64_ATTR_PREF |
-	       ((unsigned long)ptr & TO_PHYS_MASK);
+	return ((0xaUL << PCI64_ATTR_TARG_SHFT) | dma_attr |
+		((unsigned long)ptr & TO_PHYS_MASK));
 #else
 	return virt_to_bus(ptr);
 #endif
 }
 
-/* BEWARE: The IOC3 documentation documents the size of rx buffers as
-   1644 while it's actually 1664.  This one was nasty to track down ...  */
-#define RX_OFFSET		10
-#define RX_BUF_ALLOC_SIZE	(1664 + RX_OFFSET + IOC3_CACHELINE)
-
-/* DMA barrier to separate cached and uncached accesses.  */
-#define BARRIER()							\
-	__asm__("sync" ::: "memory")
-
-
-#define IOC3_SIZE 0x100000
-
-/*
- * IOC3 is a big endian device
- *
- * Unorthodox but makes the users of these macros more readable - the pointer
- * to the IOC3's memory mapped registers is expected as struct ioc3 * ioc3
- * in the environment.
- */
-#define ioc3_r_mcr()		be32_to_cpu(ioc3->mcr)
-#define ioc3_w_mcr(v)		do { ioc3->mcr = cpu_to_be32(v); } while (0)
-#define ioc3_w_gpcr_s(v)	do { ioc3->gpcr_s = cpu_to_be32(v); } while (0)
-#define ioc3_r_emcr()		be32_to_cpu(ioc3->emcr)
-#define ioc3_w_emcr(v)		do { ioc3->emcr = cpu_to_be32(v); } while (0)
-#define ioc3_r_eisr()		be32_to_cpu(ioc3->eisr)
-#define ioc3_w_eisr(v)		do { ioc3->eisr = cpu_to_be32(v); } while (0)
-#define ioc3_r_eier()		be32_to_cpu(ioc3->eier)
-#define ioc3_w_eier(v)		do { ioc3->eier = cpu_to_be32(v); } while (0)
-#define ioc3_r_ercsr()		be32_to_cpu(ioc3->ercsr)
-#define ioc3_w_ercsr(v)		do { ioc3->ercsr = cpu_to_be32(v); } while (0)
-#define ioc3_r_erbr_h()		be32_to_cpu(ioc3->erbr_h)
-#define ioc3_w_erbr_h(v)	do { ioc3->erbr_h = cpu_to_be32(v); } while (0)
-#define ioc3_r_erbr_l()		be32_to_cpu(ioc3->erbr_l)
-#define ioc3_w_erbr_l(v)	do { ioc3->erbr_l = cpu_to_be32(v); } while (0)
-#define ioc3_r_erbar()		be32_to_cpu(ioc3->erbar)
-#define ioc3_w_erbar(v)		do { ioc3->erbar = cpu_to_be32(v); } while (0)
-#define ioc3_r_ercir()		be32_to_cpu(ioc3->ercir)
-#define ioc3_w_ercir(v)		do { ioc3->ercir = cpu_to_be32(v); } while (0)
-#define ioc3_r_erpir()		be32_to_cpu(ioc3->erpir)
-#define ioc3_w_erpir(v)		do { ioc3->erpir = cpu_to_be32(v); } while (0)
-#define ioc3_r_ertr()		be32_to_cpu(ioc3->ertr)
-#define ioc3_w_ertr(v)		do { ioc3->ertr = cpu_to_be32(v); } while (0)
-#define ioc3_r_etcsr()		be32_to_cpu(ioc3->etcsr)
-#define ioc3_w_etcsr(v)		do { ioc3->etcsr = cpu_to_be32(v); } while (0)
-#define ioc3_r_ersr()		be32_to_cpu(ioc3->ersr)
-#define ioc3_w_ersr(v)		do { ioc3->ersr = cpu_to_be32(v); } while (0)
-#define ioc3_r_etcdc()		be32_to_cpu(ioc3->etcdc)
-#define ioc3_w_etcdc(v)		do { ioc3->etcdc = cpu_to_be32(v); } while (0)
-#define ioc3_r_ebir()		be32_to_cpu(ioc3->ebir)
-#define ioc3_w_ebir(v)		do { ioc3->ebir = cpu_to_be32(v); } while (0)
-#define ioc3_r_etbr_h()		be32_to_cpu(ioc3->etbr_h)
-#define ioc3_w_etbr_h(v)	do { ioc3->etbr_h = cpu_to_be32(v); } while (0)
-#define ioc3_r_etbr_l()		be32_to_cpu(ioc3->etbr_l)
-#define ioc3_w_etbr_l(v)	do { ioc3->etbr_l = cpu_to_be32(v); } while (0)
-#define ioc3_r_etcir()		be32_to_cpu(ioc3->etcir)
-#define ioc3_w_etcir(v)		do { ioc3->etcir = cpu_to_be32(v); } while (0)
-#define ioc3_r_etpir()		be32_to_cpu(ioc3->etpir)
-#define ioc3_w_etpir(v)		do { ioc3->etpir = cpu_to_be32(v); } while (0)
-#define ioc3_r_emar_h()		be32_to_cpu(ioc3->emar_h)
-#define ioc3_w_emar_h(v)	do { ioc3->emar_h = cpu_to_be32(v); } while (0)
-#define ioc3_r_emar_l()		be32_to_cpu(ioc3->emar_l)
-#define ioc3_w_emar_l(v)	do { ioc3->emar_l = cpu_to_be32(v); } while (0)
-#define ioc3_r_ehar_h()		be32_to_cpu(ioc3->ehar_h)
-#define ioc3_w_ehar_h(v)	do { ioc3->ehar_h = cpu_to_be32(v); } while (0)
-#define ioc3_r_ehar_l()		be32_to_cpu(ioc3->ehar_l)
-#define ioc3_w_ehar_l(v)	do { ioc3->ehar_l = cpu_to_be32(v); } while (0)
-#define ioc3_r_micr()		be32_to_cpu(ioc3->micr)
-#define ioc3_w_micr(v)		do { ioc3->micr = cpu_to_be32(v); } while (0)
-#define ioc3_r_midr_r()		be32_to_cpu(ioc3->midr_r)
-#define ioc3_w_midr_r(v)	do { ioc3->midr_r = cpu_to_be32(v); } while (0)
-#define ioc3_r_midr_w()		be32_to_cpu(ioc3->midr_w)
-#define ioc3_w_midr_w(v)	do { ioc3->midr_w = cpu_to_be32(v); } while (0)
-
-static inline u32 mcr_pack(u32 pulse, u32 sample)
-{
-	return (pulse << 10) | (sample << 2);
-}
-
-static int nic_wait(struct ioc3 *ioc3)
-{
-	u32 mcr;
-
-        do {
-                mcr = ioc3_r_mcr();
-        } while (!(mcr & 2));
-
-        return mcr & 1;
-}
-
-static int nic_reset(struct ioc3 *ioc3)
-{
-        int presence;
-
-	ioc3_w_mcr(mcr_pack(500, 65));
-	presence = nic_wait(ioc3);
-
-	ioc3_w_mcr(mcr_pack(0, 500));
-	nic_wait(ioc3);
-
-        return presence;
-}
-
-static inline int nic_read_bit(struct ioc3 *ioc3)
-{
-	int result;
-
-	ioc3_w_mcr(mcr_pack(6, 13));
-	result = nic_wait(ioc3);
-	ioc3_w_mcr(mcr_pack(0, 100));
-	nic_wait(ioc3);
-
-	return result;
-}
-
-static inline void nic_write_bit(struct ioc3 *ioc3, int bit)
+static void __ioc3_set_mac_address(struct net_device *dev,
+				   struct ioc3_ethregs *regs)
 {
-	if (bit)
-		ioc3_w_mcr(mcr_pack(6, 110));
-	else
-		ioc3_w_mcr(mcr_pack(80, 30));
-
-	nic_wait(ioc3);
-}
-
-/*
- * Read a byte from an iButton device
- */
-static u32 nic_read_byte(struct ioc3 *ioc3)
-{
-	u32 result = 0;
-	int i;
-
-	for (i = 0; i < 8; i++)
-		result = (result >> 1) | (nic_read_bit(ioc3) << 7);
-
-	return result;
+	writel((dev->dev_addr[5] <<  8) |
+		dev->dev_addr[4],
+		&regs->emar_h);
+	writel((dev->dev_addr[3] << 24) |
+		(dev->dev_addr[2] << 16) |
+		(dev->dev_addr[1] <<  8) |
+		dev->dev_addr[0],
+		&regs->emar_l);
 }
 
 /*
- * Write a byte to an iButton device
+ * Caller must hold the ioc3_lock ever for MII readers.  This is also
+ * used to protect the transmitter side but it's low contention.
  */
-static void nic_write_byte(struct ioc3 *ioc3, int byte)
+static int ioc3_mdio_read(struct net_device *dev, int phy, int reg)
 {
-	int i, bit;
+	struct ioc3_private *ip = netdev_priv(dev);
+	struct ioc3_ethregs *regs = ip->regs;
 
-	for (i = 8; i; i--) {
-		bit = byte & 1;
-		byte >>= 1;
+	while (readl(&regs->micr) & MICR_BUSY)
+		;
+	writel((phy << MICR_PHYADDR_SHIFT) | reg | MICR_READTRIG,
+	       &regs->micr);
+	while (readl(&regs->micr) & MICR_BUSY)
+		;
 
-		nic_write_bit(ioc3, bit);
-	}
+	return readl(&regs->midr_r) & MIDR_DATA_MASK;
 }
 
-static u64 nic_find(struct ioc3 *ioc3, int *last)
+static void ioc3_mdio_write(struct net_device *dev, int phy, int reg, int data)
 {
-	int a, b, index, disc;
-	u64 address = 0;
-
-	nic_reset(ioc3);
-	/* Search ROM.  */
-	nic_write_byte(ioc3, 0xf0);
-
-	/* Algorithm from ``Book of iButton Standards''.  */
-	for (index = 0, disc = 0; index < 64; index++) {
-		a = nic_read_bit(ioc3);
-		b = nic_read_bit(ioc3);
-
-		if (a && b) {
-			printk("NIC search failed (not fatal).\n");
-			*last = 0;
-			return 0;
-		}
-
-		if (!a && !b) {
-			if (index == *last) {
-				address |= 1UL << index;
-			} else if (index > *last) {
-				address &= ~(1UL << index);
-				disc = index;
-			} else if ((address & (1UL << index)) == 0)
-				disc = index;
-			nic_write_bit(ioc3, address & (1UL << index));
-			continue;
-		} else {
-			if (a)
-				address |= 1UL << index;
-			else
-				address &= ~(1UL << index);
-			nic_write_bit(ioc3, a);
-			continue;
-		}
-	}
-
-	*last = disc;
-
-	return address;
+	struct ioc3_private *ip = netdev_priv(dev);
+	struct ioc3_ethregs *regs = ip->regs;
+
+	while (readl(&regs->micr) & MICR_BUSY)
+		;
+	writel(data, &regs->midr_w);
+	writel((phy << MICR_PHYADDR_SHIFT) | reg, &regs->micr);
+	while (readl(&regs->micr) & MICR_BUSY)
+		;
 }
 
-static int nic_init(struct ioc3 *ioc3)
+static void ioc3_stop(struct ioc3_private *ip)
 {
-	const char *unknown = "unknown";
-	const char *type = unknown;
-	u8 crc;
-	u8 serial[6];
-	int save = 0, i;
-
-	while (1) {
-		u64 reg;
-		reg = nic_find(ioc3, &save);
-
-		switch (reg & 0xff) {
-		case 0x91:
-			type = "DS1981U";
-			break;
-		default:
-			if (save == 0) {
-				/* Let the caller try again.  */
-				return -1;
-			}
-			continue;
-		}
-
-		nic_reset(ioc3);
-
-		/* Match ROM.  */
-		nic_write_byte(ioc3, 0x55);
-		for (i = 0; i < 8; i++)
-			nic_write_byte(ioc3, (reg >> (i << 3)) & 0xff);
-
-		reg >>= 8; /* Shift out type.  */
-		for (i = 0; i < 6; i++) {
-			serial[i] = reg & 0xff;
-			reg >>= 8;
-		}
-		crc = reg & 0xff;
-		break;
-	}
+	struct ioc3_ethregs *regs = ip->regs;
 
-	printk("Found %s NIC", type);
-	if (type != unknown)
-		printk (" registration number %pM, CRC %02x", serial, crc);
-	printk(".\n");
-
-	return 0;
+	writel(0, &regs->emcr);		/* Shutup */
+	writel(0, &regs->eier);		/* Disable interrupts */
+	(void)readl(&regs->eier);	/* Flush */
 }
 
 /*
- * Read the NIC (Number-In-a-Can) device used to store the MAC address on
- * SN0 / SN00 nodeboards and PCI cards.
+ * Given a multicast ethernet address, this routine calculates the
+ * address's bit index in the logical address filter mask
  */
-static void ioc3_get_eaddr_nic(struct ioc3_private *ip)
+static u32 ioc3_hash(const u8 *addr)
 {
-	struct ioc3 *ioc3 = ip->regs;
-	u8 nic[14];
-	int tries = 2; /* There may be some problem with the battery?  */
-	int i;
-
-	ioc3_w_gpcr_s(1 << 21);
+	u32 crc, temp = 0;
+	int bits;
 
-	while (tries--) {
-		if (!nic_init(ioc3))
-			break;
-		udelay(500);
-	}
+	crc = ether_crc_le(ETH_ALEN, addr);
 
-	if (tries < 0) {
-		printk("Failed to read MAC address\n");
-		return;
+	crc &= 0x3f;    /* bit reverse lowest 6 bits for hash index */
+	for (bits = 6; --bits >= 0; ) {
+		temp <<= 1;
+		temp |= (crc & 0x1);
+		crc >>= 1;
 	}
 
-	/* Read Memory.  */
-	nic_write_byte(ioc3, 0xf0);
-	nic_write_byte(ioc3, 0x00);
-	nic_write_byte(ioc3, 0x00);
-
-	for (i = 13; i >= 0; i--)
-		nic[i] = nic_read_byte(ioc3);
-
-	for (i = 2; i < 8; i++)
-		ip->dev->dev_addr[i - 2] = nic[i];
-}
-
-/*
- * Ok, this is hosed by design.  It's necessary to know what machine the
- * NIC is in in order to know how to read the NIC address.  We also have
- * to know if it's a PCI card or a NIC in on the node board ...
- */
-static void ioc3_get_eaddr(struct ioc3_private *ip)
-{
-	ioc3_get_eaddr_nic(ip);
-
-	printk("Ethernet address is %pM.\n", ip->dev->dev_addr);
-}
-
-static void __ioc3_set_mac_address(struct net_device *dev)
-{
-	struct ioc3_private *ip = netdev_priv(dev);
-	struct ioc3 *ioc3 = ip->regs;
-
-	ioc3_w_emar_h((dev->dev_addr[5] <<  8) | dev->dev_addr[4]);
-	ioc3_w_emar_l((dev->dev_addr[3] << 24) | (dev->dev_addr[2] << 16) |
-	              (dev->dev_addr[1] <<  8) | dev->dev_addr[0]);
+	return temp;
 }
 
-static int ioc3_set_mac_address(struct net_device *dev, void *addr)
+static void ioc3_set_multicast_list(struct net_device *dev)
 {
+	struct netdev_hw_addr *ha;
 	struct ioc3_private *ip = netdev_priv(dev);
-	struct sockaddr *sa = addr;
-
-	memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
+	struct ioc3_ethregs *regs = ip->regs;
+	u64 ehar = 0;
 
 	spin_lock_irq(&ip->ioc3_lock);
-	__ioc3_set_mac_address(dev);
-	spin_unlock_irq(&ip->ioc3_lock);
-
-	return 0;
-}
-
-/*
- * Caller must hold the ioc3_lock ever for MII readers.  This is also
- * used to protect the transmitter side but it's low contention.
- */
-static int ioc3_mdio_read(struct net_device *dev, int phy, int reg)
-{
-	struct ioc3_private *ip = netdev_priv(dev);
-	struct ioc3 *ioc3 = ip->regs;
-
-	while (ioc3_r_micr() & MICR_BUSY);
-	ioc3_w_micr((phy << MICR_PHYADDR_SHIFT) | reg | MICR_READTRIG);
-	while (ioc3_r_micr() & MICR_BUSY);
-
-	return ioc3_r_midr_r() & MIDR_DATA_MASK;
-}
-
-static void ioc3_mdio_write(struct net_device *dev, int phy, int reg, int data)
-{
-	struct ioc3_private *ip = netdev_priv(dev);
-	struct ioc3 *ioc3 = ip->regs;
-
-	while (ioc3_r_micr() & MICR_BUSY);
-	ioc3_w_midr_w(data);
-	ioc3_w_micr((phy << MICR_PHYADDR_SHIFT) | reg);
-	while (ioc3_r_micr() & MICR_BUSY);
-}
-
-static int ioc3_mii_init(struct ioc3_private *ip);
+	if (dev->flags & IFF_PROMISC) {		/* Set promiscuous.  */
+		ip->emcr |= EMCR_PROMISC;
+		writel(ip->emcr, &regs->emcr);
+		(void)readl(&regs->emcr);
+	} else {
+		ip->emcr &= ~EMCR_PROMISC;
+		writel(ip->emcr, &regs->emcr);	/* Clear promiscuous. */
+		(void)readl(&regs->emcr);
 
-static struct net_device_stats *ioc3_get_stats(struct net_device *dev)
-{
-	struct ioc3_private *ip = netdev_priv(dev);
-	struct ioc3 *ioc3 = ip->regs;
+		if ((dev->flags & IFF_ALLMULTI) ||
+		    (netdev_mc_count(dev) > 64)) {
+			/* Too many for hashing to make sense or we want all
+			 * multicast packets anyway,  so skip computing all
+			 * the hashes and just accept all packets.
+			 */
+			ip->ehar_h = 0xffffffff;
+			ip->ehar_l = 0xffffffff;
+		} else {
+			netdev_for_each_mc_addr(ha, dev) {
+				ehar |= (1UL << ioc3_hash(ha->addr));
+			}
+			ip->ehar_h = ehar >> 32;
+			ip->ehar_l = ehar & 0xffffffff;
+		}
+		writel(ip->ehar_h, &regs->ehar_h);
+		writel(ip->ehar_l, &regs->ehar_l);
+	}
 
-	dev->stats.collisions += (ioc3_r_etcdc() & ETCDC_COLLCNT_MASK);
-	return &dev->stats;
+	spin_unlock_irq(&ip->ioc3_lock);
 }
 
 static void ioc3_tcpudp_checksum(struct sk_buff *skb, uint32_t hwsum, int len)
 {
 	struct ethhdr *eh = eth_hdr(skb);
-	uint32_t csum, ehsum;
-	unsigned int proto;
 	struct iphdr *ih;
-	uint16_t *ew;
-	unsigned char *cp;
+	u8 *cp;
+	u16 *ew;
+	u32 csum, ehsum, proto;
 
 	/*
 	 * Did hardware handle the checksum at all?  The cases we can handle
@@ -568,277 +304,95 @@ static void ioc3_tcpudp_checksum(struct sk_buff *skb, uint32_t hwsum, int len)
 		skb->ip_summed = CHECKSUM_UNNECESSARY;
 }
 
-static inline void ioc3_rx(struct net_device *dev)
-{
-	struct ioc3_private *ip = netdev_priv(dev);
-	struct sk_buff *skb, *new_skb;
-	struct ioc3 *ioc3 = ip->regs;
-	int rx_entry, n_entry, len;
-	struct ioc3_erxbuf *rxb;
-	unsigned long *rxr;
-	u32 w0, err;
+#define ETCSR_FD ((17 << ETCSR_IPGR2_SHIFT) | (11 << ETCSR_IPGR1_SHIFT) | 21)
+#define ETCSR_HD ((21 << ETCSR_IPGR2_SHIFT) | (21 << ETCSR_IPGR1_SHIFT) | 21)
 
-	rxr = ip->rxr;		/* Ring base */
-	rx_entry = ip->rx_ci;				/* RX consume index */
-	n_entry = ip->rx_pi;
+static void ioc3_setup_duplex(struct ioc3_private *ip)
+{
+	struct ioc3_ethregs *regs = ip->regs;
 
-	skb = ip->rx_skbs[rx_entry];
-	rxb = (struct ioc3_erxbuf *) (skb->data - RX_OFFSET);
-	w0 = be32_to_cpu(rxb->w0);
+	if (ip->mii.full_duplex) {
+		writel(ETCSR_FD, &regs->etcsr);
+		ip->emcr |= EMCR_DUPLEX;
+	} else {
+		writel(ETCSR_HD, &regs->etcsr);
+		ip->emcr &= ~EMCR_DUPLEX;
+	}
+	writel(ip->emcr, &regs->emcr);
+	(void)readl(&regs->emcr);
+}
 
-	while (w0 & ERXBUF_V) {
-		err = be32_to_cpu(rxb->err);		/* It's valid ...  */
-		if (err & ERXBUF_GOODPKT) {
-			len = ((w0 >> ERXBUF_BYTECNT_SHIFT) & 0x7ff) - 4;
-			skb_trim(skb, len);
-			skb->protocol = eth_type_trans(skb, dev);
+static void ioc3_timer(struct timer_list *t)
+{
+	struct ioc3_private *ip = from_timer(ip, t, ioc3_timer);
 
-			new_skb = ioc3_alloc_skb(RX_BUF_ALLOC_SIZE, GFP_ATOMIC);
-			if (!new_skb) {
-				/* Ouch, drop packet and just recycle packet
-				   to keep the ring filled.  */
-				dev->stats.rx_dropped++;
-				new_skb = skb;
-				goto next;
-			}
+	/* Print the link status if it has changed */
+	mii_check_media(&ip->mii, 1, 0);
+	ioc3_setup_duplex(ip);
 
-			if (likely(dev->features & NETIF_F_RXCSUM))
-				ioc3_tcpudp_checksum(skb,
-					w0 & ERXBUF_IPCKSUM_MASK, len);
+	ip->ioc3_timer.expires = jiffies + ((12 * HZ) / 10); /* 1.2s */
+	add_timer(&ip->ioc3_timer);
+}
 
-			netif_rx(skb);
+/* Try to find a PHY.  There is no apparent relation between the MII addresses
+ * in the SGI documentation and what we find in reality, so we simply probe
+ * for the PHY.  It seems IOC3 PHYs usually live on address 31.  One of my
+ * onboard IOC3s has the special oddity that probing doesn't seem to find it
+ * yet the interface seems to work fine, so if probing fails we for now will
+ * simply default to PHY 31 instead of bailing out.
+ */
+static int ioc3_mii_init(struct ioc3_private *ip)
+{
+	int i, found = 0, res = 0;
+	int ioc3_phy_workaround = 1;
+	u16 word;
 
-			ip->rx_skbs[rx_entry] = NULL;	/* Poison  */
+	for (i = 0; i < 32; i++) {
+		word = ioc3_mdio_read(ip->mii.dev, i, MII_PHYSID1);
 
-			/* Because we reserve afterwards. */
-			skb_put(new_skb, (1664 + RX_OFFSET));
-			rxb = (struct ioc3_erxbuf *) new_skb->data;
-			skb_reserve(new_skb, RX_OFFSET);
+		if (word != 0xffff && word != 0x0000) {
+			found = 1;
+			break;  /* Found a PHY */
+		}
+	}
 
-			dev->stats.rx_packets++;		/* Statistics */
-			dev->stats.rx_bytes += len;
+	if (!found) {
+		if (ioc3_phy_workaround) {
+			i = 31;
 		} else {
-			/* The frame is invalid and the skb never
-			   reached the network layer so we can just
-			   recycle it.  */
-			new_skb = skb;
-			dev->stats.rx_errors++;
+			ip->mii.phy_id = -1;
+			res = -ENODEV;
+			goto out;
 		}
-		if (err & ERXBUF_CRCERR)	/* Statistics */
-			dev->stats.rx_crc_errors++;
-		if (err & ERXBUF_FRAMERR)
-			dev->stats.rx_frame_errors++;
-next:
-		ip->rx_skbs[n_entry] = new_skb;
-		rxr[n_entry] = cpu_to_be64(ioc3_map(rxb, 1));
-		rxb->w0 = 0;				/* Clear valid flag */
-		n_entry = (n_entry + 1) & 511;		/* Update erpir */
-
-		/* Now go on to the next ring entry.  */
-		rx_entry = (rx_entry + 1) & 511;
-		skb = ip->rx_skbs[rx_entry];
-		rxb = (struct ioc3_erxbuf *) (skb->data - RX_OFFSET);
-		w0 = be32_to_cpu(rxb->w0);
 	}
-	ioc3_w_erpir((n_entry << 3) | ERPIR_ARM);
-	ip->rx_pi = n_entry;
-	ip->rx_ci = rx_entry;
+
+	ip->mii.phy_id = i;
+
+out:
+	return res;
 }
 
-static inline void ioc3_tx(struct net_device *dev)
+static void ioc3_mii_start(struct ioc3_private *ip)
+{
+	ip->ioc3_timer.expires = jiffies + (12 * HZ) / 10;  /* 1.2 sec. */
+	add_timer(&ip->ioc3_timer);
+}
+
+static void ioc3_clean_rx_ring(struct ioc3_private *ip)
 {
-	struct ioc3_private *ip = netdev_priv(dev);
-	unsigned long packets, bytes;
-	struct ioc3 *ioc3 = ip->regs;
-	int tx_entry, o_entry;
 	struct sk_buff *skb;
-	u32 etcir;
+	struct ioc3_erxbuf *rxb;
+	int i;
 
-	spin_lock(&ip->ioc3_lock);
-	etcir = ioc3_r_etcir();
-
-	tx_entry = (etcir >> 7) & 127;
-	o_entry = ip->tx_ci;
-	packets = 0;
-	bytes = 0;
-
-	while (o_entry != tx_entry) {
-		packets++;
-		skb = ip->tx_skbs[o_entry];
-		bytes += skb->len;
-		dev_consume_skb_irq(skb);
-		ip->tx_skbs[o_entry] = NULL;
-
-		o_entry = (o_entry + 1) & 127;		/* Next */
-
-		etcir = ioc3_r_etcir();			/* More pkts sent?  */
-		tx_entry = (etcir >> 7) & 127;
-	}
-
-	dev->stats.tx_packets += packets;
-	dev->stats.tx_bytes += bytes;
-	ip->txqlen -= packets;
-
-	if (ip->txqlen < 128)
-		netif_wake_queue(dev);
-
-	ip->tx_ci = o_entry;
-	spin_unlock(&ip->ioc3_lock);
-}
-
-/*
- * Deal with fatal IOC3 errors.  This condition might be caused by a hard or
- * software problems, so we should try to recover
- * more gracefully if this ever happens.  In theory we might be flooded
- * with such error interrupts if something really goes wrong, so we might
- * also consider to take the interface down.
- */
-static void ioc3_error(struct net_device *dev, u32 eisr)
-{
-	struct ioc3_private *ip = netdev_priv(dev);
-	unsigned char *iface = dev->name;
-
-	spin_lock(&ip->ioc3_lock);
-
-	if (eisr & EISR_RXOFLO)
-		printk(KERN_ERR "%s: RX overflow.\n", iface);
-	if (eisr & EISR_RXBUFOFLO)
-		printk(KERN_ERR "%s: RX buffer overflow.\n", iface);
-	if (eisr & EISR_RXMEMERR)
-		printk(KERN_ERR "%s: RX PCI error.\n", iface);
-	if (eisr & EISR_RXPARERR)
-		printk(KERN_ERR "%s: RX SSRAM parity error.\n", iface);
-	if (eisr & EISR_TXBUFUFLO)
-		printk(KERN_ERR "%s: TX buffer underflow.\n", iface);
-	if (eisr & EISR_TXMEMERR)
-		printk(KERN_ERR "%s: TX PCI error.\n", iface);
-
-	ioc3_stop(ip);
-	ioc3_init(dev);
-	ioc3_mii_init(ip);
-
-	netif_wake_queue(dev);
-
-	spin_unlock(&ip->ioc3_lock);
-}
-
-/* The interrupt handler does all of the Rx thread work and cleans up
-   after the Tx thread.  */
-static irqreturn_t ioc3_interrupt(int irq, void *_dev)
-{
-	struct net_device *dev = (struct net_device *)_dev;
-	struct ioc3_private *ip = netdev_priv(dev);
-	struct ioc3 *ioc3 = ip->regs;
-	const u32 enabled = EISR_RXTIMERINT | EISR_RXOFLO | EISR_RXBUFOFLO |
-	                    EISR_RXMEMERR | EISR_RXPARERR | EISR_TXBUFUFLO |
-	                    EISR_TXEXPLICIT | EISR_TXMEMERR;
-	u32 eisr;
-
-	eisr = ioc3_r_eisr() & enabled;
-
-	ioc3_w_eisr(eisr);
-	(void) ioc3_r_eisr();				/* Flush */
-
-	if (eisr & (EISR_RXOFLO | EISR_RXBUFOFLO | EISR_RXMEMERR |
-	            EISR_RXPARERR | EISR_TXBUFUFLO | EISR_TXMEMERR))
-		ioc3_error(dev, eisr);
-	if (eisr & EISR_RXTIMERINT)
-		ioc3_rx(dev);
-	if (eisr & EISR_TXEXPLICIT)
-		ioc3_tx(dev);
-
-	return IRQ_HANDLED;
-}
-
-static inline void ioc3_setup_duplex(struct ioc3_private *ip)
-{
-	struct ioc3 *ioc3 = ip->regs;
-
-	if (ip->mii.full_duplex) {
-		ioc3_w_etcsr(ETCSR_FD);
-		ip->emcr |= EMCR_DUPLEX;
-	} else {
-		ioc3_w_etcsr(ETCSR_HD);
-		ip->emcr &= ~EMCR_DUPLEX;
-	}
-	ioc3_w_emcr(ip->emcr);
-}
-
-static void ioc3_timer(struct timer_list *t)
-{
-	struct ioc3_private *ip = from_timer(ip, t, ioc3_timer);
-
-	/* Print the link status if it has changed */
-	mii_check_media(&ip->mii, 1, 0);
-	ioc3_setup_duplex(ip);
-
-	ip->ioc3_timer.expires = jiffies + ((12 * HZ)/10); /* 1.2s */
-	add_timer(&ip->ioc3_timer);
-}
-
-/*
- * Try to find a PHY.  There is no apparent relation between the MII addresses
- * in the SGI documentation and what we find in reality, so we simply probe
- * for the PHY.  It seems IOC3 PHYs usually live on address 31.  One of my
- * onboard IOC3s has the special oddity that probing doesn't seem to find it
- * yet the interface seems to work fine, so if probing fails we for now will
- * simply default to PHY 31 instead of bailing out.
- */
-static int ioc3_mii_init(struct ioc3_private *ip)
-{
-	int i, found = 0, res = 0;
-	int ioc3_phy_workaround = 1;
-	u16 word;
-
-	for (i = 0; i < 32; i++) {
-		word = ioc3_mdio_read(ip->dev, i, MII_PHYSID1);
-
-		if (word != 0xffff && word != 0x0000) {
-			found = 1;
-			break;			/* Found a PHY		*/
-		}
-	}
-
-	if (!found) {
-		if (ioc3_phy_workaround)
-			i = 31;
-		else {
-			ip->mii.phy_id = -1;
-			res = -ENODEV;
-			goto out;
-		}
-	}
-
-	ip->mii.phy_id = i;
-
-out:
-	return res;
-}
-
-static void ioc3_mii_start(struct ioc3_private *ip)
-{
-	ip->ioc3_timer.expires = jiffies + (12 * HZ)/10;  /* 1.2 sec. */
-	add_timer(&ip->ioc3_timer);
-}
-
-static inline void ioc3_clean_rx_ring(struct ioc3_private *ip)
-{
-	struct sk_buff *skb;
-	int i;
-
-	for (i = ip->rx_ci; i & 15; i++) {
-		ip->rx_skbs[ip->rx_pi] = ip->rx_skbs[ip->rx_ci];
-		ip->rxr[ip->rx_pi++] = ip->rxr[ip->rx_ci++];
-	}
-	ip->rx_pi &= 511;
-	ip->rx_ci &= 511;
-
-	for (i = ip->rx_ci; i != ip->rx_pi; i = (i+1) & 511) {
-		struct ioc3_erxbuf *rxb;
+	for (i = 0; i < RX_BUFFS; i++) {
 		skb = ip->rx_skbs[i];
-		rxb = (struct ioc3_erxbuf *) (skb->data - RX_OFFSET);
-		rxb->w0 = 0;
+		if (skb) {
+			rxb = (struct ioc3_erxbuf *)(skb->data - RX_OFFSET);
+			rxb->w0 = 0;
+		}
 	}
+	ip->rx_ci = 0;
+	ip->rx_pi = RX_BUFFS - 16;
 }
 
 static inline void ioc3_clean_tx_ring(struct ioc3_private *ip)
@@ -846,7 +400,7 @@ static inline void ioc3_clean_tx_ring(struct ioc3_private *ip)
 	struct sk_buff *skb;
 	int i;
 
-	for (i=0; i < 128; i++) {
+	for (i = 0; i < TX_BUFFS; i++) {
 		skb = ip->tx_skbs[i];
 		if (skb) {
 			ip->tx_skbs[i] = NULL;
@@ -861,7 +415,7 @@ static inline void ioc3_clean_tx_ring(struct ioc3_private *ip)
 static void ioc3_free_rings(struct ioc3_private *ip)
 {
 	struct sk_buff *skb;
-	int rx_entry, n_entry;
+	int i;
 
 	if (ip->txr) {
 		ioc3_clean_tx_ring(ip);
@@ -870,15 +424,10 @@ static void ioc3_free_rings(struct ioc3_private *ip)
 	}
 
 	if (ip->rxr) {
-		n_entry = ip->rx_ci;
-		rx_entry = ip->rx_pi;
-
-		while (n_entry != rx_entry) {
-			skb = ip->rx_skbs[n_entry];
+		for (i = 0; i < RX_BUFFS; i++) {
+			skb = ip->rx_skbs[i];
 			if (skb)
 				dev_kfree_skb_any(skb);
-
-			n_entry = (n_entry + 1) & 511;
 		}
 		free_page((unsigned long)ip->rxr);
 		ip->rxr = NULL;
@@ -897,15 +446,16 @@ static void ioc3_alloc_rings(struct net_device *dev)
 		ip->rxr = (unsigned long *) get_zeroed_page(GFP_ATOMIC);
 		rxr = ip->rxr;
 		if (!rxr)
-			printk("ioc3_alloc_rings(): get_zeroed_page() failed!\n");
+			printk(KERN_ERR "ioc3_alloc_rings(): get_zeroed_page() failed!\n");
 
-		/* Now the rx buffers.  The RX ring may be larger but
-		   we only allocate 16 buffers for now.  Need to tune
-		   this for performance and memory later.  */
+		/* Now the rx buffers.  The RX ring may be larger but we
+		 * only allocate RX_BUFFS buffers for now.  Need to tune
+		 * this for performance and memory later.
+		 */
 		for (i = 0; i < RX_BUFFS; i++) {
 			struct sk_buff *skb;
 
-			skb = ioc3_alloc_skb(RX_BUF_ALLOC_SIZE, GFP_ATOMIC);
+			skb = ioc3_alloc_skb(dev);
 			if (!skb) {
 				show_free_areas(0, NULL);
 				continue;
@@ -914,141 +464,135 @@ static void ioc3_alloc_rings(struct net_device *dev)
 			ip->rx_skbs[i] = skb;
 
 			/* Because we reserve afterwards. */
-			skb_put(skb, (1664 + RX_OFFSET));
+			skb_put(skb, (RX_BUF_SIZE + RX_OFFSET));
 			rxb = (struct ioc3_erxbuf *) skb->data;
-			rxr[i] = cpu_to_be64(ioc3_map(rxb, 1));
+			rxr[i] = cpu_to_be64(ioc3_map(rxb, PCI64_ATTR_BAR));
 			skb_reserve(skb, RX_OFFSET);
 		}
-		ip->rx_ci = 0;
-		ip->rx_pi = RX_BUFFS;
 	}
 
 	if (ip->txr == NULL) {
 		/* Allocate and initialize tx rings.  16kb = 128 bufs.  */
-		ip->txr = (struct ioc3_etxd *)__get_free_pages(GFP_KERNEL, 2);
+		ip->txr = (struct ioc3_etxd *)__get_free_pages((GFP_ATOMIC |
+								__GFP_ZERO), 2);
 		if (!ip->txr)
-			printk("ioc3_alloc_rings(): __get_free_pages() failed!\n");
-		ip->tx_pi = 0;
-		ip->tx_ci = 0;
+			printk(KERN_ERR "ioc3_alloc_rings(): __get_free_pages() failed!\n");
 	}
 }
 
 static void ioc3_init_rings(struct net_device *dev)
 {
 	struct ioc3_private *ip = netdev_priv(dev);
-	struct ioc3 *ioc3 = ip->regs;
+	struct ioc3_ethregs *regs = ip->regs;
 	unsigned long ring;
 
 	ioc3_free_rings(ip);
 	ioc3_alloc_rings(dev);
-
 	ioc3_clean_rx_ring(ip);
 	ioc3_clean_tx_ring(ip);
 
 	/* Now the rx ring base, consume & produce registers.  */
-	ring = ioc3_map(ip->rxr, 0);
-	ioc3_w_erbr_h(ring >> 32);
-	ioc3_w_erbr_l(ring & 0xffffffff);
-	ioc3_w_ercir(ip->rx_ci << 3);
-	ioc3_w_erpir((ip->rx_pi << 3) | ERPIR_ARM);
-
-	ring = ioc3_map(ip->txr, 0);
-
-	ip->txqlen = 0;					/* nothing queued  */
+	ring = ioc3_map(ip->rxr, PCI64_ATTR_VIRTUAL | PCI64_ATTR_PREC);
+	writel(ring >> 32, &regs->erbr_h);
+	writel(ring & 0xffffffff, &regs->erbr_l);
+	writel(ip->rx_ci << 3, &regs->ercir);
+	writel((ip->rx_pi << 3) | ERPIR_ARM, &regs->erpir);
 
 	/* Now the tx ring base, consume & produce registers.  */
-	ioc3_w_etbr_h(ring >> 32);
-	ioc3_w_etbr_l(ring & 0xffffffff);
-	ioc3_w_etpir(ip->tx_pi << 7);
-	ioc3_w_etcir(ip->tx_ci << 7);
-	(void) ioc3_r_etcir();				/* Flush */
+	ring = ioc3_map(ip->txr, PCI64_ATTR_VIRTUAL | PCI64_ATTR_PREC);
+	ip->txbfree = TX_BUFFS;  /* nothing queued  */
+	writel(ring >> 32, &regs->etbr_h);
+	writel(ring & 0xffffffff, &regs->etbr_l);
+	writel(ip->tx_pi << 7, &regs->etpir);
+	writel(ip->tx_ci << 7, &regs->etcir);
+	(void)readl(&regs->etcir);  /* Flush */
 }
 
-static inline void ioc3_ssram_disc(struct ioc3_private *ip)
+static void ioc3_ssram_disc(struct ioc3_private *ip)
 {
-	struct ioc3 *ioc3 = ip->regs;
-	volatile u32 *ssram0 = &ioc3->ssram[0x0000];
-	volatile u32 *ssram1 = &ioc3->ssram[0x4000];
-	unsigned int pattern = 0x5555;
+	struct ioc3_ethregs *regs = ip->regs;
+	u32 *emcr_p = &regs->emcr;
+	u32 *ssram0 = &ip->ssram[0x0000];
+	u32 *ssram1 = &ip->ssram[0x4000];
+	const u32 pattern0 = 0x5555;
+	const u32 pattern1 = ~pattern0 & IOC3_SSRAM_DM;
 
 	/* Assume the larger size SSRAM and enable parity checking */
-	ioc3_w_emcr(ioc3_r_emcr() | (EMCR_BUFSIZ | EMCR_RAMPAR));
+	writel(readl(emcr_p) | EMCR_BUFSIZ | EMCR_RAMPAR, emcr_p);
+	(void)readl(emcr_p);
 
-	*ssram0 = pattern;
-	*ssram1 = ~pattern & IOC3_SSRAM_DM;
+	writel(pattern0, ssram0);
+	writel(pattern1, ssram1);
 
-	if ((*ssram0 & IOC3_SSRAM_DM) != pattern ||
-	    (*ssram1 & IOC3_SSRAM_DM) != (~pattern & IOC3_SSRAM_DM)) {
+	if (((readl(ssram0) & IOC3_SSRAM_DM) != pattern0) ||
+	    ((readl(ssram1) & IOC3_SSRAM_DM) != pattern1)) {
 		/* set ssram size to 64 KB */
-		ip->emcr = EMCR_RAMPAR;
-		ioc3_w_emcr(ioc3_r_emcr() & ~EMCR_BUFSIZ);
-	} else
-		ip->emcr = EMCR_BUFSIZ | EMCR_RAMPAR;
+		ip->emcr |= EMCR_RAMPAR;
+		writel(readl(emcr_p) & ~EMCR_BUFSIZ, emcr_p);
+		(void)readl(emcr_p);
+	} else {
+		ip->emcr |= EMCR_RAMPAR | EMCR_BUFSIZ;
+	}
 }
 
 static void ioc3_init(struct net_device *dev)
 {
 	struct ioc3_private *ip = netdev_priv(dev);
-	struct ioc3 *ioc3 = ip->regs;
+	struct ioc3_ethregs *regs = ip->regs;
 
 	del_timer_sync(&ip->ioc3_timer);	/* Kill if running	*/
 
-	ioc3_w_emcr(EMCR_RST);			/* Reset		*/
-	(void) ioc3_r_emcr();			/* Flush WB		*/
-	udelay(4);				/* Give it time ...	*/
-	ioc3_w_emcr(0);
-	(void) ioc3_r_emcr();
+	writel(EMCR_RST, &regs->emcr);		/* Reset */
+	(void)readl(&regs->emcr);		/* Flush WB */
+	udelay(4);				/* Give it time... */
+	writel(0, &regs->emcr);
+	(void)readl(&regs->emcr);
 
 	/* Misc registers  */
 #ifdef CONFIG_SGI_IP27
-	ioc3_w_erbar(PCI64_ATTR_BAR >> 32);	/* Barrier on last store */
+	/* Barrier on last store */
+	writel(readl(&regs->erbar) |
+		(ERBAR_BARRIER_BIT << ERBAR_RXBARR_SHIFT), &regs->erbar);
 #else
-	ioc3_w_erbar(0);			/* Let PCI API get it right */
+	/* Let PCI API get it right */
+	writel(0, &regs->erbar);
 #endif
-	(void) ioc3_r_etcdc();			/* Clear on read */
-	ioc3_w_ercsr(15);			/* RX low watermark  */
-	ioc3_w_ertr(0);				/* Interrupt immediately */
-	__ioc3_set_mac_address(dev);
-	ioc3_w_ehar_h(ip->ehar_h);
-	ioc3_w_ehar_l(ip->ehar_l);
-	ioc3_w_ersr(42);			/* XXX should be random */
+	readl(&regs->etcdc);			/* Clear on read */
+	writel(15, &regs->ercsr);		/* RX low watermark  */
+	writel(1, &regs->ertr);			/* Interrupt immediately */
+	__ioc3_set_mac_address(dev, regs);
+	writel(ip->ehar_h, &regs->ehar_h);
+	writel(ip->ehar_l, &regs->ehar_l);
+	writel(42, &regs->ersr);		/* XXX Should be random */
 
 	ioc3_init_rings(dev);
 
-	ip->emcr |= ((RX_OFFSET / 2) << EMCR_RXOFF_SHIFT) | EMCR_TXDMAEN |
-	             EMCR_TXEN | EMCR_RXDMAEN | EMCR_RXEN | EMCR_PADEN;
-	ioc3_w_emcr(ip->emcr);
-	ioc3_w_eier(EISR_RXTIMERINT | EISR_RXOFLO | EISR_RXBUFOFLO |
-	            EISR_RXMEMERR | EISR_RXPARERR | EISR_TXBUFUFLO |
-	            EISR_TXEXPLICIT | EISR_TXMEMERR);
-	(void) ioc3_r_eier();
-}
-
-static inline void ioc3_stop(struct ioc3_private *ip)
-{
-	struct ioc3 *ioc3 = ip->regs;
-
-	ioc3_w_emcr(0);				/* Shutup */
-	ioc3_w_eier(0);				/* Disable interrupts */
-	(void) ioc3_r_eier();			/* Flush */
+	ip->emcr |= ((RX_OFFSET / 2) << EMCR_RXOFF_SHIFT) |
+			EMCR_TXDMAEN | EMCR_TXEN |
+			EMCR_RXDMAEN | EMCR_RXEN |
+			EMCR_PADEN;
+	writel(ip->emcr, &regs->emcr);
+	readl(&regs->emcr);
+	writel(EISR_RXTIMERINT | EISR_RXTHRESHINT |
+		EISR_RXOFLO | EISR_RXBUFOFLO |
+		EISR_RXMEMERR | EISR_RXPARERR |
+		EISR_TXEXDEF |
+		EISR_TXBUFUFLO |
+		EISR_TXMEMERR, &regs->eier);
+	readl(&regs->eier);
 }
 
 static int ioc3_open(struct net_device *dev)
 {
 	struct ioc3_private *ip = netdev_priv(dev);
 
-	if (request_irq(dev->irq, ioc3_interrupt, IRQF_SHARED, ioc3_str, dev)) {
-		printk(KERN_ERR "%s: Can't get irq %d\n", dev->name, dev->irq);
-
-		return -EAGAIN;
-	}
-
 	ip->ehar_h = 0;
 	ip->ehar_l = 0;
 	ioc3_init(dev);
+	ioc3_mii_init(ip);
 	ioc3_mii_start(ip);
-
 	netif_start_queue(dev);
+
 	return 0;
 }
 
@@ -1061,444 +605,152 @@ static int ioc3_close(struct net_device *dev)
 	netif_stop_queue(dev);
 
 	ioc3_stop(ip);
-	free_irq(dev->irq, dev);
+	ioc3_free_rings(ip);
 
 	ioc3_free_rings(ip);
 	return 0;
 }
 
-/*
- * MENET cards have four IOC3 chips, which are attached to two sets of
- * PCI slot resources each: the primary connections are on slots
- * 0..3 and the secondaries are on 4..7
- *
- * All four ethernets are brought out to connectors; six serial ports
- * (a pair from each of the first three IOC3s) are brought out to
- * MiniDINs; all other subdevices are left swinging in the wind, leave
- * them disabled.
- */
-
-static int ioc3_adjacent_is_ioc3(struct pci_dev *pdev, int slot)
+static netdev_tx_t ioc3_start_xmit(struct sk_buff *skb, struct net_device *dev)
 {
-	struct pci_dev *dev = pci_get_slot(pdev->bus, PCI_DEVFN(slot, 0));
-	int ret = 0;
-
-	if (dev) {
-		if (dev->vendor == PCI_VENDOR_ID_SGI &&
-			dev->device == PCI_DEVICE_ID_SGI_IOC3)
-			ret = 1;
-		pci_dev_put(dev);
-	}
-
-	return ret;
-}
+	struct ioc3_private *ip = netdev_priv(dev);
+	struct ioc3_etxd *desc;
+	unsigned long data;
+	u32 len, w0 = 0;
+	int produce;
 
-static int ioc3_is_menet(struct pci_dev *pdev)
-{
-	return pdev->bus->parent == NULL &&
-	       ioc3_adjacent_is_ioc3(pdev, 0) &&
-	       ioc3_adjacent_is_ioc3(pdev, 1) &&
-	       ioc3_adjacent_is_ioc3(pdev, 2);
-}
+	/* IOC3 has a fairly simple minded checksumming hardware which simply
+	 * adds up the 1's complement checksum for the entire packet and
+	 * inserts it at an offset which can be specified in the descriptor
+	 * into the transmit packet.  This means we have to compensate for the
+	 * MAC header which should not be summed and the TCP/UDP pseudo headers
+	 * manually.
+	 */
+	if (skb->ip_summed == CHECKSUM_PARTIAL) {
+		struct iphdr *ih = ip_hdr(skb);
+		u32 proto = ntohs(ih->protocol);
+		u32 csoff, csum, ehsum;
+		u16 *eh;
 
-#ifdef CONFIG_SERIAL_8250
-/*
- * Note about serial ports and consoles:
- * For console output, everyone uses the IOC3 UARTA (offset 0x178)
- * connected to the master node (look in ip27_setup_console() and
- * ip27prom_console_write()).
- *
- * For serial (/dev/ttyS0 etc), we can not have hardcoded serial port
- * addresses on a partitioned machine. Since we currently use the ioc3
- * serial ports, we use dynamic serial port discovery that the serial.c
- * driver uses for pci/pnp ports (there is an entry for the SGI ioc3
- * boards in pci_boards[]). Unfortunately, UARTA's pio address is greater
- * than UARTB's, although UARTA on o200s has traditionally been known as
- * port 0. So, we just use one serial port from each ioc3 (since the
- * serial driver adds addresses to get to higher ports).
- *
- * The first one to do a register_console becomes the preferred console
- * (if there is no kernel command line console= directive). /dev/console
- * (ie 5, 1) is then "aliased" into the device number returned by the
- * "device" routine referred to in this console structure
- * (ip27prom_console_dev).
- *
- * Also look in ip27-pci.c:pci_fixup_ioc3() for some comments on working
- * around ioc3 oddities in this respect.
- *
- * The IOC3 serials use a 22MHz clock rate with an additional divider which
- * can be programmed in the SCR register if the DLAB bit is set.
- *
- * Register to interrupt zero because we share the interrupt with
- * the serial driver which we don't properly support yet.
- *
- * Can't use UPF_IOREMAP as the whole of IOC3 resources have already been
- * registered.
- */
-static void ioc3_8250_register(struct ioc3_uartregs __iomem *uart)
-{
-#define COSMISC_CONSTANT 6
-
-	struct uart_8250_port port = {
-	        .port = {
-			.irq		= 0,
-			.flags		= UPF_SKIP_TEST | UPF_BOOT_AUTOCONF,
-			.iotype		= UPIO_MEM,
-			.regshift	= 0,
-			.uartclk	= (22000000 << 1) / COSMISC_CONSTANT,
-
-			.membase	= (unsigned char __iomem *) uart,
-			.mapbase	= (unsigned long) uart,
-                }
-	};
-	unsigned char lcr;
-
-	lcr = uart->iu_lcr;
-	uart->iu_lcr = lcr | UART_LCR_DLAB;
-	uart->iu_scr = COSMISC_CONSTANT,
-	uart->iu_lcr = lcr;
-	uart->iu_lcr;
-	serial8250_register_8250_port(&port);
-}
+		/* The MAC header.  skb->mac seems the logical approach
+		 * to find the MAC header.  Except if it's a NULL pointer...
+		 */
+		eh = (u16 *)skb->data;
 
-static void ioc3_serial_probe(struct pci_dev *pdev, struct ioc3 *ioc3)
-{
-	/*
-	 * We need to recognice and treat the fourth MENET serial as it
-	 * does not have an SuperIO chip attached to it, therefore attempting
-	 * to access it will result in bus errors.  We call something an
-	 * MENET if PCI slot 0, 1, 2 and 3 of a master PCI bus all have an IOC3
-	 * in it.  This is paranoid but we want to avoid blowing up on a
-	 * showhorn PCI box that happens to have 4 IOC3 cards in it so it's
-	 * not paranoid enough ...
-	 */
-	if (ioc3_is_menet(pdev) && PCI_SLOT(pdev->devfn) == 3)
-		return;
+		/* Sum up dest addr, src addr and protocol  */
+		ehsum = eh[0] + eh[1] + eh[2] + eh[3] + eh[4] + eh[5] + eh[6];
 
-	/*
-	 * Switch IOC3 to PIO mode.  It probably already was but let's be
-	 * paranoid
-	 */
-	ioc3->gpcr_s = GPCR_UARTA_MODESEL | GPCR_UARTB_MODESEL;
-	ioc3->gpcr_s;
-	ioc3->gppr_6 = 0;
-	ioc3->gppr_6;
-	ioc3->gppr_7 = 0;
-	ioc3->gppr_7;
-	ioc3->sscr_a = ioc3->sscr_a & ~SSCR_DMA_EN;
-	ioc3->sscr_a;
-	ioc3->sscr_b = ioc3->sscr_b & ~SSCR_DMA_EN;
-	ioc3->sscr_b;
-	/* Disable all SA/B interrupts except for SA/B_INT in SIO_IEC. */
-	ioc3->sio_iec &= ~ (SIO_IR_SA_TX_MT | SIO_IR_SA_RX_FULL |
-			    SIO_IR_SA_RX_HIGH | SIO_IR_SA_RX_TIMER |
-			    SIO_IR_SA_DELTA_DCD | SIO_IR_SA_DELTA_CTS |
-			    SIO_IR_SA_TX_EXPLICIT | SIO_IR_SA_MEMERR);
-	ioc3->sio_iec |= SIO_IR_SA_INT;
-	ioc3->sscr_a = 0;
-	ioc3->sio_iec &= ~ (SIO_IR_SB_TX_MT | SIO_IR_SB_RX_FULL |
-			    SIO_IR_SB_RX_HIGH | SIO_IR_SB_RX_TIMER |
-			    SIO_IR_SB_DELTA_DCD | SIO_IR_SB_DELTA_CTS |
-			    SIO_IR_SB_TX_EXPLICIT | SIO_IR_SB_MEMERR);
-	ioc3->sio_iec |= SIO_IR_SB_INT;
-	ioc3->sscr_b = 0;
-
-	ioc3_8250_register(&ioc3->sregs.uarta);
-	ioc3_8250_register(&ioc3->sregs.uartb);
-}
-#endif
+		/* Skip IP header; it's sum is always zero and was already
+		 * filled in by ip_output.c
+		 */
+		csum = csum_tcpudp_nofold(ih->saddr, ih->daddr,
+					  ih->tot_len - (ih->ihl << 2),
+					  proto, csum_fold(ehsum));
 
-static const struct net_device_ops ioc3_netdev_ops = {
-	.ndo_open		= ioc3_open,
-	.ndo_stop		= ioc3_close,
-	.ndo_start_xmit		= ioc3_start_xmit,
-	.ndo_tx_timeout		= ioc3_timeout,
-	.ndo_get_stats		= ioc3_get_stats,
-	.ndo_set_rx_mode	= ioc3_set_multicast_list,
-	.ndo_do_ioctl		= ioc3_ioctl,
-	.ndo_validate_addr	= eth_validate_addr,
-	.ndo_set_mac_address	= ioc3_set_mac_address,
-};
+		csum = (csum & 0xffff) + (csum >> 16); /* Fold again */
+		csum = (csum & 0xffff) + (csum >> 16);
 
-static int ioc3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
-{
-	unsigned int sw_physid1, sw_physid2;
-	struct net_device *dev = NULL;
-	struct ioc3_private *ip;
-	struct ioc3 *ioc3;
-	unsigned long ioc3_base, ioc3_size;
-	u32 vendor, model, rev;
-	int err, pci_using_dac;
-
-	/* Configure DMA attributes. */
-	err = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
-	if (!err) {
-		pci_using_dac = 1;
-		err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
-		if (err < 0) {
-			printk(KERN_ERR "%s: Unable to obtain 64 bit DMA "
-			       "for consistent allocations\n", pci_name(pdev));
-			goto out;
+		csoff = ETH_HLEN + (ih->ihl << 2);
+		if (proto == IPPROTO_UDP) {
+			csoff += offsetof(struct udphdr, check);
+			udp_hdr(skb)->check = csum;
 		}
-	} else {
-		err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
-		if (err) {
-			printk(KERN_ERR "%s: No usable DMA configuration, "
-			       "aborting.\n", pci_name(pdev));
-			goto out;
+		if (proto == IPPROTO_TCP) {
+			csoff += offsetof(struct tcphdr, check);
+			tcp_hdr(skb)->check = csum;
 		}
-		pci_using_dac = 0;
+
+		w0 = ETXD_DOCHECKSUM | (csoff << ETXD_CHKOFF_SHIFT);
 	}
 
-	if (pci_enable_device(pdev))
-		return -ENODEV;
+	spin_lock_irq(&ip->ioc3_lock);
 
-	dev = alloc_etherdev(sizeof(struct ioc3_private));
-	if (!dev) {
-		err = -ENOMEM;
-		goto out_disable;
+	if (ip->txbfree <= 0) {
+		netif_stop_queue(dev);
+		spin_unlock_irq(&ip->ioc3_lock);
+		printk(KERN_ERR "%s: BUG! Tx Ring full when queue awake!\n",
+		       dev->name);
+		return NETDEV_TX_BUSY;
 	}
 
-	if (pci_using_dac)
-		dev->features |= NETIF_F_HIGHDMA;
+	data = (unsigned long)skb->data;
+	len = skb->len;
 
-	err = pci_request_regions(pdev, "ioc3");
-	if (err)
-		goto out_free;
+	produce = ip->tx_pi;
+	desc = &ip->txr[produce];
 
-	SET_NETDEV_DEV(dev, &pdev->dev);
+	if (len <= 104) {
+		/* Short packet, let's copy it directly into the ring.  */
+		skb_copy_from_linear_data(skb, desc->data, skb->len);
+		if (len < ETH_ZLEN) {
+			/* Very short packet, pad with zeros at the end. */
+			memset(desc->data + len, 0, ETH_ZLEN - len);
+			len = ETH_ZLEN;
+		}
+		desc->cmd = cpu_to_be32(len | ETXD_INTWHENDONE | ETXD_D0V | w0);
+		desc->bufcnt = cpu_to_be32(len);
+	} else if ((data ^ (data + len - 1)) & 0x4000) {
+		unsigned long b2 = (data | 0x3fffUL) + 1UL;
+		unsigned long s1 = b2 - data;
+		unsigned long s2 = data + len - b2;
 
-	ip = netdev_priv(dev);
-	ip->dev = dev;
-
-	dev->irq = pdev->irq;
-
-	ioc3_base = pci_resource_start(pdev, 0);
-	ioc3_size = pci_resource_len(pdev, 0);
-	ioc3 = (struct ioc3 *) ioremap(ioc3_base, ioc3_size);
-	if (!ioc3) {
-		printk(KERN_CRIT "ioc3eth(%s): ioremap failed, goodbye.\n",
-		       pci_name(pdev));
-		err = -ENOMEM;
-		goto out_res;
+		desc->cmd    = cpu_to_be32(len | ETXD_INTWHENDONE |
+					   ETXD_B1V | ETXD_B2V | w0);
+		desc->bufcnt = cpu_to_be32((s1 << ETXD_B1CNT_SHIFT) |
+					   (s2 << ETXD_B2CNT_SHIFT));
+		desc->p1     = cpu_to_be64(ioc3_map(skb->data,
+						    PCI64_ATTR_PREF));
+		desc->p2     = cpu_to_be64(ioc3_map((void *)b2,
+						    PCI64_ATTR_PREF));
+	} else {
+		/* Normal sized packet that doesn't cross a page boundary. */
+		desc->cmd = cpu_to_be32(len | ETXD_INTWHENDONE | ETXD_B1V | w0);
+		desc->bufcnt = cpu_to_be32(len << ETXD_B1CNT_SHIFT);
+		desc->p1     = cpu_to_be64(ioc3_map(skb->data,
+						    PCI64_ATTR_PREF));
 	}
-	ip->regs = ioc3;
 
-#ifdef CONFIG_SERIAL_8250
-	ioc3_serial_probe(pdev, ioc3);
-#endif
+	mb(); /* make sure all descriptor changes are visible */
 
-	spin_lock_init(&ip->ioc3_lock);
-	timer_setup(&ip->ioc3_timer, ioc3_timer, 0);
+	ip->tx_skbs[produce] = skb;			/* Remember skb */
+	produce = (produce + 1) & TX_MASK;
+	ip->tx_pi = produce;
+	writel(produce << 7, &ip->regs->etpir);	/* Fire! */
 
-	ioc3_stop(ip);
-	ioc3_init(dev);
+	ip->txbfree--;
+	if (ip->txbfree == 0)
+		netif_stop_queue(dev);
 
-	ip->pdev = pdev;
-
-	ip->mii.phy_id_mask = 0x1f;
-	ip->mii.reg_num_mask = 0x1f;
-	ip->mii.dev = dev;
-	ip->mii.mdio_read = ioc3_mdio_read;
-	ip->mii.mdio_write = ioc3_mdio_write;
-
-	ioc3_mii_init(ip);
-
-	if (ip->mii.phy_id == -1) {
-		printk(KERN_CRIT "ioc3-eth(%s): Didn't find a PHY, goodbye.\n",
-		       pci_name(pdev));
-		err = -ENODEV;
-		goto out_stop;
-	}
-
-	ioc3_mii_start(ip);
-	ioc3_ssram_disc(ip);
-	ioc3_get_eaddr(ip);
-
-	/* The IOC3-specific entries in the device structure. */
-	dev->watchdog_timeo	= 5 * HZ;
-	dev->netdev_ops		= &ioc3_netdev_ops;
-	dev->ethtool_ops	= &ioc3_ethtool_ops;
-	dev->hw_features	= NETIF_F_IP_CSUM | NETIF_F_RXCSUM;
-	dev->features		= NETIF_F_IP_CSUM;
-
-	sw_physid1 = ioc3_mdio_read(dev, ip->mii.phy_id, MII_PHYSID1);
-	sw_physid2 = ioc3_mdio_read(dev, ip->mii.phy_id, MII_PHYSID2);
-
-	err = register_netdev(dev);
-	if (err)
-		goto out_stop;
-
-	mii_check_media(&ip->mii, 1, 1);
-	ioc3_setup_duplex(ip);
-
-	vendor = (sw_physid1 << 12) | (sw_physid2 >> 4);
-	model  = (sw_physid2 >> 4) & 0x3f;
-	rev    = sw_physid2 & 0xf;
-	printk(KERN_INFO "%s: Using PHY %d, vendor 0x%x, model %d, "
-	       "rev %d.\n", dev->name, ip->mii.phy_id, vendor, model, rev);
-	printk(KERN_INFO "%s: IOC3 SSRAM has %d kbyte.\n", dev->name,
-	       ip->emcr & EMCR_BUFSIZ ? 128 : 64);
-
-	return 0;
+	spin_unlock_irq(&ip->ioc3_lock);
 
-out_stop:
-	ioc3_stop(ip);
-	del_timer_sync(&ip->ioc3_timer);
-	ioc3_free_rings(ip);
-out_res:
-	pci_release_regions(pdev);
-out_free:
-	free_netdev(dev);
-out_disable:
-	/*
-	 * We should call pci_disable_device(pdev); here if the IOC3 wasn't
-	 * such a weird device ...
-	 */
-out:
-	return err;
+	return NETDEV_TX_OK;
 }
 
-static void ioc3_remove_one(struct pci_dev *pdev)
+static int ioc3_set_mac_address(struct net_device *dev, void *addr)
 {
-	struct net_device *dev = pci_get_drvdata(pdev);
 	struct ioc3_private *ip = netdev_priv(dev);
-	struct ioc3 *ioc3 = ip->regs;
-
-	unregister_netdev(dev);
-	del_timer_sync(&ip->ioc3_timer);
+	struct ioc3_ethregs *regs = ip->regs;
+	struct sockaddr *sa = addr;
 
-	iounmap(ioc3);
-	pci_release_regions(pdev);
-	free_netdev(dev);
-	/*
-	 * We should call pci_disable_device(pdev); here if the IOC3 wasn't
-	 * such a weird device ...
-	 */
-}
+	memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
 
-static const struct pci_device_id ioc3_pci_tbl[] = {
-	{ PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_IOC3, PCI_ANY_ID, PCI_ANY_ID },
-	{ 0 }
-};
-MODULE_DEVICE_TABLE(pci, ioc3_pci_tbl);
+	spin_lock_irq(&ip->ioc3_lock);
+	__ioc3_set_mac_address(dev, regs);
+	spin_unlock_irq(&ip->ioc3_lock);
 
-static struct pci_driver ioc3_driver = {
-	.name		= "ioc3-eth",
-	.id_table	= ioc3_pci_tbl,
-	.probe		= ioc3_probe,
-	.remove		= ioc3_remove_one,
-};
+	return 0;
+}
 
-static netdev_tx_t ioc3_start_xmit(struct sk_buff *skb, struct net_device *dev)
+static int ioc3_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
 {
-	unsigned long data;
 	struct ioc3_private *ip = netdev_priv(dev);
-	struct ioc3 *ioc3 = ip->regs;
-	unsigned int len;
-	struct ioc3_etxd *desc;
-	uint32_t w0 = 0;
-	int produce;
-
-	/*
-	 * IOC3 has a fairly simple minded checksumming hardware which simply
-	 * adds up the 1's complement checksum for the entire packet and
-	 * inserts it at an offset which can be specified in the descriptor
-	 * into the transmit packet.  This means we have to compensate for the
-	 * MAC header which should not be summed and the TCP/UDP pseudo headers
-	 * manually.
-	 */
-	if (skb->ip_summed == CHECKSUM_PARTIAL) {
-		const struct iphdr *ih = ip_hdr(skb);
-		const int proto = ntohs(ih->protocol);
-		unsigned int csoff;
-		uint32_t csum, ehsum;
-		uint16_t *eh;
-
-		/* The MAC header.  skb->mac seem the logic approach
-		   to find the MAC header - except it's a NULL pointer ...  */
-		eh = (uint16_t *) skb->data;
-
-		/* Sum up dest addr, src addr and protocol  */
-		ehsum = eh[0] + eh[1] + eh[2] + eh[3] + eh[4] + eh[5] + eh[6];
-
-		/* Fold ehsum.  can't use csum_fold which negates also ...  */
-		ehsum = (ehsum & 0xffff) + (ehsum >> 16);
-		ehsum = (ehsum & 0xffff) + (ehsum >> 16);
-
-		/* Skip IP header; it's sum is always zero and was
-		   already filled in by ip_output.c */
-		csum = csum_tcpudp_nofold(ih->saddr, ih->daddr,
-		                          ih->tot_len - (ih->ihl << 2),
-		                          proto, 0xffff ^ ehsum);
-
-		csum = (csum & 0xffff) + (csum >> 16);	/* Fold again */
-		csum = (csum & 0xffff) + (csum >> 16);
-
-		csoff = ETH_HLEN + (ih->ihl << 2);
-		if (proto == IPPROTO_UDP) {
-			csoff += offsetof(struct udphdr, check);
-			udp_hdr(skb)->check = csum;
-		}
-		if (proto == IPPROTO_TCP) {
-			csoff += offsetof(struct tcphdr, check);
-			tcp_hdr(skb)->check = csum;
-		}
-
-		w0 = ETXD_DOCHECKSUM | (csoff << ETXD_CHKOFF_SHIFT);
-	}
+	int rc;
 
 	spin_lock_irq(&ip->ioc3_lock);
-
-	data = (unsigned long) skb->data;
-	len = skb->len;
-
-	produce = ip->tx_pi;
-	desc = &ip->txr[produce];
-
-	if (len <= 104) {
-		/* Short packet, let's copy it directly into the ring.  */
-		skb_copy_from_linear_data(skb, desc->data, skb->len);
-		if (len < ETH_ZLEN) {
-			/* Very short packet, pad with zeros at the end. */
-			memset(desc->data + len, 0, ETH_ZLEN - len);
-			len = ETH_ZLEN;
-		}
-		desc->cmd = cpu_to_be32(len | ETXD_INTWHENDONE | ETXD_D0V | w0);
-		desc->bufcnt = cpu_to_be32(len);
-	} else if ((data ^ (data + len - 1)) & 0x4000) {
-		unsigned long b2 = (data | 0x3fffUL) + 1UL;
-		unsigned long s1 = b2 - data;
-		unsigned long s2 = data + len - b2;
-
-		desc->cmd    = cpu_to_be32(len | ETXD_INTWHENDONE |
-		                           ETXD_B1V | ETXD_B2V | w0);
-		desc->bufcnt = cpu_to_be32((s1 << ETXD_B1CNT_SHIFT) |
-		                           (s2 << ETXD_B2CNT_SHIFT));
-		desc->p1     = cpu_to_be64(ioc3_map(skb->data, 1));
-		desc->p2     = cpu_to_be64(ioc3_map((void *) b2, 1));
-	} else {
-		/* Normal sized packet that doesn't cross a page boundary. */
-		desc->cmd = cpu_to_be32(len | ETXD_INTWHENDONE | ETXD_B1V | w0);
-		desc->bufcnt = cpu_to_be32(len << ETXD_B1CNT_SHIFT);
-		desc->p1     = cpu_to_be64(ioc3_map(skb->data, 1));
-	}
-
-	BARRIER();
-
-	ip->tx_skbs[produce] = skb;			/* Remember skb */
-	produce = (produce + 1) & 127;
-	ip->tx_pi = produce;
-	ioc3_w_etpir(produce << 7);			/* Fire ... */
-
-	ip->txqlen++;
-
-	if (ip->txqlen >= 127)
-		netif_stop_queue(dev);
-
+	rc = generic_mii_ioctl(&ip->mii, if_mii(rq), cmd, NULL);
 	spin_unlock_irq(&ip->ioc3_lock);
 
-	return NETDEV_TX_OK;
+	return rc;
 }
 
 static void ioc3_timeout(struct net_device *dev)
@@ -1507,49 +759,42 @@ static void ioc3_timeout(struct net_device *dev)
 
 	printk(KERN_ERR "%s: transmit timed out, resetting\n", dev->name);
 
-	spin_lock_irq(&ip->ioc3_lock);
-
 	ioc3_stop(ip);
 	ioc3_init(dev);
 	ioc3_mii_init(ip);
 	ioc3_mii_start(ip);
 
-	spin_unlock_irq(&ip->ioc3_lock);
-
 	netif_wake_queue(dev);
 }
 
-/*
- * Given a multicast ethernet address, this routine calculates the
- * address's bit index in the logical address filter mask
- */
-
-static inline unsigned int ioc3_hash(const unsigned char *addr)
+static struct net_device_stats *ioc3_get_stats(struct net_device *dev)
 {
-	unsigned int temp = 0;
-	u32 crc;
-	int bits;
-
-	crc = ether_crc_le(ETH_ALEN, addr);
-
-	crc &= 0x3f;    /* bit reverse lowest 6 bits for hash index */
-	for (bits = 6; --bits >= 0; ) {
-		temp <<= 1;
-		temp |= (crc & 0x1);
-		crc >>= 1;
-	}
+	struct ioc3_private *ip = netdev_priv(dev);
+	struct ioc3_ethregs *regs = ip->regs;
 
-	return temp;
+	dev->stats.collisions += (readl(&regs->etcdc) & ETCDC_COLLCNT_MASK);
+	return &dev->stats;
 }
 
-static void ioc3_get_drvinfo (struct net_device *dev,
-	struct ethtool_drvinfo *info)
-{
-	struct ioc3_private *ip = netdev_priv(dev);
+static const struct net_device_ops ioc3_netdev_ops = {
+	.ndo_open		= ioc3_open,
+	.ndo_stop		= ioc3_close,
+	.ndo_start_xmit		= ioc3_start_xmit,
+	.ndo_tx_timeout		= ioc3_timeout,
+	.ndo_get_stats		= ioc3_get_stats,
+	.ndo_set_rx_mode	= ioc3_set_multicast_list,
+	.ndo_do_ioctl		= ioc3_ioctl,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_set_mac_address	= ioc3_set_mac_address,
+};
 
+static void ioc3_get_drvinfo(struct net_device *dev,
+			     struct ethtool_drvinfo *info)
+{
 	strlcpy(info->driver, IOC3_NAME, sizeof(info->driver));
 	strlcpy(info->version, IOC3_VERSION, sizeof(info->version));
-	strlcpy(info->bus_info, pci_name(ip->pdev), sizeof(info->bus_info));
+	strlcpy(info->bus_info, pci_name(to_pci_dev(dev->dev.parent)),
+		sizeof(info->bus_info));
 }
 
 static int ioc3_get_link_ksettings(struct net_device *dev,
@@ -1609,58 +854,298 @@ static const struct ethtool_ops ioc3_ethtool_ops = {
 	.set_link_ksettings	= ioc3_set_link_ksettings,
 };
 
-static int ioc3_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+/* Deal with fatal IOC3 errors.  This condition might be caused by a hard or
+ * software problems, so we should try to recover
+ * more gracefully if this ever happens.  In theory we might be flooded
+ * with such error interrupts if something really goes wrong, so we might
+ * also consider to take the interface down.
+ */
+static noinline void ioc3_error(struct ioc3_private *ip,
+				struct net_device *dev, u32 eisr)
 {
-	struct ioc3_private *ip = netdev_priv(dev);
-	int rc;
+	u8 *iface = dev->name;
 
-	spin_lock_irq(&ip->ioc3_lock);
-	rc = generic_mii_ioctl(&ip->mii, if_mii(rq), cmd, NULL);
-	spin_unlock_irq(&ip->ioc3_lock);
+	if (eisr & EISR_RXOFLO)
+		printk(KERN_ERR "%s: RX overflow.\n", iface);
+	if (eisr & EISR_RXBUFOFLO)
+		printk(KERN_ERR "%s: RX buffer overflow.\n", iface);
+	if (eisr & EISR_RXMEMERR)
+		printk(KERN_ERR "%s: RX PCI error.\n", iface);
+	if (eisr & EISR_RXPARERR)
+		printk(KERN_ERR "%s: RX SSRAM parity error.\n", iface);
+	if (eisr & EISR_TXBUFUFLO)
+		printk(KERN_ERR "%s: TX buffer underflow.\n", iface);
+	if (eisr & EISR_TXMEMERR)
+		printk(KERN_ERR "%s: TX PCI error.\n", iface);
 
-	return rc;
+	ioc3_stop(ip);
+
+	/* This can trigger a BUG(): sleeping function called */
+	ioc3_init(dev);
+	ioc3_mii_init(ip);
+	ioc3_mii_start(ip);
+
+	netif_wake_queue(dev);
 }
 
-static void ioc3_set_multicast_list(struct net_device *dev)
+static noinline void ioc3_rx(struct ioc3_private *ip, struct net_device *dev)
 {
-	struct netdev_hw_addr *ha;
-	struct ioc3_private *ip = netdev_priv(dev);
-	struct ioc3 *ioc3 = ip->regs;
-	u64 ehar = 0;
+	struct sk_buff *skb, *new_skb;
+	struct ioc3_erxbuf *rxb;
+	int rx_entry, n_entry, len;
+	u32 w0, err;
 
-	netif_stop_queue(dev);				/* Lock out others. */
+	rx_entry = ip->rx_ci;  /* RX consume index */
+	n_entry = ip->rx_pi;
 
-	if (dev->flags & IFF_PROMISC) {			/* Set promiscuous.  */
-		ip->emcr |= EMCR_PROMISC;
-		ioc3_w_emcr(ip->emcr);
-		(void) ioc3_r_emcr();
-	} else {
-		ip->emcr &= ~EMCR_PROMISC;
-		ioc3_w_emcr(ip->emcr);			/* Clear promiscuous. */
-		(void) ioc3_r_emcr();
+	skb = ip->rx_skbs[rx_entry];
+	rxb = (struct ioc3_erxbuf *)(skb->data - RX_OFFSET);
+	w0 = be32_to_cpu(rxb->w0);
 
-		if ((dev->flags & IFF_ALLMULTI) ||
-		    (netdev_mc_count(dev) > 64)) {
-			/* Too many for hashing to make sense or we want all
-			   multicast packets anyway,  so skip computing all the
-			   hashes and just accept all packets.  */
-			ip->ehar_h = 0xffffffff;
-			ip->ehar_l = 0xffffffff;
-		} else {
-			netdev_for_each_mc_addr(ha, dev) {
-				ehar |= (1UL << ioc3_hash(ha->addr));
+	while (w0 & ERXBUF_V) {
+		err = be32_to_cpu(rxb->err);  /* It's valid */
+		if (err & ERXBUF_GOODPKT) {
+			len = ((w0 >> ERXBUF_BYTECNT_SHIFT) & 0x7ff) - 4;
+			skb_trim(skb, len);
+			skb->protocol = eth_type_trans(skb, dev);
+
+			new_skb = ioc3_alloc_skb(dev);
+			if (!new_skb) {
+				/* Ouch, drop packet and just recycle packet
+				 * to keep the ring filled.
+				 */
+				dev->stats.rx_dropped++;
+				new_skb = skb;
+				goto next;
 			}
-			ip->ehar_h = ehar >> 32;
-			ip->ehar_l = ehar & 0xffffffff;
+
+			if (likely(ip->flags & IOC3_FLAG_RX_CHECKSUMS))
+				ioc3_tcpudp_checksum(skb,
+						     (w0 & ERXBUF_IPCKSUM_MASK),
+						      len);
+
+			netif_rx(skb);
+
+			ip->rx_skbs[rx_entry] = NULL;  /* Poison */
+
+			/* Because we reserve afterwards. */
+			skb_put(new_skb, (RX_BUF_SIZE + RX_OFFSET));
+			rxb = (struct ioc3_erxbuf *)new_skb->data;
+			skb_reserve(new_skb, RX_OFFSET);
+
+			dev->stats.rx_packets++;  /* Statistics */
+			dev->stats.rx_bytes += len;
+		} else {
+			/* The frame is invalid and the skb never reached the
+			 * network layer so we can just recycle it.
+			 */
+			new_skb = skb;
+			dev->stats.rx_errors++;
 		}
-		ioc3_w_ehar_h(ip->ehar_h);
-		ioc3_w_ehar_l(ip->ehar_l);
+
+		/* Statistics */
+		if (err & ERXBUF_CRCERR)
+			dev->stats.rx_crc_errors++;
+		if (err & ERXBUF_FRAMERR)
+			dev->stats.rx_frame_errors++;
+next:
+		ip->rx_skbs[rx_entry] = new_skb;
+		ip->rxr[rx_entry] = cpu_to_be64(ioc3_map(rxb, PCI64_ATTR_BAR));
+		rxb->w0 = 0;				/* Clear valid flag */
+
+		/* Now go on to the next ring entry.  */
+		n_entry++;
+		n_entry &= RX_MASK;
+		rx_entry++;
+		rx_entry &= RX_MASK;
+
+		skb = ip->rx_skbs[rx_entry];
+		rxb = (struct ioc3_erxbuf *)(skb->data - RX_OFFSET);
+		w0 = be32_to_cpu(rxb->w0);
+	}
+	ip->rx_ci = rx_entry;
+	ip->rx_pi = n_entry;
+	writel((n_entry << 3) | ERPIR_ARM, &ip->regs->erpir);
+}
+
+static noinline void ioc3_tx(struct ioc3_private *ip, struct net_device *dev)
+{
+	struct sk_buff *skb;
+	struct ioc3_ethregs *regs = ip->regs;
+	unsigned long packets = 0;
+	unsigned long bytes = 0;
+	int tx_entry, o_entry;
+	u32 etcir;
+
+	etcir = readl(&regs->etcir);
+	tx_entry = (etcir >> 7) & TX_MASK;
+	o_entry = ip->tx_ci;
+
+	while (o_entry != tx_entry) {
+		packets++;
+		skb = ip->tx_skbs[o_entry];
+		bytes += skb->len;
+		dev_consume_skb_irq(skb);
+		ip->tx_skbs[o_entry] = NULL;
+
+		etcir = readl(&regs->etcir);		/* More pkts sent?  */
+		tx_entry = (etcir >> 7) & TX_MASK;
+		o_entry = (o_entry + 1) & TX_MASK;	/* Next */
+	}
+	ip->tx_ci = o_entry;
+
+	dev->stats.tx_bytes   += bytes;
+	dev->stats.tx_packets += packets;
+	ip->txbfree += packets;
+
+	if (netif_queue_stopped(dev) && ip->txbfree > 0)
+		netif_wake_queue(dev);
+}
+
+/* The interrupt handler does all of the Rx thread work
+ * and cleans up after the Tx thread.
+ */
+static irqreturn_t ioc3eth_intr(int irq, void *dev_id)
+{
+	struct net_device *dev = dev_id;
+	struct ioc3_private *ip = netdev_priv(dev);
+	struct ioc3_ethregs *regs = ip->regs;
+	u32 eisr;
+
+	spin_lock(&ip->ioc3_lock);
+
+	eisr = readl(&regs->eisr);
+
+	writel(eisr, &regs->eisr);
+	(void)readl(&regs->eisr);	/* Flush */
+
+	if (eisr & (EISR_RXTIMERINT | EISR_RXTHRESHINT))
+		ioc3_rx(ip, dev);
+
+	if (eisr & (EISR_TXEMPTY | EISR_TXEXDEF | EISR_TXEXPLICIT))
+		ioc3_tx(ip, dev);
+
+	if (eisr & (EISR_RXOFLO | EISR_RXBUFOFLO |
+		    EISR_RXMEMERR | EISR_RXPARERR |
+		    EISR_TXBUFUFLO | EISR_TXMEMERR))
+		ioc3_error(ip, dev, eisr);
+
+	spin_unlock(&ip->ioc3_lock);
+
+	return eisr ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static int ioc3eth_probe(struct platform_device *pdev)
+{
+	struct ioc3eth_platform_data *data = dev_get_platdata(&pdev->dev);
+	struct net_device *dev;
+	struct ioc3_private *ip;
+	struct resource *r;
+	u32 sw_physid1, sw_physid2, vendor, model, rev;
+	int err;
+
+	dev = alloc_etherdev(sizeof(struct ioc3_private));
+	if (!dev)
+		return -ENOMEM;
+
+	/* The IOC3-specific entries in the device structure. */
+	dev->watchdog_timeo	= 5 * HZ;
+	dev->netdev_ops		= &ioc3_netdev_ops;
+	dev->ethtool_ops	= &ioc3_ethtool_ops;
+	dev->features		= NETIF_F_IP_CSUM | NETIF_F_HIGHDMA;
+
+	SET_NETDEV_DEV(dev, &pdev->dev);
+	ip = netdev_priv(dev);
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	ip->regs = ioremap(r->start, resource_size(r));
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	ip->ssram = ioremap(r->start, resource_size(r));
+
+	dev->irq = platform_get_irq(pdev, 0);
+	if (dev->irq < 0)
+		return dev->irq;
+
+	if (request_irq(dev->irq, ioc3eth_intr, IRQF_SHARED, "ioc3-eth", dev)) {
+		pr_err("%s: Can't get irq %d\n", dev->name, dev->irq);
+		return -ENODEV;
 	}
 
-	netif_wake_queue(dev);			/* Let us get going again. */
+	spin_lock_init(&ip->ioc3_lock);
+	timer_setup(&ip->ioc3_timer, ioc3_timer, 0);
+
+	ioc3_stop(ip);
+	ioc3_init(dev);
+
+	ip->mii.phy_id_mask = 0x1f;
+	ip->mii.reg_num_mask = 0x1f;
+	ip->mii.dev = dev;
+	ip->mii.mdio_read = ioc3_mdio_read;
+	ip->mii.mdio_write = ioc3_mdio_write;
+
+	ioc3_mii_init(ip);
+
+	if (ip->mii.phy_id == -1) {
+		printk(KERN_CRIT "ioc3-eth(%s): Didn't find a PHY, goodbye.\n",
+		       dev->name);
+		err = -ENODEV;
+		goto out_stop;
+	}
+
+	ioc3_mii_start(ip);
+	ioc3_ssram_disc(ip);
+	memcpy(dev->dev_addr, data->mac_addr, ETH_ALEN);
+
+	sw_physid1 = ioc3_mdio_read(dev, ip->mii.phy_id, MII_PHYSID1);
+	sw_physid2 = ioc3_mdio_read(dev, ip->mii.phy_id, MII_PHYSID2);
+
+	err = register_netdev(dev);
+	if (err)
+		goto out_stop;
+
+	mii_check_media(&ip->mii, 1, 1);
+	ioc3_setup_duplex(ip);
+
+	vendor = (sw_physid1 << 12) | (sw_physid2 >> 4);
+	model  = (sw_physid2 >> 4) & 0x3f;
+	rev    = sw_physid2 & 0xf;
+	printk(KERN_INFO "%s: Using PHY %d, vendor 0x%x, model %d, rev %d.\n",
+	       dev->name, ip->mii.phy_id, vendor, model, rev);
+	printk(KERN_INFO "%s: IOC3 SSRAM has %d kbyte.\n", dev->name,
+	       (ip->emcr & EMCR_BUFSIZ ? 128 : 64));
+
+	return 0;
+
+out_stop:
+	ioc3_stop(ip);
+	del_timer_sync(&ip->ioc3_timer);
+	ioc3_free_rings(ip);
+	free_netdev(dev);
+	return err;
+}
+
+static int ioc3eth_remove(struct platform_device *pdev)
+{
+	struct net_device *dev = platform_get_drvdata(pdev);
+	struct ioc3_private *ip = netdev_priv(dev);
+
+	unregister_netdev(dev);
+	del_timer_sync(&ip->ioc3_timer);
+	ioc3_free_rings(ip);
+	free_netdev(dev);
+
+	return 0;
 }
 
-module_pci_driver(ioc3_driver);
+static struct platform_driver ioc3eth_driver = {
+	.probe  = ioc3eth_probe,
+	.remove = ioc3eth_remove,
+	.driver = {
+		.name = "ioc3-eth",
+	}
+};
+
+module_platform_driver(ioc3eth_driver);
+
 MODULE_AUTHOR("Ralf Baechle <ralf@linux-mips.org>");
 MODULE_DESCRIPTION("SGI IOC3 Ethernet driver");
 MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/8250/8250_ioc3.c b/drivers/tty/serial/8250/8250_ioc3.c
new file mode 100644
index 000000000000..2be6ed2967e0
--- /dev/null
+++ b/drivers/tty/serial/8250/8250_ioc3.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SGI IOC3 8250 UART driver
+ *
+ * Copyright (C) 2019 Thomas Bogendoerfer <tbogendoerfer@suse.de>
+ *
+ * based on code Copyright (C) 2005 Stanislaw Skowronek <skylark@unaligned.org>
+ *               Copyright (C) 2014 Joshua Kinard <kumba@gentoo.org>
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+
+#include "8250.h"
+
+#define IOC3_UARTCLK (22000000 / 3)
+
+struct ioc3_8250_data {
+	int line;
+};
+
+static unsigned int ioc3_serial_in(struct uart_port *p, int offset)
+{
+	return readb(p->membase + offset);
+}
+
+static void ioc3_serial_out(struct uart_port *p, int offset, int value)
+{
+	writeb(value, p->membase + offset);
+}
+
+static int serial8250_ioc3_probe(struct platform_device *pdev)
+{
+	struct ioc3_8250_data *data;
+	struct uart_8250_port up;
+	struct resource *r;
+	void __iomem *membase;
+	int irq, line;
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r)
+		return -ENODEV;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	membase = devm_ioremap_nocache(&pdev->dev, r->start, resource_size(r));
+	if (!membase)
+		return -ENOMEM;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		irq = 0; /* no interrupt -> use polling */
+
+	/* Register serial ports with 8250.c */
+	memset(&up, 0, sizeof(struct uart_8250_port));
+	up.port.iotype = UPIO_MEM;
+	up.port.uartclk = IOC3_UARTCLK;
+	up.port.type = PORT_16550A;
+	up.port.irq = irq;
+	up.port.flags = (UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ);
+	up.port.dev = &pdev->dev;
+	up.port.membase = membase;
+	up.port.mapbase = r->start;
+	up.port.serial_in = ioc3_serial_in;
+	up.port.serial_out = ioc3_serial_out;
+	line = serial8250_register_8250_port(&up);
+	if (line < 0)
+		return line;
+
+	platform_set_drvdata(pdev, data);
+	return 0;
+}
+
+static int serial8250_ioc3_remove(struct platform_device *pdev)
+{
+	struct ioc3_8250_data *data = platform_get_drvdata(pdev);
+
+	serial8250_unregister_port(data->line);
+	return 0;
+}
+
+static struct platform_driver serial8250_ioc3_driver = {
+	.probe  = serial8250_ioc3_probe,
+	.remove = serial8250_ioc3_remove,
+	.driver = {
+		.name = "ioc3-serial8250",
+	}
+};
+
+module_platform_driver(serial8250_ioc3_driver);
+
+MODULE_AUTHOR("Thomas Bogendoerfer <tbogendoerfer@suse.de>");
+MODULE_DESCRIPTION("SGI IOC3 8250 UART driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig
index 15c2c5463835..9d24615aabfb 100644
--- a/drivers/tty/serial/8250/Kconfig
+++ b/drivers/tty/serial/8250/Kconfig
@@ -364,6 +364,17 @@ config SERIAL_8250_EM
 	  port hardware found on the Emma Mobile line of processors.
 	  If unsure, say N.
 
+config SERIAL_8250_IOC3
+	tristate "SGI IOC3 8250 UART support"
+	depends on SGI_MFD_IOC3 && SERIAL_8250
+	select SERIAL_8250_EXTENDED
+	select SERIAL_8250_SHARE_IRQ
+	help
+	  Enable this if you have a SGI Origin or Octane machine. This module
+	  provides basic serial support by directly driving the UART chip
+	  behind the IOC3 device on those systems.  Maximum baud speed is
+	  38400bps using this driver.
+
 config SERIAL_8250_RT288X
 	bool "Ralink RT288x/RT305x/RT3662/RT3883 serial port support"
 	depends on SERIAL_8250
diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile
index 18751bc63a84..79f74b4d57e5 100644
--- a/drivers/tty/serial/8250/Makefile
+++ b/drivers/tty/serial/8250/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_SERIAL_8250_FSL)		+= 8250_fsl.o
 obj-$(CONFIG_SERIAL_8250_MEN_MCB)	+= 8250_men_mcb.o
 obj-$(CONFIG_SERIAL_8250_DW)		+= 8250_dw.o
 obj-$(CONFIG_SERIAL_8250_EM)		+= 8250_em.o
+obj-$(CONFIG_SERIAL_8250_IOC3)		+= 8250_ioc3.o
 obj-$(CONFIG_SERIAL_8250_OMAP)		+= 8250_omap.o
 obj-$(CONFIG_SERIAL_8250_LPC18XX)	+= 8250_lpc18xx.o
 obj-$(CONFIG_SERIAL_8250_MT6577)	+= 8250_mtk.o
diff --git a/include/linux/platform_data/ioc3eth.h b/include/linux/platform_data/ioc3eth.h
new file mode 100644
index 000000000000..d8a688c7db48
--- /dev/null
+++ b/include/linux/platform_data/ioc3eth.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Ethernet driver for the SGI IOC3 chip.
+ */
+
+#ifndef PLATFORM_DATA_IOC3ETH_H
+#define PLATFORM_DATA_IOC3ETH_H
+
+#include <linux/if_ether.h>
+
+struct ioc3eth_platform_data {
+	u8	mac_addr[ETH_ALEN];
+};
+
+#endif /* PLATFORM_DATA_IOC3ETH_H */
-- 
2.13.7

^ permalink raw reply related

* [PATCH v2 1/4] MIPS: SGI-IP27: remove ioc3 ethernet init
From: Thomas Bogendoerfer @ 2019-04-09 15:46 UTC (permalink / raw)
  To: Ralf Baechle, Paul Burton, James Hogan, Dmitry Torokhov,
	Lee Jones, David S. Miller, Alessandro Zummo, Alexandre Belloni,
	Greg Kroah-Hartman, Jiri Slaby, linux-mips, linux-kernel,
	linux-input, netdev, linux-rtc, linux-serial
In-Reply-To: <20190409154610.6735-1-tbogendoerfer@suse.de>

Removed not needed disabling of ethernet interrupts in IP27 platform code.

Signed-off-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
---
 arch/mips/sgi-ip27/ip27-init.c | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/arch/mips/sgi-ip27/ip27-init.c b/arch/mips/sgi-ip27/ip27-init.c
index 066b33f50bcc..59d5375c9021 100644
--- a/arch/mips/sgi-ip27/ip27-init.c
+++ b/arch/mips/sgi-ip27/ip27-init.c
@@ -130,17 +130,6 @@ cnodeid_t get_compact_nodeid(void)
 	return NASID_TO_COMPACT_NODEID(get_nasid());
 }
 
-static inline void ioc3_eth_init(void)
-{
-	struct ioc3 *ioc3;
-	nasid_t nid;
-
-	nid = get_nasid();
-	ioc3 = (struct ioc3 *) KL_CONFIG_CH_CONS_INFO(nid)->memory_base;
-
-	ioc3->eier = 0;
-}
-
 extern void ip27_reboot_setup(void);
 
 void __init plat_mem_setup(void)
@@ -182,8 +171,6 @@ void __init plat_mem_setup(void)
 		panic("Kernel compiled for N mode.");
 #endif
 
-	ioc3_eth_init();
-
 	ioport_resource.start = 0;
 	ioport_resource.end = ~0UL;
 	set_io_port_base(IO_BASE);
-- 
2.13.7

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox