Subject: [PATCH v2 10/16] serial: support for 16550A serial ports on LP-8x4x

From: [email-masked] (Sergei Ianovich)
Date: Fri, 13 Dec 2013 06:27:19 +0400

The patch adds support for 3 additional LP-8x4x built-in serial
ports.

The device can also host up to 8 extension cards with 4 serial ports
on each card for a total of 35 ports. However, I don't have
the hardware to test extension cards, so they are not supported, yet.

Signed-off-by: Sergei Ianovich <[email-masked]>
CC: Heikki Krogerus <[email-masked]>
---
   v0..v2
   * register platform driver instead of platform device
   * use device tree
   * use devm helpers where possible

 .../devicetree/bindings/serial/lp8x4x-serial.txt   |  35 +++++
 arch/arm/boot/dts/pxa27x-lp8x4x.dts                |  27 ++++
 arch/arm/configs/lp8x4x_defconfig                  |   1 +
 drivers/tty/serial/8250/8250_lp8x4x.c              | 162 +++++++++++++++++++++
 drivers/tty/serial/8250/Kconfig                    |  12 ++
 drivers/tty/serial/8250/Makefile                   |   1 +
 6 files changed, 238 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serial/lp8x4x-serial.txt
 create mode 100644 drivers/tty/serial/8250/8250_lp8x4x.c

diff --git a/Documentation/devicetree/bindings/serial/lp8x4x-serial.txt b/Documentation/devicetree/bindings/serial/lp8x4x-serial.txt
new file mode 100644
index 0000000..eed4ffc
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/lp8x4x-serial.txt
@@ -0,0 +1,35 @@
+UART ports on ICP DAS LP-8x4x
+
+ICP DAS LP-8x4x contains three additional serial ports interfaced via
+Analog Devices ADM213EA chips in addition to 3 serial ports on PXA CPU.
+
+Required properties:
+- compatible : should be "icpdas,uart-lp8x4x"
+
+- reg : should provide 16 byte man IO memory region and 1 byte region for
+	termios
+
+- interrupts : should provide interrupt
+
+- interrupt-parent : should provide a link to interrupt controller either
+		     explicitly and implicitly from a parent node
+
+Examples (from pxa27x-lp8x4x.dts):
+
+		uart at 17009050 {
+			compatible = "icpdas,uart-lp8x4x";
+			reg = <0x17009050 0x10
+			       0x17009030 0x02>;
+			interrupt-parent = <&fpga>;
+			interrupts = <13>;
+			status = "okay";
+		};
+
+		uart at 17009060 {
+			compatible = "icpdas,uart-lp8x4x";
+			reg = <0x17009060 0x10
+			       0x17009032 0x02>;
+			interrupt-parent = <&fpga>;
+			interrupts = <14>;
+			status = "okay";
+		};
diff --git a/arch/arm/boot/dts/pxa27x-lp8x4x.dts b/arch/arm/boot/dts/pxa27x-lp8x4x.dts
index 2704760..4bcf400 100644
--- a/arch/arm/boot/dts/pxa27x-lp8x4x.dts
+++ b/arch/arm/boot/dts/pxa27x-lp8x4x.dts
@@ -129,5 +129,32 @@
 			interrupt-controller;
 			status = "okay";
 		};
+
+		uart at 17009050 {
+			compatible = "icpdas,uart-lp8x4x";
+			reg = <0x17009050 0x10
+			       0x17009030 0x02>;
+			interrupt-parent = <&fpga>;
+			interrupts = <13>;
+			status = "okay";
+		};
+
+		uart at 17009060 {
+			compatible = "icpdas,uart-lp8x4x";
+			reg = <0x17009060 0x10
+			       0x17009032 0x02>;
+			interrupt-parent = <&fpga>;
+			interrupts = <14>;
+			status = "okay";
+		};
+
+		uart at 17009070 {
+			compatible = "icpdas,uart-lp8x4x";
+			reg = <0x17009070 0x10
+			       0x17009034 0x02>;
+			interrupt-parent = <&fpga>;
+			interrupts = <15>;
+			status = "okay";
+		};
 	};
 };
diff --git a/arch/arm/configs/lp8x4x_defconfig b/arch/arm/configs/lp8x4x_defconfig
index 8fa871e..ea1a4b8 100644
--- a/arch/arm/configs/lp8x4x_defconfig
+++ b/arch/arm/configs/lp8x4x_defconfig
@@ -111,6 +111,7 @@ CONFIG_SERIAL_8250_EXTENDED=y
 CONFIG_SERIAL_8250_MANY_PORTS=y
 CONFIG_SERIAL_8250_SHARE_IRQ=y
 CONFIG_SERIAL_PXA=y
+CONFIG_SERIAL_8250_LP8X4X=m
 CONFIG_HW_RANDOM=y
 CONFIG_I2C=m
 # CONFIG_I2C_COMPAT is not set
diff --git a/drivers/tty/serial/8250/8250_lp8x4x.c b/drivers/tty/serial/8250/8250_lp8x4x.c
new file mode 100644
index 0000000..ec2f215
--- /dev/null
+++ b/drivers/tty/serial/8250/8250_lp8x4x.c
@@ -0,0 +1,162 @@
+/*  linux/drivers/tty/serial/8250/8250_lp8x4x.c
+ *
+ *  Support for 16550A serial ports on ICP DAS LP-8x4x
+ *
+ *  Copyright (C) 2013 Sergei Ianovich <[email-masked]>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ */
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/serial_8250.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+struct lp8x4x_serial_data {
+	int			line;
+	void			*ios_mem;
+};
+
+static void lp8x4x_serial_set_termios(struct uart_port *port,
+		struct ktermios *termios, struct ktermios *old)
+{
+	unsigned int len;
+	unsigned int baud;
+	struct lp8x4x_serial_data *data = port->private_data;
+
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:
+		len = 7;
+		break;
+	case CS6:
+		len = 8;
+		break;
+	case CS7:
+		len = 9;
+		break;
+	default:
+	case CS8:
+		len = 10;
+		break;
+	}
+
+	if (termios->c_cflag & CSTOPB)
+		len++;
+	if (termios->c_cflag & PARENB)
+		len++;
+	if (!(termios->c_cflag & PARODD))
+		len++;
+#ifdef CMSPAR
+	if (termios->c_cflag & CMSPAR)
+		len++;
+#endif
+
+	len -= 9;
+	len &= 3;
+	len <<= 3;
+	/*
+	 * Ask the core to calculate the divisor for us.
+	 */
+	baud = uart_get_baud_rate(port, termios, old,
+				  port->uartclk / 16 / 0xffff,
+				  port->uartclk / 16);
+	switch (baud) {
+	case 2400:
+		len |= 1;
+	case 4800:
+		len |= 2;
+	case 19200:
+		len |= 4;
+	case 38400:
+		len |= 5;
+	case 57600:
+		len |= 6;
+	case 115200:
+		len |= 7;
+	case 9600:
+	default:
+		len |= 3;
+	};
+	iowrite8(len, data->ios_mem);
+
+	serial8250_do_set_termios(port, termios, old);
+}
+
+static struct of_device_id lp8x4x_serial_dt_ids[] = {
+	{ .compatible = "icpdas,uart-lp8x4x", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, lp8x4x_serial_dt_ids);
+
+static int lp8x4x_serial_probe(struct platform_device *pdev)
+{
+	struct uart_8250_port uart = {};
+	struct lp8x4x_serial_data *data;
+	struct resource *mmres, *mires, *irqres;
+	int ret;
+
+	mmres = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	mires = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	irqres = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (!mmres || !mires || !irqres)
+		return -ENODEV;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->ios_mem = devm_ioremap_resource(&pdev->dev, mires);
+	if (!data->ios_mem)
+		return -EFAULT;
+
+	uart.port.iotype = UPIO_MEM;
+	uart.port.mapbase = mmres->start;
+	uart.port.iobase = mmres->start;
+	uart.port.regshift = 1;
+	uart.port.irq = irqres->start;
+	uart.port.flags = UPF_IOREMAP;
+	uart.port.dev = &pdev->dev;
+	uart.port.uartclk = 14745600;
+	uart.port.set_termios = lp8x4x_serial_set_termios;
+	uart.port.private_data = data;
+
+	ret = serial8250_register_8250_port(&uart);
+	if (ret < 0)
+		return ret;
+
+	data->line = ret;
+
+	platform_set_drvdata(pdev, data);
+
+	return 0;
+}
+
+static int lp8x4x_serial_remove(struct platform_device *pdev)
+{
+	struct lp8x4x_serial_data *data = platform_get_drvdata(pdev);
+
+	serial8250_unregister_port(data->line);
+
+	return 0;
+}
+
+static struct platform_driver lp8x4x_serial_driver = {
+	.probe          = lp8x4x_serial_probe,
+	.remove         = lp8x4x_serial_remove,
+
+	.driver		= {
+		.name	= "uart-lp8x4x",
+		.owner	= THIS_MODULE,
+		.of_match_table = lp8x4x_serial_dt_ids,
+	},
+};
+
+module_platform_driver(lp8x4x_serial_driver);
+
+MODULE_AUTHOR("Sergei Ianovich");
+MODULE_DESCRIPTION("8250 serial port module for LP-8x4x");
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig
index 81bd7c9..9fb0fbb 100644
--- a/drivers/tty/serial/8250/Kconfig
+++ b/drivers/tty/serial/8250/Kconfig
@@ -311,3 +311,15 @@ config SERIAL_PXA
 	  can enable its onboard serial ports by enabling this option.
 
 	  If you choose M here, the module name will be 8250_pxa.
+
+config SERIAL_8250_LP8X4X
+	tristate "Support 16550A ports on ICP DAS LP-8x4x"
+	depends on OF && SERIAL_8250 && SERIAL_8250_MANY_PORTS && ARCH_PXA
+	select LP8X4X_IRQ
+	help
+	  In addition to serial ports on PXA270 SoC, LP-8x4x has 1 dual
+	  RS232/RS485 port, 1 RS485 port and 1 RS232 port.
+
+	  Say N here, unless you plan to run this kernel on a LP-8x4x system.
+
+	  If you choose M here, the module name will be 8250_lp8x4x.
diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile
index b7d1b61..7370bfb 100644
--- a/drivers/tty/serial/8250/Makefile
+++ b/drivers/tty/serial/8250/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_SERIAL_8250_ACCENT)	+= 8250_accent.o
 obj-$(CONFIG_SERIAL_8250_BOCA)		+= 8250_boca.o
 obj-$(CONFIG_SERIAL_8250_EXAR_ST16C554)	+= 8250_exar_st16c554.o
 obj-$(CONFIG_SERIAL_8250_HUB6)		+= 8250_hub6.o
+obj-$(CONFIG_SERIAL_8250_LP8X4X)	+= 8250_lp8x4x.o
 obj-$(CONFIG_SERIAL_8250_FSL)		+= 8250_fsl.o
 obj-$(CONFIG_SERIAL_8250_DW)		+= 8250_dw.o
 obj-$(CONFIG_SERIAL_8250_EM)		+= 8250_em.o
-- 
1.8.4.3