All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] Add sc16is7x2 driver
@ 2010-09-03 13:11 Manuel Stahl
  2010-09-14  0:25 ` Andrew Morton
  0 siblings, 1 reply; 12+ messages in thread
From: Manuel Stahl @ 2010-09-03 13:11 UTC (permalink / raw)
  To: linux-kernel

This patch adds support for the sc16is7x2 chips.

Signed-off-by: Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
---
   drivers/serial/Kconfig        |    7 +
   drivers/serial/Makefile       |    1 +
   drivers/serial/sc16is7x2.c    | 1351 
+++++++++++++++++++++++++++++++++++++++++
   include/linux/serial_core.h   |    3 +
   include/linux/spi/sc16is7x2.h |   17 +
   5 files changed, 1379 insertions(+), 0 deletions(-)

diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 12900f7..c336ea6 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -269,6 +269,13 @@ config SERIAL_8250_RM9K

   comment "Non-8250 serial port support"

+config SERIAL_SC16IS7X2
+	tristate "SC16IS7x2 chips"
+	depends on SPI_MASTER && GPIOLIB
+	select SERIAL_CORE
+	help
+	  gpio driver for SC16IS7x2 SPI UARTs.
+
   config SERIAL_AMBA_PL010
   	tristate "ARM AMBA PL010 serial port support"
   	depends on ARM_AMBA && (BROKEN || !ARCH_VERSATILE)
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index 1ca4fd5..4c6a297 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -28,6 +28,7 @@ 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_MCA) += 8250_mca.o
+obj-$(CONFIG_SERIAL_SC16IS7X2)	+= sc16is7x2.o
   obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
   obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
   obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o
diff --git a/drivers/serial/sc16is7x2.c b/drivers/serial/sc16is7x2.c
new file mode 100644
index 0000000..3ce3099
--- /dev/null
+++ b/drivers/serial/sc16is7x2.c
@@ -0,0 +1,1351 @@
+/**
+ * drivers/serial/sc16is7x2.c
+ *
+ * Copyright (C) 2009 Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
+ *
+ * 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.
+ *
+ * The SC16IS7x2 device is a SPI driven dual UART with GPIOs.
+ *
+ * The driver exports two uarts and a gpiochip interface.
+ */
+
+/* #define DEBUG */
+
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/spi/spi.h>
+#include <linux/freezer.h>
+#include <linux/spi/sc16is7x2.h>
+#include <linux/serial_core.h>
+#include <linux/serial_reg.h>
+#include <linux/gpio.h>
+
+#define SC16IS7X2_MAJOR		204
+#define SC16IS7X2_MINOR		209
+#define MAX_SC16IS7X2		8
+#define FIFO_SIZE		64
+
+#define DRIVER_NAME		"sc16is7x2"
+#define TYPE_NAME		"SC16IS7x2"
+
+
+
+#define REG_READ	0x80
+#define REG_WRITE	0x00
+
+/* Special registers */
+#define REG_TXLVL	0x08	/* Transmitter FIFO Level register */
+#define REG_RXLVL	0x09	/* Receiver FIFO Level register */
+#define REG_IOD		0x0A	/* IO Direction register */
+#define REG_IOS		0x0B	/* IO State register */
+#define REG_IOI		0x0C	/* IO Interrupt Enable register */
+#define REG_IOC		0x0E	/* IO Control register */
+
+#define IOC_SRESET	0x08    /* Software reset */
+#define IOC_GPIO30	0x04    /* GPIO 3:0 unset: as IO, set: as modem pins */
+#define IOC_GPIO74	0x02    /* GPIO 7:4 unset: as IO, set: as modem pins */
+#define IOC_IOLATCH	0x01    /* Unset: input unlatched, set: input 
latched */
+
+/* Redefine some MCR bits */
+#ifdef UART_MCR_TCRTLR
+#undef UART_MCR_TCRTLR
+#endif
+#define UART_MCR_TCRTLR		0x04
+#define UART_MCR_IRDA		0x40
+
+
+#define WRITE_CMD(reg, ch) (REG_WRITE | (reg & 0xf) << 3 | (ch & 0x1) << 1)
+#define READ_CMD(reg, ch)  (REG_READ  | (reg & 0xf) << 3 | (ch & 0x1) << 1)
+
+/* 16bit SPI command to read or write a register */
+struct sc16is7x2_spi_reg {
+	u8 cmd;
+	u8 value;
+} __attribute__ ((packed));
+
+struct sc16is7x2_chip;
+
+/*
+ * Some registers must be read back to modify.
+ * To save time we cache them here in memory
+ */
+struct sc16is7x2_channel {
+	struct sc16is7x2_chip	*chip;	/* back link */
+	struct mutex		lock;
+	struct uart_port 	uart;
+	struct spi_transfer fifo_rx;
+	struct spi_transfer fifo_tx[3];
+	u8		iir;
+	u8		lsr;
+	u8		msr;
+	u8		ier;		/* cache for IER register */
+	u8		fcr;		/* cache for FCR register */
+	u8		lcr;		/* cache for LCR register */
+	u8		mcr;		/* cache for MCR register */
+	u8		rxbuf[FIFO_SIZE+1];
+	u8		write_fifo_cmd;
+	u8		read_fifo_cmd;
+	bool		active;
+};
+
+struct sc16is7x2_chip {
+	struct spi_device *spi;
+	struct gpio_chip gpio;
+	struct mutex	 lock;
+	struct sc16is7x2_channel channel[2];
+
+	/* for handling irqs: need workqueue since we do spi_sync */
+	struct workqueue_struct *workqueue;
+	struct work_struct work;
+	/* set to 1 to make the workhandler exit as soon as possible */
+	int force_end_work;
+	/* need to know we are suspending to avoid deadlock on workqueue */
+	int suspending;
+
+	struct spi_message fifo_message;
+
+#define UART_BUG_TXEN	BIT(1)	/* UART has buggy TX IIR status */
+#define UART_BUG_NOMSR	BIT(2)	/* UART has buggy MSR status bits (Au1x00) */
+#define UART_BUG_THRE	BIT(3)	/* UART has buggy THRE reassertion */
+	u16		bugs;		/* port bugs */
+
+#define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS
+	u8		lsr_saved_flags;
+#define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA
+	u8		msr_saved_flags;
+	u8		io_dir;		/* cache for IODir register */
+	u8		io_state;	/* cache for IOState register */
+	u8		io_gpio;	/* PIN is GPIO */
+	u8		io_control;	/* cache for IOControl register */
+};
+
+/* ******************************** SPI 
********************************* */
+
+
+/*
+ * Reserve memory for command sequence
+ * @param cnt number of commands
+ */
+static inline struct sc16is7x2_spi_reg *
+sc16is7x2_alloc_spi_cmds(unsigned cnt)
+{
+	return kzalloc(sizeof(struct sc16is7x2_spi_reg)*cnt, GFP_KERNEL);
+}
+
+/*
+ * sc16is7x2_add_write_cmd - Add write command to sequence
+ */
+static inline void sc16is7x2_add_write_cmd(struct sc16is7x2_spi_reg *cmd,
+		u8 reg, u8 ch, u8 value)
+{
+	cmd->cmd = WRITE_CMD(reg, ch);
+	cmd->value = value;
+}
+
+/*
+ * sc16is7x2_add_read_cmd - Add read command to sequence
+ */
+static inline void sc16is7x2_add_read_cmd(struct sc16is7x2_spi_reg *cmd,
+		u8 reg, u8 ch)
+{
+	cmd->cmd = READ_CMD(reg, ch);
+	cmd->value = 0;
+}
+
+/*
+ * sc16is7x2_complete - Completion handler for async SPI transfers
+ */
+static void sc16is7x2_complete(void *context)
+{
+	struct spi_message *m = context;
+	u8 *tx_chain = m->state;
+
+	kfree(tx_chain);
+	kfree(m);
+}
+
+/*
+ * sc16is7x2_spi_async - Send command sequence
+ */
+static int sc16is7x2_spi_async(struct spi_device *spi,
+		struct sc16is7x2_spi_reg *cmds, unsigned len)
+{
+	struct spi_transfer *t;
+	struct spi_message *m;
+
+	m = spi_message_alloc(len, GFP_KERNEL);
+	if (!m)
+		return -ENOMEM;
+
+	m->complete = sc16is7x2_complete;
+	m->context = m;
+	m->state = cmds;
+	list_for_each_entry(t, &m->transfers, transfer_list) {
+		t->tx_buf = (u8 *)cmds;
+		t->len = 2;
+		t->cs_change = true;
+		cmds++;
+	}
+
+	return spi_async(spi, m);
+}
+
+/*
+ * sc16is7x2_write_async - Write a new register content (async)
+ */
+static inline int sc16is7x2_write_async(struct spi_device *spi, u8 reg, 
u8 ch,
+		u8 value)
+{
+	struct sc16is7x2_spi_reg *cmd = sc16is7x2_alloc_spi_cmds(1);
+	if (!cmd)
+		return -ENOMEM;
+	sc16is7x2_add_write_cmd(cmd, reg, ch, value);
+	return sc16is7x2_spi_async(spi, cmd, 1);
+}
+
+/*
+ * sc16is7x2_write - Write a new register content (sync)
+ */
+static int sc16is7x2_write(struct spi_device *spi, u8 reg, u8 ch, u8 val)
+{
+	u16 word = REG_WRITE | (reg & 0xf) << 3 | (ch & 0x3) << 1 | val << 8;
+	return spi_write(spi, (const u8 *)&word, sizeof(word));
+}
+
+/**
+ * sc16is7x2_read - Read back register content
+ * @spi: The SPI device
+ * @reg: Register offset
+ *
+ * Returns positive 8 bit value from device if successful or a
+ * negative value on error
+ */
+static int sc16is7x2_read(struct spi_device *spi, unsigned reg, 
unsigned ch)
+{
+	u8 cmd = REG_READ | (reg & 0xf) << 3 | (ch & 0x3) << 1;
+	return spi_w8r8(spi, cmd);
+}
+
+/* ******************************** UART 
********************************* */
+
+/* Uart divisor latch write */
+static inline void sc16is7x2_add_dl_write_cmd(struct sc16is7x2_spi_reg 
*cmd,
+		u8 ch, int value)
+{
+	sc16is7x2_add_write_cmd(&cmd[0], UART_DLL, ch, value & 0xff);
+	sc16is7x2_add_write_cmd(&cmd[1], UART_DLM, ch, value >> 8 & 0xff);
+}
+
+static unsigned int sc16is7x2_tx_empty(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned lsr;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	mutex_lock(&chan->lock);
+	lsr = chan->lsr;
+	mutex_unlock(&chan->lock);
+
+	return lsr & UART_LSR_TEMT ? TIOCSER_TEMT : 0;
+}
+
+static unsigned int sc16is7x2_get_mctrl(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned int status;
+	unsigned int ret;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	status = chan->msr;
+
+	ret = 0;
+	if (status & UART_MSR_DCD)
+		ret |= TIOCM_CAR;
+	if (status & UART_MSR_RI)
+		ret |= TIOCM_RNG;
+	if (status & UART_MSR_DSR)
+		ret |= TIOCM_DSR;
+	if (status & UART_MSR_CTS)
+		ret |= TIOCM_CTS;
+	return ret;
+}
+
+static unsigned int __set_mctrl(unsigned int mctrl)
+{
+	unsigned char mcr = 0;
+
+	if (mctrl & TIOCM_RTS)
+		mcr |= UART_MCR_RTS;
+	if (mctrl & TIOCM_DTR)
+		mcr |= UART_MCR_DTR;
+	if (mctrl & TIOCM_LOOP)
+		mcr |= UART_MCR_LOOP;
+
+	return mcr;
+}
+
+static void sc16is7x2_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	sc16is7x2_write_async(ts->spi, UART_MCR, ch, __set_mctrl(mctrl));
+}
+
+static inline void __stop_tx(struct sc16is7x2_channel *chan)
+{
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = chan->uart.line & 0x01;
+	if (chan->ier & UART_IER_THRI) {
+		chan->ier &= ~UART_IER_THRI;
+		sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
+	}
+}
+
+static void sc16is7x2_stop_tx(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	__stop_tx(chan);
+}
+
+static void sc16is7x2_start_tx(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	if (!(chan->ier & UART_IER_THRI)) {
+		chan->ier |= UART_IER_THRI;
+		sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
+	}
+}
+
+static void sc16is7x2_stop_rx(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	chan->ier &= ~UART_IER_RLSI;
+	chan->uart.read_status_mask &= ~UART_LSR_DR;
+	sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
+}
+
+static void sc16is7x2_enable_ms(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	chan->ier |= UART_IER_MSI;
+	sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
+}
+
+static void sc16is7x2_break_ctl(struct uart_port *port, int break_state)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+	unsigned long flags;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	spin_lock_irqsave(&chan->uart.lock, flags);
+	if (break_state == -1)
+		chan->lcr |= UART_LCR_SBC;
+	else
+		chan->lcr &= ~UART_LCR_SBC;
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	sc16is7x2_write_async(ts->spi, UART_LCR, ch, chan->lcr);
+}
+
+static int sc16is7x2_startup(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+	struct sc16is7x2_spi_reg *cmds, *cmd;
+	unsigned long flags;
+
+	dev_dbg(&ts->spi->dev, "%s (line %d)\n", __func__, port->line);
+
+	spin_lock_irqsave(&chan->uart.lock, flags);
+	chan->lcr = UART_LCR_WLEN8;
+	chan->mcr = __set_mctrl(chan->uart.mctrl);
+	chan->fcr = 0;
+	chan->ier = UART_IER_RLSI | UART_IER_RDI;
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	cmds = sc16is7x2_alloc_spi_cmds(8);
+	if (!cmds)
+		return -ENOMEM;
+
+	cmd = cmds;
+	/* Clear the interrupt registers. */
+	sc16is7x2_add_write_cmd(cmd, UART_IER, ch, 0);
+	sc16is7x2_add_read_cmd(++cmd, UART_IIR, ch);
+	sc16is7x2_add_read_cmd(++cmd, UART_LSR, ch);
+	sc16is7x2_add_read_cmd(++cmd, UART_MSR, ch);
+
+	sc16is7x2_add_write_cmd(++cmd, UART_FCR, ch, UART_FCR_ENABLE_FIFO |
+		       UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+	sc16is7x2_add_write_cmd(++cmd, UART_FCR, ch, chan->fcr);
+	/* Now, initialize the UART */
+	sc16is7x2_add_write_cmd(++cmd, UART_LCR, ch, chan->lcr);
+	sc16is7x2_add_write_cmd(++cmd, UART_MCR, ch, chan->mcr);
+
+	sc16is7x2_spi_async(ts->spi, cmds, 8);
+
+	chan->active = true;
+	return 0;
+}
+
+static void sc16is7x2_shutdown(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned long flags;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	BUG_ON(!chan);
+	BUG_ON(!ts);
+
+	if (ts->suspending)
+		return;
+
+	/* Disable interrupts from this port */
+	chan->ier = 0;
+	chan->active = false;
+	sc16is7x2_write(ts->spi, UART_IER, ch, chan->ier);
+
+	/* Wait for worker of this channel to finish */
+	mutex_lock(&chan->lock);
+
+	spin_lock_irqsave(&chan->uart.lock, flags);
+	chan->mcr = __set_mctrl(chan->uart.mctrl);
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	/* Disable break condition and FIFOs */
+	chan->lcr &= ~UART_LCR_SBC;
+
+	sc16is7x2_write(ts->spi, UART_MCR, ch, chan->mcr);
+	sc16is7x2_write(ts->spi, UART_LCR, ch, chan->lcr);
+
+	mutex_unlock(&chan->lock);
+}
+
+static void
+sc16is7x2_set_termios(struct uart_port *port, struct ktermios *termios,
+		       struct ktermios *old)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	struct sc16is7x2_spi_reg *cmds;
+	unsigned ch = port->line & 0x01;
+	unsigned long flags;
+	unsigned int baud, quot;
+	u8 ier, mcr, lcr, fcr = 0;
+	u8 efr = UART_EFR_ECB;
+
+	/* set word length */
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:
+		lcr = UART_LCR_WLEN5;
+		break;
+	case CS6:
+		lcr = UART_LCR_WLEN6;
+		break;
+	case CS7:
+		lcr = UART_LCR_WLEN7;
+		break;
+	default:
+	case CS8:
+		lcr = UART_LCR_WLEN8;
+		break;
+	}
+
+	if (termios->c_cflag & CSTOPB)
+		lcr |= UART_LCR_STOP;
+	if (termios->c_cflag & PARENB)
+		lcr |= UART_LCR_PARITY;
+	if (!(termios->c_cflag & PARODD))
+		lcr |= UART_LCR_EPAR;
+#ifdef CMSPAR
+	if (termios->c_cflag & CMSPAR)
+		lcr |= UART_LCR_SPAR;
+#endif
+
+	/* Ask the core to calculate the divisor for us. */
+	baud = uart_get_baud_rate(port, termios, old,
+				  port->uartclk / 16 / 0xffff,
+				  port->uartclk / 16);
+	quot = uart_get_divisor(port, baud);
+
+	dev_dbg(&ts->spi->dev, "%s (baud %u)\n", __func__, baud);
+
+
+	/* configure the fifo */
+	if (baud < 2400)
+		fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1;
+	else
+		fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_01;
+
+	/*
+	 * MCR-based auto flow control.  When AFE is enabled, RTS will be
+	 * deasserted when the receive FIFO contains more characters than
+	 * the trigger, or the MCR RTS bit is cleared.  In the case where
+	 * the remote UART is not using CTS auto flow control, we must
+	 * have sufficient FIFO entries for the latency of the remote
+	 * UART to respond.  IOW, at least 32 bytes of FIFO.
+	 */
+	chan->mcr &= ~UART_MCR_AFE;
+	if (termios->c_cflag & CRTSCTS)
+		chan->mcr |= UART_MCR_AFE;
+
+	/*
+	 * Ok, we're now changing the port state.  Do it with
+	 * interrupts disabled.
+	 */
+	spin_lock_irqsave(&chan->uart.lock, flags);
+
+	/* we are sending char from a workqueue so enable */
+	chan->uart.state->port.tty->low_latency = 1;
+
+	/* Update the per-port timeout. */
+	uart_update_timeout(port, termios->c_cflag, baud);
+
+	chan->uart.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+	if (termios->c_iflag & INPCK)
+		chan->uart.read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+	if (termios->c_iflag & (BRKINT | PARMRK))
+		chan->uart.read_status_mask |= UART_LSR_BI;
+
+	/* Characters to ignore */
+	chan->uart.ignore_status_mask = 0;
+	if (termios->c_iflag & IGNPAR)
+		chan->uart.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+	if (termios->c_iflag & IGNBRK) {
+		chan->uart.ignore_status_mask |= UART_LSR_BI;
+		/*
+		 * If we're ignoring parity and break indicators,
+		 * ignore overruns too (for real raw support).
+		 */
+		if (termios->c_iflag & IGNPAR)
+			chan->uart.ignore_status_mask |= UART_LSR_OE;
+	}
+
+	/* ignore all characters if CREAD is not set */
+	if ((termios->c_cflag & CREAD) == 0)
+		chan->uart.ignore_status_mask |= UART_LSR_DR;
+
+	/* CTS flow control flag and modem status interrupts */
+	chan->ier &= ~UART_IER_MSI;
+	if (UART_ENABLE_MS(&chan->uart, termios->c_cflag))
+		chan->ier |= UART_IER_MSI;
+
+	if (termios->c_cflag & CRTSCTS)
+		efr |= UART_EFR_CTS | UART_EFR_RTS;
+
+	mcr = __set_mctrl(chan->uart.mctrl);
+	ier = chan->ier;
+	chan->lcr = lcr;				/* Save LCR */
+	chan->fcr = fcr;				/* Save FCR */
+	chan->mcr = mcr;				/* Save MCR */
+
+	fcr |= UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT;
+
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	/* build a compound spi message to set all registers */
+	cmds = sc16is7x2_alloc_spi_cmds(9);
+	if (!cmds)
+		return;
+
+	/* set DLAB */
+	sc16is7x2_add_write_cmd(&cmds[0], UART_LCR, ch, UART_LCR_DLAB);
+	/* set divisor, must be set before UART_EFR_ECB */
+	sc16is7x2_add_dl_write_cmd(&cmds[1], ch, quot);
+	sc16is7x2_add_write_cmd(&cmds[3], UART_LCR, ch, 0xBF);	/* access EFR */
+	sc16is7x2_add_write_cmd(&cmds[4], UART_EFR, ch, efr);
+	sc16is7x2_add_write_cmd(&cmds[5], UART_LCR, ch, lcr);	/* reset DLAB */
+	sc16is7x2_add_write_cmd(&cmds[6], UART_FCR, ch, fcr);
+	sc16is7x2_add_write_cmd(&cmds[7], UART_MCR, ch, mcr);
+	sc16is7x2_add_write_cmd(&cmds[8], UART_IER, ch, ier);
+
+	sc16is7x2_spi_async(ts->spi, cmds, 9);
+
+	/* Don't rewrite B0 */
+	if (tty_termios_baud_rate(termios))
+		tty_termios_encode_baud_rate(termios, baud, baud);
+}
+
+static const char *
+sc16is7x2_type(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	return TYPE_NAME;
+}
+
+static void sc16is7x2_release_port(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	ts->force_end_work = 1;
+}
+
+static int sc16is7x2_request_port(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	return 0;
+}
+
+static void sc16is7x2_config_port(struct uart_port *port, int flags)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	if (flags & UART_CONFIG_TYPE)
+		chan->uart.type = PORT_SC16IS7X2;
+}
+
+static int
+sc16is7x2_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	if (ser->irq < 0 || ser->baud_base < 9600 ||
+			ser->type != PORT_SC16IS7X2)
+		return -EINVAL;
+	return 0;
+}
+
+static struct uart_ops sc16is7x2_uart_ops = {
+	.tx_empty	= sc16is7x2_tx_empty,
+	.set_mctrl	= sc16is7x2_set_mctrl,
+	.get_mctrl	= sc16is7x2_get_mctrl,
+	.stop_tx        = sc16is7x2_stop_tx,
+	.start_tx	= sc16is7x2_start_tx,
+	.stop_rx	= sc16is7x2_stop_rx,
+	.enable_ms      = sc16is7x2_enable_ms,
+	.break_ctl      = sc16is7x2_break_ctl,
+	.startup	= sc16is7x2_startup,
+	.shutdown	= sc16is7x2_shutdown,
+	.set_termios	= sc16is7x2_set_termios,
+	.type		= sc16is7x2_type,
+	.release_port   = sc16is7x2_release_port,
+	.request_port   = sc16is7x2_request_port,
+	.config_port	= sc16is7x2_config_port,
+	.verify_port	= sc16is7x2_verify_port,
+};
+
+
+#define MIN(a, b) ((a < b) ? (a) : (b))
+
+/* ******************************** GPIO 
********************************* */
+
+static int sc16is7x2_gpio_request(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	int control = (offset < 4) ? IOC_GPIO30 : IOC_GPIO74;
+	int ret = 0;
+
+	BUG_ON(offset > 8);
+	dev_dbg(&ts->spi->dev, "%s: offset = %d\n", __func__, offset);
+
+	mutex_lock(&ts->lock);
+
+	/* GPIO 0:3 and 4:7 can only be controlled as block */
+	ts->io_gpio |= BIT(offset);
+	if (ts->io_control & control) {
+		dev_dbg(&ts->spi->dev, "activate GPIOs %s\n",
+				(offset < 4) ? "0-3" : "4-7");
+		ts->io_control &= ~control;
+		ret = sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control);
+	}
+
+	mutex_unlock(&ts->lock);
+
+	return ret;
+}
+
+static void sc16is7x2_gpio_free(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	int control = (offset < 4) ? IOC_GPIO30 : IOC_GPIO74;
+	int mask = (offset < 4) ? 0x0f : 0xf0;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	/* GPIO 0:3 and 4:7 can only be controlled as block */
+	ts->io_gpio &= ~BIT(offset);
+	dev_dbg(&ts->spi->dev, "%s: io_gpio = 0x%02X\n", __func__, ts->io_gpio);
+	if (!(ts->io_control & control) && !(ts->io_gpio & mask)) {
+		dev_dbg(&ts->spi->dev, "deactivate GPIOs %s\n",
+				(offset < 4) ? "0-3" : "4-7");
+		ts->io_control |= control;
+		sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control);
+	}
+
+	mutex_unlock(&ts->lock);
+}
+
+static int sc16is7x2_direction_input(struct gpio_chip *gpio, unsigned 
offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	unsigned io_dir;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	ts->io_dir &= ~BIT(offset);
+	io_dir = ts->io_dir;
+
+	mutex_unlock(&ts->lock);
+
+	return sc16is7x2_write_async(ts->spi, REG_IOD, 0, io_dir);
+}
+
+static int sc16is7x2_direction_output(struct gpio_chip *gpio, unsigned 
offset,
+				    int value)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	struct sc16is7x2_spi_reg *cmds;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	if (value)
+		ts->io_state |= BIT(offset);
+	else
+		ts->io_state &= ~BIT(offset);
+
+	ts->io_dir |= BIT(offset);
+
+	cmds = sc16is7x2_alloc_spi_cmds(2);
+	if (cmds) {
+		sc16is7x2_add_write_cmd(&cmds[0], REG_IOS, 0, ts->io_state);
+		sc16is7x2_add_write_cmd(&cmds[1], REG_IOD, 0, ts->io_dir);
+	}
+
+	mutex_unlock(&ts->lock);
+
+	return sc16is7x2_spi_async(ts->spi, cmds, 2);
+}
+
+static int sc16is7x2_get(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	int level = -EINVAL;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	if (ts->io_dir & BIT(offset)) {
+		/* Output: return cached level */
+		level = (ts->io_state >> offset) & 0x01;
+	} else {
+		/* Input: read out all pins */
+		level = sc16is7x2_read(ts->spi, REG_IOS, 0);
+		if (level >= 0) {
+			ts->io_state = level;
+			level = (ts->io_state >> offset) & 0x01;
+		}
+	}
+
+	mutex_unlock(&ts->lock);
+
+	return level;
+}
+
+static void sc16is7x2_set(struct gpio_chip *gpio, unsigned offset, int 
value)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	unsigned io_state;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	if (value)
+		ts->io_state |= BIT(offset);
+	else
+		ts->io_state &= ~BIT(offset);
+	io_state = ts->io_state;
+
+	mutex_unlock(&ts->lock);
+
+	sc16is7x2_write_async(ts->spi, REG_IOS, 0, io_state);
+}
+
+/* ******************************** IRQ 
********************************* */
+
+static void sc16is7x2_handle_fifo_rx(struct sc16is7x2_channel *chan)
+{
+	struct uart_port *uart = &chan->uart;
+	struct tty_struct *tty = uart->state->port.tty;
+	u8 *rxbuf = chan->rxbuf;
+	u8 lsr = chan->lsr;
+	unsigned i, count = chan->fifo_rx.len;
+	unsigned long flags;
+	char flag = TTY_NORMAL;
+
+	spin_lock_irqsave(&uart->lock, flags);
+
+	if (unlikely(lsr & UART_LSR_BRK_ERROR_BITS)) {
+		/*
+		 * For statistics only
+		 */
+		if (lsr & UART_LSR_BI) {
+			lsr &= ~(UART_LSR_FE | UART_LSR_PE);
+			chan->uart.icount.brk++;
+			/*
+			 * We do the SysRQ and SAK checking
+			 * here because otherwise the break
+			 * may get masked by ignore_status_mask
+			 * or read_status_mask.
+			 */
+			if (uart_handle_break(&chan->uart))
+				goto ignore_char;
+		} else if (lsr & UART_LSR_PE)
+			chan->uart.icount.parity++;
+		else if (lsr & UART_LSR_FE)
+			chan->uart.icount.frame++;
+		if (lsr & UART_LSR_OE)
+			chan->uart.icount.overrun++;
+
+		/*
+		 * Mask off conditions which should be ignored.
+		 */
+		lsr &= chan->uart.read_status_mask;
+
+		if (lsr & UART_LSR_BI)
+			flag = TTY_BREAK;
+		else if (lsr & UART_LSR_PE)
+			flag = TTY_PARITY;
+		else if (lsr & UART_LSR_FE)
+			flag = TTY_FRAME;
+	}
+
+	for (i = 1; i < count; i++) {
+		uart->icount.rx++;
+
+		if (!uart_handle_sysrq_char(uart, rxbuf[i]))
+			uart_insert_char(uart, lsr, UART_LSR_OE,
+					rxbuf[i], flag);
+	}
+
+ignore_char:
+	spin_unlock_irqrestore(&uart->lock, flags);
+
+	if (count > 1)
+		tty_flip_buffer_push(tty);
+}
+
+static void sc16is7x2_handle_fifo_tx(struct sc16is7x2_channel *chan)
+{
+	struct uart_port *uart = &chan->uart;
+	struct circ_buf *xmit = &uart->state->xmit;
+	unsigned count = chan->fifo_tx[1].len + chan->fifo_tx[2].len;
+	unsigned long flags;
+
+	BUG_ON(!uart);
+	BUG_ON(!xmit);
+
+	spin_lock_irqsave(&uart->lock, flags);
+
+	uart->icount.tx += count;
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(uart);
+
+	if (uart_circ_empty(xmit))
+		__stop_tx(chan);
+
+	spin_unlock_irqrestore(&uart->lock, flags);
+}
+
+static bool sc16is7x2_msg_add_fifo_rx(struct sc16is7x2_chip *ts, 
unsigned ch)
+{
+	struct spi_message *m = &(ts->fifo_message);
+	struct spi_transfer *t = &(ts->channel[ch].fifo_rx);
+	int rxlvl = sc16is7x2_read(ts->spi, REG_RXLVL, ch);
+	if (rxlvl > 0) {
+		t->len = rxlvl + 1;
+		spi_message_add_tail(t, m);
+		return true;
+	}
+	return false;
+}
+
+static bool sc16is7x2_msg_add_fifo_tx(struct sc16is7x2_chip *ts, 
unsigned ch)
+{
+	struct sc16is7x2_channel * const chan = &(ts->channel[ch]);
+	struct uart_port *uart = &chan->uart;
+	struct circ_buf *xmit = &uart->state->xmit;
+	unsigned count;
+	bool split_transfer;
+	u8 txlvl;
+
+	if (chan->uart.x_char && chan->lsr & UART_LSR_THRE) {
+		dev_dbg(&ts->spi->dev, "tx: x-char\n");
+		sc16is7x2_write(ts->spi, UART_TX, ch, uart->x_char);
+		uart->icount.tx++;
+		uart->x_char = 0;
+		return false;
+	}
+	if (uart_tx_stopped(&chan->uart)) {
+		dev_dbg(&ts->spi->dev, "tx: stopped!\n");
+		sc16is7x2_stop_tx(uart);
+		return false;
+	}
+	if (uart_circ_empty(xmit)) {
+		__stop_tx(chan);
+		return false;
+	}
+
+	txlvl = sc16is7x2_read(ts->spi, REG_TXLVL, ch);
+	if (txlvl <= 0) {
+		dev_dbg(&ts->spi->dev, " fifo full\n");
+		return false;
+	}
+
+	/* number of bytes to transfer to the fifo */
+	count = MIN(txlvl, uart_circ_chars_pending(xmit));
+	split_transfer = (UART_XMIT_SIZE - xmit->tail) <= count;
+
+	/* add command transfer */
+	spi_message_add_tail(&(chan->fifo_tx[0]), &(ts->fifo_message));
+	/* add first fifo transfer */
+	spi_message_add_tail(&(chan->fifo_tx[1]), &(ts->fifo_message));
+
+	chan->fifo_tx[1].tx_buf = xmit->buf + xmit->tail;
+
+	if (!split_transfer) {
+		chan->fifo_tx[1].len = count;
+		chan->fifo_tx[1].cs_change = true;
+
+		chan->fifo_tx[2].len = 0;
+	} else {
+		chan->fifo_tx[1].len = (UART_XMIT_SIZE - 1) - xmit->tail;
+		chan->fifo_tx[1].cs_change = false;
+
+		chan->fifo_tx[2].tx_buf = xmit->buf;
+		chan->fifo_tx[2].cs_change = true;
+		chan->fifo_tx[2].len = count - chan->fifo_tx[1].len;
+		/* add second fifo transfer */
+		spi_message_add_tail(&(chan->fifo_tx[2]), &(ts->fifo_message));
+	}
+
+	xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
+	return true;
+}
+
+static void sc16is7x2_handle_modem(struct sc16is7x2_chip *ts, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
+	struct uart_port *uart = &chan->uart;
+
+	if (chan->msr & UART_MSR_ANY_DELTA
+			&& chan->ier & UART_IER_MSI
+			&& uart->state != NULL) {
+		if (chan->msr & UART_MSR_TERI)
+			uart->icount.rng++;
+		if (chan->msr & UART_MSR_DDSR)
+			uart->icount.dsr++;
+		if (chan->msr & UART_MSR_DDCD)
+			uart_handle_dcd_change(uart, chan->msr & UART_MSR_DCD);
+		if (chan->msr & UART_MSR_DCTS)
+			uart_handle_cts_change(uart, chan->msr & UART_MSR_CTS);
+
+		wake_up_interruptible(&uart->state->port.delta_msr_wait);
+	}
+}
+
+static bool sc16is7x2_handle_channel(struct sc16is7x2_chip *ts, 
unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
+	struct spi_message *m = &(ts->fifo_message);
+	bool rx, tx;
+
+	dev_dbg(&ts->spi->dev, "%s (%i)\n", __func__, ch);
+
+	chan->iir = sc16is7x2_read(ts->spi, UART_IIR, ch);
+	chan->msr = sc16is7x2_read(ts->spi, UART_MSR, ch);
+	chan->lsr = sc16is7x2_read(ts->spi, UART_LSR, ch);
+
+	sc16is7x2_handle_modem(ts, ch);
+
+	spi_message_init(m);
+	rx = sc16is7x2_msg_add_fifo_rx(ts, ch);
+	tx = sc16is7x2_msg_add_fifo_tx(ts, ch);
+
+	if (rx || tx)
+		spi_sync(ts->spi, m);
+
+	if (rx)
+		sc16is7x2_handle_fifo_rx(chan);
+	if (tx)
+		sc16is7x2_handle_fifo_tx(chan);
+
+	dev_dbg(&ts->spi->dev, "%s finished (iir = 0x%02x)\n",
+			__func__, chan->iir);
+
+	return (chan->iir & UART_IIR_NO_INT) == 0x00;
+}
+
+static void sc16is7x2_work(struct work_struct *w)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(w, struct sc16is7x2_chip, work);
+	unsigned pending = 0;
+	unsigned ch = 0;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	BUG_ON(!w);
+	BUG_ON(!ts);
+
+
+	if (ts->force_end_work) {
+		dev_dbg(&ts->spi->dev, "%s: force end!\n", __func__);
+		return;
+	}
+
+	if (ts->channel[0].active)
+		pending |= BIT(0);
+	if (ts->channel[1].active)
+		pending |= BIT(1);
+
+	do {
+		mutex_lock(&(ts->channel[ch].lock));
+		if (pending & BIT(ch) && ts->channel[ch].active) {
+			if (!sc16is7x2_handle_channel(ts, ch))
+				pending &= ~BIT(ch);
+		}
+		mutex_unlock(&(ts->channel[ch].lock));
+		ch ^= 1;	/* switch channel */
+	} while (!ts->force_end_work && !freezing(current) && pending);
+
+	dev_dbg(&ts->spi->dev, "%s finished\n", __func__);
+}
+
+static irqreturn_t sc16is7x2_interrupt(int irq, void *dev_id)
+{
+	struct sc16is7x2_chip *ts = dev_id;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	if (!ts->force_end_work && !work_pending(&ts->work) &&
+	    !freezing(current) && !ts->suspending)
+		queue_work(ts->workqueue, &ts->work);
+
+	return IRQ_HANDLED;
+}
+
+/* ******************************** INIT 
********************************* */
+
+static struct uart_driver sc16is7x2_uart_driver;
+
+static int sc16is7x2_register_gpio(struct sc16is7x2_chip *ts,
+		struct sc16is7x2_platform_data *pdata)
+{
+	struct sc16is7x2_spi_reg *cmds;
+
+	ts->gpio.label = (pdata->label) ? pdata->label : DRIVER_NAME;
+	ts->gpio.request	= sc16is7x2_gpio_request;
+	ts->gpio.free		= sc16is7x2_gpio_free;
+	ts->gpio.get		= sc16is7x2_get;
+	ts->gpio.set		= sc16is7x2_set;
+	ts->gpio.direction_input = sc16is7x2_direction_input;
+	ts->gpio.direction_output = sc16is7x2_direction_output;
+
+	ts->gpio.base = pdata->gpio_base;
+	ts->gpio.names = pdata->names;
+	ts->gpio.ngpio = SC16IS7X2_NR_GPIOS;
+	ts->gpio.can_sleep = 1;
+	ts->gpio.dev = &ts->spi->dev;
+	ts->gpio.owner = THIS_MODULE;
+
+	/* disable all GPIOs, enable on request */
+	ts->io_dir = 0x0f;
+	ts->io_state = 0;
+	ts->io_gpio = 0;
+	ts->io_control = IOC_GPIO30 | IOC_GPIO74;
+
+	cmds = sc16is7x2_alloc_spi_cmds(4);
+	if (!cmds)
+		return -ENOMEM;
+
+	sc16is7x2_add_write_cmd(&cmds[0], REG_IOI, 0, 0);
+	sc16is7x2_add_write_cmd(&cmds[1], REG_IOC, 0, ts->io_control);
+	sc16is7x2_add_write_cmd(&cmds[2], REG_IOS, 0, ts->io_state);
+	sc16is7x2_add_write_cmd(&cmds[3], REG_IOD, 0, ts->io_dir);
+	sc16is7x2_spi_async(ts->spi, cmds, 4);
+
+	return gpiochip_add(&ts->gpio);
+}
+
+static int sc16is7x2_register_uart_port(struct sc16is7x2_chip *ts,
+		struct sc16is7x2_platform_data *pdata, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
+	struct uart_port *uart = &chan->uart;
+
+	mutex_init(&chan->lock);
+	chan->active = false;	/* will be set in startup */
+	chan->chip = ts;
+
+	chan->read_fifo_cmd = READ_CMD(UART_RX, ch);
+	chan->fifo_rx.tx_buf = &(chan->read_fifo_cmd);
+	chan->fifo_rx.rx_buf = chan->rxbuf;
+	chan->fifo_rx.cs_change = true;
+
+	chan->write_fifo_cmd = WRITE_CMD(UART_TX, ch);
+	chan->fifo_tx[0].tx_buf = &(chan->write_fifo_cmd);
+	chan->fifo_tx[0].rx_buf = NULL;
+	chan->fifo_tx[0].len = 1;
+	chan->fifo_tx[0].cs_change = false;
+	chan->fifo_tx[1].rx_buf = NULL;
+	chan->fifo_tx[2].rx_buf = NULL;
+
+	uart->irq = ts->spi->irq;
+	uart->uartclk = pdata->uartclk;
+	uart->fifosize = FIFO_SIZE;
+	uart->ops = &sc16is7x2_uart_ops;
+	uart->flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF;
+	uart->line = pdata->uart_base + ch;
+	uart->type = PORT_SC16IS7X2;
+	uart->dev = &ts->spi->dev;
+
+	return uart_add_one_port(&sc16is7x2_uart_driver, uart);
+}
+
+static int __devinit sc16is7x2_probe(struct spi_device *spi)
+{
+	struct sc16is7x2_chip *ts;
+	struct sc16is7x2_platform_data *pdata;
+	int ret;
+
+	pdata = spi->dev.platform_data;
+	if (!pdata || !pdata->gpio_base /* || pdata->uart_base */) {
+		dev_dbg(&spi->dev, "incorrect or missing platform data\n");
+		return -EINVAL;
+	}
+
+	printk(KERN_INFO DRIVER_NAME " at CS%d (irq %d), 2 UARTs, 8 GPIOs\n"
+			"    eser%d, eser%d, gpiochip%d\n",
+			spi->chip_select, spi->irq,
+			pdata->uart_base, pdata->uart_base + 1,
+			pdata->gpio_base);
+
+	ret = spi_setup(spi);
+	if (ret < 0)
+		return ret;
+
+	ts = kzalloc(sizeof(struct sc16is7x2_chip), GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	mutex_init(&ts->lock);
+	dev_set_drvdata(&spi->dev, ts);
+	ts->spi = spi;
+	ts->force_end_work = 1;
+
+	/* Reset the chip TODO: and disable IRQ output */
+	sc16is7x2_write(spi, REG_IOC, 0, IOC_SRESET);
+
+	ret = request_irq(spi->irq, sc16is7x2_interrupt,
+			IRQF_TRIGGER_FALLING | IRQF_SHARED, "sc16is7x2", ts);
+	if (ret) {
+		dev_warn(&ts->spi->dev, "cannot register interrupt\n");
+		goto exit_destroy;
+	}
+
+	ret = sc16is7x2_register_uart_port(ts, pdata, 0);
+	if (ret)
+		goto exit_irq;
+	ret = sc16is7x2_register_uart_port(ts, pdata, 1);
+	if (ret)
+		goto exit_uart0;
+
+	ret = sc16is7x2_register_gpio(ts, pdata);
+	if (ret)
+		goto exit_uart1;
+
+	ts->workqueue = create_freezeable_workqueue(DRIVER_NAME);
+	if (!ts->workqueue) {
+		dev_warn(&ts->spi->dev, "cannot create workqueue\n");
+		ret = -EBUSY;
+		goto exit_gpio;
+	}
+	INIT_WORK(&ts->work, sc16is7x2_work);
+	ts->force_end_work = 0;
+
+	return ret;
+
+exit_gpio:
+	ret = gpiochip_remove(&ts->gpio);
+
+exit_uart1:
+	uart_remove_one_port(&sc16is7x2_uart_driver, &ts->channel[1].uart);
+
+exit_uart0:
+	uart_remove_one_port(&sc16is7x2_uart_driver, &ts->channel[0].uart);
+
+exit_irq:
+	free_irq(spi->irq, ts);
+
+exit_destroy:
+	dev_set_drvdata(&spi->dev, NULL);
+	mutex_destroy(&ts->lock);
+	kfree(ts);
+	return ret;
+}
+
+static int __devexit sc16is7x2_remove(struct spi_device *spi)
+{
+	struct sc16is7x2_chip *ts;
+	int ret;
+
+	ts = dev_get_drvdata(&spi->dev);
+	if (ts == NULL)
+		return -ENODEV;
+
+	free_irq(spi->irq, ts);
+	ts->force_end_work = 1;
+
+	if (ts->workqueue) {
+		flush_workqueue(ts->workqueue);
+		destroy_workqueue(ts->workqueue);
+		ts->workqueue = NULL;
+	}
+
+	dev_set_drvdata(&spi->dev, NULL);
+
+	ret = uart_remove_one_port(&sc16is7x2_uart_driver,
+			&ts->channel[0].uart);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to remove the UART port A: %d\n",
+			ret);
+		goto exit_error;
+	}
+	ret = uart_remove_one_port(&sc16is7x2_uart_driver,
+			&ts->channel[1].uart);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to remove the UART port B: %d\n",
+			ret);
+		goto exit_error;
+	}
+	ret = gpiochip_remove(&ts->gpio);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to remove the GPIO controller: %d\n",
+			ret);
+		goto exit_error;
+	}
+
+	mutex_destroy(&ts->lock);
+	kfree(ts);
+
+exit_error:
+	return ret;
+}
+
+static struct uart_driver sc16is7x2_uart_driver = {
+	.owner          = THIS_MODULE,
+	.driver_name    = DRIVER_NAME,
+	.dev_name       = "eser",
+	.major          = SC16IS7X2_MAJOR,
+	.minor          = SC16IS7X2_MINOR,
+	.nr             = MAX_SC16IS7X2,
+};
+
+static struct spi_driver sc16is7x2_spi_driver = {
+	.driver = {
+		.name		= DRIVER_NAME,
+		.owner		= THIS_MODULE,
+	},
+	.probe		= sc16is7x2_probe,
+	.remove		= __devexit_p(sc16is7x2_remove),
+};
+
+static int __init sc16is7x2_init(void)
+{
+	int ret;
+	ret = uart_register_driver(&sc16is7x2_uart_driver);
+	if (ret) {
+		printk(KERN_ERR "Couldn't register sc16is7x2 uart driver\n");
+		return ret;
+	}
+
+	return spi_register_driver(&sc16is7x2_spi_driver);
+}
+/* register after spi postcore initcall and before
+ * subsys initcalls that may rely on these GPIOs
+ */
+subsys_initcall(sc16is7x2_init);
+
+static void __exit sc16is7x2_exit(void)
+{
+	uart_unregister_driver(&sc16is7x2_uart_driver);
+	spi_unregister_driver(&sc16is7x2_spi_driver);
+}
+module_exit(sc16is7x2_exit);
+
+MODULE_AUTHOR("Manuel Stahl");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("SC16IS7x2 SPI based UART chip");
+MODULE_ALIAS("spi:" DRIVER_NAME);
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 64458a9..1869dcf 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -46,6 +46,9 @@
   #define PORT_AR7	18	/* Texas Instruments AR7 internal UART */
   #define PORT_MAX_8250	18	/* max port ID */

+/* SC16IS7x2 SPI UART */
+#define PORT_SC16IS7X2	19
+
   /*
    * ARM specific type numbers.  These are not currently guaranteed
    * to be implemented, and will change in the future.  These are
diff --git a/include/linux/spi/sc16is7x2.h b/include/linux/spi/sc16is7x2.h
new file mode 100755
index 0000000..931fe50
--- /dev/null
+++ b/include/linux/spi/sc16is7x2.h
@@ -0,0 +1,17 @@
+#ifndef LINUX_SPI_SC16IS752_H
+#define LINUX_SPI_SC16IS752_H
+
+#define SC16IS7X2_NR_GPIOS 8
+
+struct sc16is7x2_platform_data {
+	unsigned int	uartclk;
+	/* uart line number of the first channel */
+	unsigned	uart_base;
+	/* number assigned to the first GPIO */
+	unsigned	gpio_base;
+	char		*label;
+	/* list of GPIO names (array length = SC16IS7X2_NR_GPIOS) */
+	const char	*const *names;
+};
+
+#endif


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH] Add sc16is7x2 driver
  2010-09-03 13:11 [PATCH] Add sc16is7x2 driver Manuel Stahl
@ 2010-09-14  0:25 ` Andrew Morton
  2010-09-14 13:07   ` Manuel Stahl
  0 siblings, 1 reply; 12+ messages in thread
From: Andrew Morton @ 2010-09-14  0:25 UTC (permalink / raw)
  To: Manuel Stahl; +Cc: linux-kernel, Greg KH

On Fri, 03 Sep 2010 15:11:53 +0200
Manuel Stahl <manuel.stahl@iis.fraunhofer.de> wrote:

> This patch adds support for the sc16is7x2 chips.
> 

The patch was pretty badly wordwrapped.

>
> ...
>
> +#define WRITE_CMD(reg, ch) (REG_WRITE | (reg & 0xf) << 3 | (ch & 0x1) << 1)
> +#define READ_CMD(reg, ch)  (REG_READ  | (reg & 0xf) << 3 | (ch & 0x1) << 1)

It would be better to implement these as lower-case-named inlined C
functions.

> +
> +/* 16bit SPI command to read or write a register */
> +struct sc16is7x2_spi_reg {
> +	u8 cmd;
> +	u8 value;
> +} __attribute__ ((packed));

We have a __packed helper macro for this.

> +struct sc16is7x2_chip;
> +
> +/*
> + * Some registers must be read back to modify.
> + * To save time we cache them here in memory

That implies that the caches have some locking (the mutex, perhaps?). 
Documenting that here would be appropriate.

>
> ...
>
> +/* ******************************** SPI 
> ********************************* */
> +
> +
> +/*
> + * Reserve memory for command sequence
> + * @param cnt number of commands

This "@param" stuff is some form of markup which the kernel does not
use.  Please use standard kerneldoc markup throughout the driver, or
just plain old English-language comments.

> + */
> +static inline struct sc16is7x2_spi_reg *
> +sc16is7x2_alloc_spi_cmds(unsigned cnt)
> +{
> +	return kzalloc(sizeof(struct sc16is7x2_spi_reg)*cnt, GFP_KERNEL);

Use kcalloc() here.

> +}
> +
>
> ...
>
> +/*
> + * sc16is7x2_write_async - Write a new register content (async)
> + */
> +static inline int sc16is7x2_write_async(struct spi_device *spi, u8 reg, 
> u8 ch,
> +		u8 value)
> +{
> +	struct sc16is7x2_spi_reg *cmd = sc16is7x2_alloc_spi_cmds(1);
> +	if (!cmd)
> +		return -ENOMEM;
> +	sc16is7x2_add_write_cmd(cmd, reg, ch, value);
> +	return sc16is7x2_spi_async(spi, cmd, 1);
> +}

It's comventional to place a blank line between end-of-definitions and
start-of-code.  The driver has many instances of this.

> +/*
> + * sc16is7x2_write - Write a new register content (sync)
> + */
> +static int sc16is7x2_write(struct spi_device *spi, u8 reg, u8 ch, u8 val)
> +{
> +	u16 word = REG_WRITE | (reg & 0xf) << 3 | (ch & 0x3) << 1 | val << 8;
> +	return spi_write(spi, (const u8 *)&word, sizeof(word));
> +}

There's another.

> +/**
> + * sc16is7x2_read - Read back register content
> + * @spi: The SPI device
> + * @reg: Register offset
> + *
> + * Returns positive 8 bit value from device if successful or a
> + * negative value on error
> + */

ah-hah.  That's a correct kerneldoc comment.

> +static int sc16is7x2_read(struct spi_device *spi, unsigned reg, 
> unsigned ch)
> +{
> +	u8 cmd = REG_READ | (reg & 0xf) << 3 | (ch & 0x3) << 1;
> +	return spi_w8r8(spi, cmd);
> +}
> +
> +/* ******************************** UART 
> ********************************* */
> +
> +/* Uart divisor latch write */
> +static inline void sc16is7x2_add_dl_write_cmd(struct sc16is7x2_spi_reg 
> *cmd,
> +		u8 ch, int value)
> +{
> +	sc16is7x2_add_write_cmd(&cmd[0], UART_DLL, ch, value & 0xff);
> +	sc16is7x2_add_write_cmd(&cmd[1], UART_DLM, ch, value >> 8 & 0xff);
> +}

Probably the driver doesn't need any explicit "inline" usage at all -
modern gcc's work all that out for themselves.  And if gcc disagreed
with your inline directive, it will just ignore it.

> +static unsigned int sc16is7x2_tx_empty(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +	unsigned lsr;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	mutex_lock(&chan->lock);
> +	lsr = chan->lsr;
> +	mutex_unlock(&chan->lock);

It's strange to put locking around a single atomic read.  What are we
trying to do here?

> +	return lsr & UART_LSR_TEMT ? TIOCSER_TEMT : 0;
> +}
> +
>
> ...
>
> +static void sc16is7x2_break_ctl(struct uart_port *port, int break_state)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +	unsigned ch = port->line & 0x01;
> +	unsigned long flags;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	spin_lock_irqsave(&chan->uart.lock, flags);
> +	if (break_state == -1)
> +		chan->lcr |= UART_LCR_SBC;
> +	else
> +		chan->lcr &= ~UART_LCR_SBC;
> +	spin_unlock_irqrestore(&chan->uart.lock, flags);

hm.  Above we use the mutex to protect char->lcr but here we're using a
spinlock.

> +	sc16is7x2_write_async(ts->spi, UART_LCR, ch, chan->lcr);
> +}
> +
>
> ...
>
> +#define MIN(a, b) ((a < b) ? (a) : (b))

Nope.  Use the min() from include/linux/kernel.h.

>
> ...
>
> +static irqreturn_t sc16is7x2_interrupt(int irq, void *dev_id)
> +{
> +	struct sc16is7x2_chip *ts = dev_id;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	if (!ts->force_end_work && !work_pending(&ts->work) &&
> +	    !freezing(current) && !ts->suspending)
> +		queue_work(ts->workqueue, &ts->work);
> +
> +	return IRQ_HANDLED;
> +}

The kernel has infrastructure for "threaded irqs" nowadays.  What this
driver is doing basically reimplements that concept.  Did you consider
using threaded IRQs directly?

>
> ...
>


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] Add sc16is7x2 driver
  2010-09-14  0:25 ` Andrew Morton
@ 2010-09-14 13:07   ` Manuel Stahl
  2010-09-14 13:27     ` Jonathan Corbet
  2010-09-21 20:42     ` Greg KH
  0 siblings, 2 replies; 12+ messages in thread
From: Manuel Stahl @ 2010-09-14 13:07 UTC (permalink / raw)
  To: Andrew Morton; +Cc: linux-kernel, Greg KH, linux-serial

[-- Attachment #1: Type: text/plain, Size: 2762 bytes --]

Hi Andrew,

thank you for reviewing this driver. A cleaned up patch is attached.

Am 14.09.2010 02:25, schrieb Andrew Morton:
> On Fri, 03 Sep 2010 15:11:53 +0200
> Manuel Stahl<manuel.stahl@iis.fraunhofer.de>  wrote:
>
>> This patch adds support for the sc16is7x2 chips.
>>
>
> The patch was pretty badly wordwrapped.

Can you point out a few such places?


>> +static unsigned int sc16is7x2_tx_empty(struct uart_port *port)
>> +{
>> +	struct sc16is7x2_channel *chan =
>> +			container_of(port, struct sc16is7x2_channel, uart);
>> +	struct sc16is7x2_chip *ts = chan->chip;
>> +	unsigned lsr;
>> +
>> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
>> +
>> +	mutex_lock(&chan->lock);
>> +	lsr = chan->lsr;
>> +	mutex_unlock(&chan->lock);
>
> It's strange to put locking around a single atomic read.  What are we
> trying to do here?
>
>> +	return lsr&  UART_LSR_TEMT ? TIOCSER_TEMT : 0;
>> +}
>> +
>>
>> ...
>>
>> +static void sc16is7x2_break_ctl(struct uart_port *port, int break_state)
>> +{
>> +	struct sc16is7x2_channel *chan =
>> +			container_of(port, struct sc16is7x2_channel, uart);
>> +	struct sc16is7x2_chip *ts = chan->chip;
>> +	unsigned ch = port->line&  0x01;
>> +	unsigned long flags;
>> +
>> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
>> +
>> +	spin_lock_irqsave(&chan->uart.lock, flags);
>> +	if (break_state == -1)
>> +		chan->lcr |= UART_LCR_SBC;
>> +	else
>> +		chan->lcr&= ~UART_LCR_SBC;
>> +	spin_unlock_irqrestore(&chan->uart.lock, flags);
>
> hm.  Above we use the mutex to protect char->lcr but here we're using a
> spinlock.

I think in the break_ctl function it's not possible to use mutexes. What 
would be the right way to protect the register cache?

>>
>> ...
>>
>> +static irqreturn_t sc16is7x2_interrupt(int irq, void *dev_id)
>> +{
>> +	struct sc16is7x2_chip *ts = dev_id;
>> +
>> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
>> +
>> +	if (!ts->force_end_work&&  !work_pending(&ts->work)&&
>> +	    !freezing(current)&&  !ts->suspending)
>> +		queue_work(ts->workqueue,&ts->work);
>> +
>> +	return IRQ_HANDLED;
>> +}
>
> The kernel has infrastructure for "threaded irqs" nowadays.  What this
> driver is doing basically reimplements that concept.  Did you consider
> using threaded IRQs directly?

Ah, I didn't know that. The irq stuff is based on the max3100 driver. 
Where can I find some example how to use the new threaded irqs?

Regards,
-- 
Dipl.-Inf. Manuel Stahl
Fraunhofer-Institut für Integrierte Schaltungen IIS
- Leistungsoptimierte Systeme -
Nordostpark 93                Telefon  +49 (0)911/58061-6419
90411 Nürnberg                Fax      +49 (0)911/58061-6398
http://www.iis.fraunhofer.de  manuel.stahl@iis.fraunhofer.de

[-- Attachment #2: add_sc16is7x2_driver.patch --]
[-- Type: text/plain, Size: 39616 bytes --]

This patch adds support for the sc16is7x2 chips.

Signed-off-by: Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
---
 drivers/serial/Kconfig        |    9 +
 drivers/serial/Makefile       |    1 +
 drivers/serial/sc16is7x2.c    | 1375 +++++++++++++++++++++++++++++++++++++++++
 include/linux/serial_core.h   |    3 +
 include/linux/spi/sc16is7x2.h |   17 +
 5 files changed, 1405 insertions(+), 0 deletions(-)

diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 12900f7..98fc0dd 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -269,6 +269,15 @@ config SERIAL_8250_RM9K
 
 comment "Non-8250 serial port support"
 
+config SERIAL_SC16IS7X2
+	tristate "SC16IS7x2 chips"
+	depends on SPI_MASTER && GPIOLIB
+	select SERIAL_CORE
+	help
+	  Selecting this option will add support for SC16IS7x2 SPI UARTs.
+	  The GPIOs are exported via gpiolib interface.
+	  If unsure, say N.
+
 config SERIAL_AMBA_PL010
 	tristate "ARM AMBA PL010 serial port support"
 	depends on ARM_AMBA && (BROKEN || !ARCH_VERSATILE)
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index 1ca4fd5..4c6a297 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -28,6 +28,7 @@ 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_MCA) += 8250_mca.o
+obj-$(CONFIG_SERIAL_SC16IS7X2)	+= sc16is7x2.o
 obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
 obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
 obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o
diff --git a/drivers/serial/sc16is7x2.c b/drivers/serial/sc16is7x2.c
new file mode 100644
index 0000000..a9bcd03
--- /dev/null
+++ b/drivers/serial/sc16is7x2.c
@@ -0,0 +1,1375 @@
+/**
+ * drivers/serial/sc16is7x2.c
+ *
+ * Copyright (C) 2009 Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
+ *
+ * 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.
+ *
+ * The SC16IS7x2 device is a SPI driven dual UART with GPIOs.
+ *
+ * The driver exports two uarts and a gpiochip interface.
+ */
+
+/* #define DEBUG */
+
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/spi/spi.h>
+#include <linux/freezer.h>
+#include <linux/spi/sc16is7x2.h>
+#include <linux/serial_core.h>
+#include <linux/serial_reg.h>
+#include <linux/gpio.h>
+
+#define SC16IS7X2_MAJOR		204
+#define SC16IS7X2_MINOR		209
+#define MAX_SC16IS7X2		8
+#define FIFO_SIZE		64
+
+#define DRIVER_NAME		"sc16is7x2"
+#define TYPE_NAME		"SC16IS7x2"
+
+
+
+#define REG_READ	0x80
+#define REG_WRITE	0x00
+
+/* Special registers */
+#define REG_TXLVL	0x08	/* Transmitter FIFO Level register */
+#define REG_RXLVL	0x09	/* Receiver FIFO Level register */
+#define REG_IOD		0x0A	/* IO Direction register */
+#define REG_IOS		0x0B	/* IO State register */
+#define REG_IOI		0x0C	/* IO Interrupt Enable register */
+#define REG_IOC		0x0E	/* IO Control register */
+
+#define IOC_SRESET	0x08    /* Software reset */
+#define IOC_GPIO30	0x04    /* GPIO 3:0 unset: as IO, set: as modem pins */
+#define IOC_GPIO74	0x02    /* GPIO 7:4 unset: as IO, set: as modem pins */
+#define IOC_IOLATCH	0x01    /* Unset: input unlatched, set: input latched */
+
+/* Redefine some MCR bits */
+#ifdef UART_MCR_TCRTLR
+#undef UART_MCR_TCRTLR
+#endif
+#define UART_MCR_TCRTLR		0x04
+#define UART_MCR_IRDA		0x40
+
+/* 16bit SPI command to read or write a register */
+struct sc16is7x2_spi_reg {
+	u8 cmd;
+	u8 value;
+} __packed;
+
+struct sc16is7x2_chip;
+
+/*
+ * Some registers must be read back to modify.
+ * To save time we cache them here in memory.
+ * The @lock mutex is there to protect them.
+ */
+struct sc16is7x2_channel {
+	struct sc16is7x2_chip	*chip;	/* back link */
+	struct mutex		lock;
+	struct uart_port	uart;
+	struct spi_transfer fifo_rx;
+	struct spi_transfer fifo_tx[3];
+	u8		iir;
+	u8		lsr;
+	u8		msr;
+	u8		ier;		/* cache for IER register */
+	u8		fcr;		/* cache for FCR register */
+	u8		lcr;		/* cache for LCR register */
+	u8		mcr;		/* cache for MCR register */
+	u8		*rx_buf;
+	u8		write_fifo_cmd;
+	u8		read_fifo_cmd;
+	bool		active;
+};
+
+struct sc16is7x2_chip {
+	struct spi_device *spi;
+	struct gpio_chip gpio;
+	struct mutex	 lock;
+	struct sc16is7x2_channel channel[2];
+
+	/* for handling irqs: need workqueue since we do spi_sync */
+	struct workqueue_struct *workqueue;
+	struct work_struct work;
+	/* set to 1 to make the workhandler exit as soon as possible */
+	int force_end_work;
+	/* need to know we are suspending to avoid deadlock on workqueue */
+	int suspending;
+
+	struct spi_message fifo_message;
+
+#define UART_BUG_TXEN	BIT(1)	/* UART has buggy TX IIR status */
+#define UART_BUG_NOMSR	BIT(2)	/* UART has buggy MSR status bits (Au1x00) */
+#define UART_BUG_THRE	BIT(3)	/* UART has buggy THRE reassertion */
+	u16		bugs;		/* port bugs */
+
+#define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS
+	u8		lsr_saved_flags;
+#define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA
+	u8		msr_saved_flags;
+	u8		io_dir;		/* cache for IODir register */
+	u8		io_state;	/* cache for IOState register */
+	u8		io_gpio;	/* PIN is GPIO */
+	u8		io_control;	/* cache for IOControl register */
+};
+
+/* ******************************** SPI ********************************* */
+
+static u8 write_cmd(u8 reg, u8 ch)
+{
+	return REG_WRITE | (reg & 0xf) << 3 | (ch & 0x1) << 1;
+}
+
+static u8 read_cmd(u8 reg, u8 ch)
+{
+	return REG_READ  | (reg & 0xf) << 3 | (ch & 0x1) << 1;
+}
+
+/*
+ * Reserve memory for command sequence
+ * @cnt number of commands
+ */
+static struct sc16is7x2_spi_reg *
+sc16is7x2_alloc_spi_cmds(unsigned cnt)
+{
+	return kcalloc(cnt, sizeof(struct sc16is7x2_spi_reg), GFP_KERNEL);
+}
+
+/*
+ * sc16is7x2_add_write_cmd - Add write command to sequence
+ */
+static void sc16is7x2_add_write_cmd(struct sc16is7x2_spi_reg *cmd,
+		u8 reg, u8 ch, u8 value)
+{
+	cmd->cmd = write_cmd(reg, ch);
+	cmd->value = value;
+}
+
+/*
+ * sc16is7x2_add_read_cmd - Add read command to sequence
+ */
+static void sc16is7x2_add_read_cmd(struct sc16is7x2_spi_reg *cmd,
+		u8 reg, u8 ch)
+{
+	cmd->cmd = read_cmd(reg, ch);
+	cmd->value = 0;
+}
+
+/*
+ * sc16is7x2_complete - Completion handler for async SPI transfers
+ */
+static void sc16is7x2_complete(void *context)
+{
+	struct spi_message *m = context;
+	u8 *tx_chain = m->state;
+
+	kfree(tx_chain);
+	kfree(m);
+}
+
+/*
+ * sc16is7x2_spi_async - Send command sequence
+ */
+static int sc16is7x2_spi_async(struct spi_device *spi,
+		struct sc16is7x2_spi_reg *cmds, unsigned len)
+{
+	struct spi_transfer *t;
+	struct spi_message *m;
+
+	m = spi_message_alloc(len, GFP_KERNEL);
+	if (!m)
+		return -ENOMEM;
+
+	m->complete = sc16is7x2_complete;
+	m->context = m;
+	m->state = cmds;
+	list_for_each_entry(t, &m->transfers, transfer_list) {
+		t->tx_buf = (u8 *)cmds;
+		t->len = 2;
+		t->cs_change = true;
+		cmds++;
+	}
+
+	return spi_async(spi, m);
+}
+
+/*
+ * sc16is7x2_write_async - Write a new register content (async)
+ */
+static int sc16is7x2_write_async(struct spi_device *spi, u8 reg, u8 ch,
+		u8 value)
+{
+	struct sc16is7x2_spi_reg *cmd = sc16is7x2_alloc_spi_cmds(1);
+
+	if (!cmd)
+		return -ENOMEM;
+	sc16is7x2_add_write_cmd(cmd, reg, ch, value);
+	return sc16is7x2_spi_async(spi, cmd, 1);
+}
+
+/*
+ * sc16is7x2_write - Write a new register content (sync)
+ */
+static int sc16is7x2_write(struct spi_device *spi, u8 reg, u8 ch, u8 val)
+{
+	struct sc16is7x2_spi_reg out;
+
+	out.cmd = write_cmd(reg, ch);
+	out.value = val;
+	return spi_write(spi, (const u8 *)&out, sizeof(out));
+}
+
+/**
+ * sc16is7x2_read - Read back register content
+ * @spi: The SPI device
+ * @reg: Register offset
+ * @ch:  Channel (0 or 1)
+ *
+ * Returns positive 8 bit value from device if successful or a
+ * negative value on error
+ */
+static int sc16is7x2_read(struct spi_device *spi, unsigned reg, unsigned ch)
+{
+	return spi_w8r8(spi, read_cmd(reg, ch));
+}
+
+/* ******************************** UART ********************************* */
+
+/* Uart divisor latch write */
+static void sc16is7x2_add_dl_write_cmd(struct sc16is7x2_spi_reg *cmd,
+		u8 ch, int value)
+{
+	sc16is7x2_add_write_cmd(&cmd[0], UART_DLL, ch, value & 0xff);
+	sc16is7x2_add_write_cmd(&cmd[1], UART_DLM, ch, value >> 8 & 0xff);
+}
+
+static unsigned int sc16is7x2_tx_empty(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned lsr;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	lsr = chan->lsr;
+	return lsr & UART_LSR_TEMT ? TIOCSER_TEMT : 0;
+}
+
+static unsigned int sc16is7x2_get_mctrl(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned int status;
+	unsigned int ret;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	status = chan->msr;
+
+	ret = 0;
+	if (status & UART_MSR_DCD)
+		ret |= TIOCM_CAR;
+	if (status & UART_MSR_RI)
+		ret |= TIOCM_RNG;
+	if (status & UART_MSR_DSR)
+		ret |= TIOCM_DSR;
+	if (status & UART_MSR_CTS)
+		ret |= TIOCM_CTS;
+	return ret;
+}
+
+static unsigned int __set_mctrl(unsigned int mctrl)
+{
+	unsigned char mcr = 0;
+
+	if (mctrl & TIOCM_RTS)
+		mcr |= UART_MCR_RTS;
+	if (mctrl & TIOCM_DTR)
+		mcr |= UART_MCR_DTR;
+	if (mctrl & TIOCM_LOOP)
+		mcr |= UART_MCR_LOOP;
+
+	return mcr;
+}
+
+static void sc16is7x2_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	sc16is7x2_write_async(ts->spi, UART_MCR, ch, __set_mctrl(mctrl));
+}
+
+static void __stop_tx(struct sc16is7x2_channel *chan)
+{
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = chan->uart.line & 0x01;
+
+	if (chan->ier & UART_IER_THRI) {
+		chan->ier &= ~UART_IER_THRI;
+		sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
+	}
+}
+
+static void sc16is7x2_stop_tx(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	__stop_tx(chan);
+}
+
+static void sc16is7x2_start_tx(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	if (!(chan->ier & UART_IER_THRI)) {
+		chan->ier |= UART_IER_THRI;
+		sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
+	}
+}
+
+static void sc16is7x2_stop_rx(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	chan->ier &= ~UART_IER_RLSI;
+	chan->uart.read_status_mask &= ~UART_LSR_DR;
+	sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
+}
+
+static void sc16is7x2_enable_ms(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	chan->ier |= UART_IER_MSI;
+	sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
+}
+
+static void sc16is7x2_break_ctl(struct uart_port *port, int break_state)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+	unsigned long flags;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	spin_lock_irqsave(&chan->uart.lock, flags);
+	if (break_state == -1)
+		chan->lcr |= UART_LCR_SBC;
+	else
+		chan->lcr &= ~UART_LCR_SBC;
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	sc16is7x2_write_async(ts->spi, UART_LCR, ch, chan->lcr);
+}
+
+static int sc16is7x2_startup(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+	struct sc16is7x2_spi_reg *cmds, *cmd;
+	unsigned long flags;
+
+	dev_dbg(&ts->spi->dev, "%s (line %d)\n", __func__, port->line);
+
+	spin_lock_irqsave(&chan->uart.lock, flags);
+	chan->lcr = UART_LCR_WLEN8;
+	chan->mcr = __set_mctrl(chan->uart.mctrl);
+	chan->fcr = 0;
+	chan->ier = UART_IER_RLSI | UART_IER_RDI;
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	cmds = sc16is7x2_alloc_spi_cmds(8);
+	if (!cmds)
+		return -ENOMEM;
+
+	cmd = cmds;
+	/* Clear the interrupt registers. */
+	sc16is7x2_add_write_cmd(cmd, UART_IER, ch, 0);
+	sc16is7x2_add_read_cmd(++cmd, UART_IIR, ch);
+	sc16is7x2_add_read_cmd(++cmd, UART_LSR, ch);
+	sc16is7x2_add_read_cmd(++cmd, UART_MSR, ch);
+
+	sc16is7x2_add_write_cmd(++cmd, UART_FCR, ch, UART_FCR_ENABLE_FIFO |
+		       UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+	sc16is7x2_add_write_cmd(++cmd, UART_FCR, ch, chan->fcr);
+	/* Now, initialize the UART */
+	sc16is7x2_add_write_cmd(++cmd, UART_LCR, ch, chan->lcr);
+	sc16is7x2_add_write_cmd(++cmd, UART_MCR, ch, chan->mcr);
+
+	sc16is7x2_spi_async(ts->spi, cmds, 8);
+
+	chan->active = true;
+	return 0;
+}
+
+static void sc16is7x2_shutdown(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned long flags;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	BUG_ON(!chan);
+	BUG_ON(!ts);
+
+	if (ts->suspending)
+		return;
+
+	/* Disable interrupts from this port */
+	chan->ier = 0;
+	chan->active = false;
+	sc16is7x2_write(ts->spi, UART_IER, ch, chan->ier);
+
+	/* Wait for worker of this channel to finish */
+	mutex_lock(&chan->lock);
+
+	spin_lock_irqsave(&chan->uart.lock, flags);
+	chan->mcr = __set_mctrl(chan->uart.mctrl);
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	/* Disable break condition and FIFOs */
+	chan->lcr &= ~UART_LCR_SBC;
+
+	sc16is7x2_write(ts->spi, UART_MCR, ch, chan->mcr);
+	sc16is7x2_write(ts->spi, UART_LCR, ch, chan->lcr);
+
+	mutex_unlock(&chan->lock);
+}
+
+static void
+sc16is7x2_set_termios(struct uart_port *port, struct ktermios *termios,
+		       struct ktermios *old)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	struct sc16is7x2_spi_reg *cmds;
+	unsigned ch = port->line & 0x01;
+	unsigned long flags;
+	unsigned int baud, quot;
+	u8 ier, mcr, lcr, fcr = 0;
+	u8 efr = UART_EFR_ECB;
+
+	/* set word length */
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:
+		lcr = UART_LCR_WLEN5;
+		break;
+	case CS6:
+		lcr = UART_LCR_WLEN6;
+		break;
+	case CS7:
+		lcr = UART_LCR_WLEN7;
+		break;
+	default:
+	case CS8:
+		lcr = UART_LCR_WLEN8;
+		break;
+	}
+
+	if (termios->c_cflag & CSTOPB)
+		lcr |= UART_LCR_STOP;
+	if (termios->c_cflag & PARENB)
+		lcr |= UART_LCR_PARITY;
+	if (!(termios->c_cflag & PARODD))
+		lcr |= UART_LCR_EPAR;
+#ifdef CMSPAR
+	if (termios->c_cflag & CMSPAR)
+		lcr |= UART_LCR_SPAR;
+#endif
+
+	/* Ask the core to calculate the divisor for us. */
+	baud = uart_get_baud_rate(port, termios, old,
+				  port->uartclk / 16 / 0xffff,
+				  port->uartclk / 16);
+	quot = uart_get_divisor(port, baud);
+
+	dev_dbg(&ts->spi->dev, "%s (baud %u)\n", __func__, baud);
+
+
+	/* configure the fifo */
+	if (baud < 2400)
+		fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1;
+	else
+		fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_01;
+
+	/*
+	 * MCR-based auto flow control.  When AFE is enabled, RTS will be
+	 * deasserted when the receive FIFO contains more characters than
+	 * the trigger, or the MCR RTS bit is cleared.  In the case where
+	 * the remote UART is not using CTS auto flow control, we must
+	 * have sufficient FIFO entries for the latency of the remote
+	 * UART to respond.  IOW, at least 32 bytes of FIFO.
+	 */
+	chan->mcr &= ~UART_MCR_AFE;
+	if (termios->c_cflag & CRTSCTS)
+		chan->mcr |= UART_MCR_AFE;
+
+	/*
+	 * Ok, we're now changing the port state.  Do it with
+	 * interrupts disabled.
+	 */
+	spin_lock_irqsave(&chan->uart.lock, flags);
+
+	/* we are sending char from a workqueue so enable */
+	chan->uart.state->port.tty->low_latency = 1;
+
+	/* Update the per-port timeout. */
+	uart_update_timeout(port, termios->c_cflag, baud);
+
+	chan->uart.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+	if (termios->c_iflag & INPCK)
+		chan->uart.read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+	if (termios->c_iflag & (BRKINT | PARMRK))
+		chan->uart.read_status_mask |= UART_LSR_BI;
+
+	/* Characters to ignore */
+	chan->uart.ignore_status_mask = 0;
+	if (termios->c_iflag & IGNPAR)
+		chan->uart.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+	if (termios->c_iflag & IGNBRK) {
+		chan->uart.ignore_status_mask |= UART_LSR_BI;
+		/*
+		 * If we're ignoring parity and break indicators,
+		 * ignore overruns too (for real raw support).
+		 */
+		if (termios->c_iflag & IGNPAR)
+			chan->uart.ignore_status_mask |= UART_LSR_OE;
+	}
+
+	/* ignore all characters if CREAD is not set */
+	if ((termios->c_cflag & CREAD) == 0)
+		chan->uart.ignore_status_mask |= UART_LSR_DR;
+
+	/* CTS flow control flag and modem status interrupts */
+	chan->ier &= ~UART_IER_MSI;
+	if (UART_ENABLE_MS(&chan->uart, termios->c_cflag))
+		chan->ier |= UART_IER_MSI;
+
+	if (termios->c_cflag & CRTSCTS)
+		efr |= UART_EFR_CTS | UART_EFR_RTS;
+
+	mcr = __set_mctrl(chan->uart.mctrl);
+	ier = chan->ier;
+	chan->lcr = lcr;				/* Save LCR */
+	chan->fcr = fcr;				/* Save FCR */
+	chan->mcr = mcr;				/* Save MCR */
+
+	fcr |= UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT;
+
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	/* build a compound spi message to set all registers */
+	cmds = sc16is7x2_alloc_spi_cmds(9);
+	if (!cmds)
+		return;
+
+	/* set DLAB */
+	sc16is7x2_add_write_cmd(&cmds[0], UART_LCR, ch, UART_LCR_DLAB);
+	/* set divisor, must be set before UART_EFR_ECB */
+	sc16is7x2_add_dl_write_cmd(&cmds[1], ch, quot);
+	sc16is7x2_add_write_cmd(&cmds[3], UART_LCR, ch, 0xBF);	/* access EFR */
+	sc16is7x2_add_write_cmd(&cmds[4], UART_EFR, ch, efr);
+	sc16is7x2_add_write_cmd(&cmds[5], UART_LCR, ch, lcr);	/* reset DLAB */
+	sc16is7x2_add_write_cmd(&cmds[6], UART_FCR, ch, fcr);
+	sc16is7x2_add_write_cmd(&cmds[7], UART_MCR, ch, mcr);
+	sc16is7x2_add_write_cmd(&cmds[8], UART_IER, ch, ier);
+
+	sc16is7x2_spi_async(ts->spi, cmds, 9);
+
+	/* Don't rewrite B0 */
+	if (tty_termios_baud_rate(termios))
+		tty_termios_encode_baud_rate(termios, baud, baud);
+}
+
+static const char *
+sc16is7x2_type(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	return TYPE_NAME;
+}
+
+static void sc16is7x2_release_port(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	ts->force_end_work = 1;
+}
+
+static int sc16is7x2_request_port(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	return 0;
+}
+
+static void sc16is7x2_config_port(struct uart_port *port, int flags)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	if (flags & UART_CONFIG_TYPE)
+		chan->uart.type = PORT_SC16IS7X2;
+}
+
+static int
+sc16is7x2_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	if (ser->irq < 0 || ser->baud_base < 9600 ||
+			ser->type != PORT_SC16IS7X2)
+		return -EINVAL;
+	return 0;
+}
+
+static struct uart_ops sc16is7x2_uart_ops = {
+	.tx_empty	= sc16is7x2_tx_empty,
+	.set_mctrl	= sc16is7x2_set_mctrl,
+	.get_mctrl	= sc16is7x2_get_mctrl,
+	.stop_tx        = sc16is7x2_stop_tx,
+	.start_tx	= sc16is7x2_start_tx,
+	.stop_rx	= sc16is7x2_stop_rx,
+	.enable_ms      = sc16is7x2_enable_ms,
+	.break_ctl      = sc16is7x2_break_ctl,
+	.startup	= sc16is7x2_startup,
+	.shutdown	= sc16is7x2_shutdown,
+	.set_termios	= sc16is7x2_set_termios,
+	.type		= sc16is7x2_type,
+	.release_port   = sc16is7x2_release_port,
+	.request_port   = sc16is7x2_request_port,
+	.config_port	= sc16is7x2_config_port,
+	.verify_port	= sc16is7x2_verify_port,
+};
+
+
+/* ******************************** GPIO ********************************* */
+
+static int sc16is7x2_gpio_request(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	int control = (offset < 4) ? IOC_GPIO30 : IOC_GPIO74;
+	int ret = 0;
+
+	BUG_ON(offset > 8);
+	dev_dbg(&ts->spi->dev, "%s: offset = %d\n", __func__, offset);
+
+	mutex_lock(&ts->lock);
+
+	/* GPIO 0:3 and 4:7 can only be controlled as block */
+	ts->io_gpio |= BIT(offset);
+	if (ts->io_control & control) {
+		dev_dbg(&ts->spi->dev, "activate GPIOs %s\n",
+				(offset < 4) ? "0-3" : "4-7");
+		ts->io_control &= ~control;
+		ret = sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control);
+	}
+
+	mutex_unlock(&ts->lock);
+
+	return ret;
+}
+
+static void sc16is7x2_gpio_free(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	int control = (offset < 4) ? IOC_GPIO30 : IOC_GPIO74;
+	int mask = (offset < 4) ? 0x0f : 0xf0;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	/* GPIO 0:3 and 4:7 can only be controlled as block */
+	ts->io_gpio &= ~BIT(offset);
+	dev_dbg(&ts->spi->dev, "%s: io_gpio = 0x%02X\n", __func__, ts->io_gpio);
+	if (!(ts->io_control & control) && !(ts->io_gpio & mask)) {
+		dev_dbg(&ts->spi->dev, "deactivate GPIOs %s\n",
+				(offset < 4) ? "0-3" : "4-7");
+		ts->io_control |= control;
+		sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control);
+	}
+
+	mutex_unlock(&ts->lock);
+}
+
+static int sc16is7x2_direction_input(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	unsigned io_dir;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	ts->io_dir &= ~BIT(offset);
+	io_dir = ts->io_dir;
+
+	mutex_unlock(&ts->lock);
+
+	return sc16is7x2_write_async(ts->spi, REG_IOD, 0, io_dir);
+}
+
+static int sc16is7x2_direction_output(struct gpio_chip *gpio, unsigned offset,
+				    int value)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	struct sc16is7x2_spi_reg *cmds;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	if (value)
+		ts->io_state |= BIT(offset);
+	else
+		ts->io_state &= ~BIT(offset);
+
+	ts->io_dir |= BIT(offset);
+
+	cmds = sc16is7x2_alloc_spi_cmds(2);
+	if (cmds) {
+		sc16is7x2_add_write_cmd(&cmds[0], REG_IOS, 0, ts->io_state);
+		sc16is7x2_add_write_cmd(&cmds[1], REG_IOD, 0, ts->io_dir);
+	}
+
+	mutex_unlock(&ts->lock);
+
+	return sc16is7x2_spi_async(ts->spi, cmds, 2);
+}
+
+static int sc16is7x2_get(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	int level = -EINVAL;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	if (ts->io_dir & BIT(offset)) {
+		/* Output: return cached level */
+		level = (ts->io_state >> offset) & 0x01;
+	} else {
+		/* Input: read out all pins */
+		level = sc16is7x2_read(ts->spi, REG_IOS, 0);
+		if (level >= 0) {
+			ts->io_state = level;
+			level = (ts->io_state >> offset) & 0x01;
+		}
+	}
+
+	mutex_unlock(&ts->lock);
+
+	return level;
+}
+
+static void sc16is7x2_set(struct gpio_chip *gpio, unsigned offset, int value)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	unsigned io_state;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	if (value)
+		ts->io_state |= BIT(offset);
+	else
+		ts->io_state &= ~BIT(offset);
+	io_state = ts->io_state;
+
+	mutex_unlock(&ts->lock);
+
+	sc16is7x2_write_async(ts->spi, REG_IOS, 0, io_state);
+}
+
+/* ******************************** IRQ ********************************* */
+
+static void sc16is7x2_handle_fifo_rx(struct sc16is7x2_channel *chan)
+{
+	struct uart_port *uart = &chan->uart;
+	struct tty_struct *tty = uart->state->port.tty;
+	u8 *rxbuf = chan->fifo_rx.rx_buf;
+	u8 lsr = chan->lsr;
+	unsigned i, count = chan->fifo_rx.len;
+	unsigned long flags;
+	char flag = TTY_NORMAL;
+
+	spin_lock_irqsave(&uart->lock, flags);
+
+	if (unlikely(lsr & UART_LSR_BRK_ERROR_BITS)) {
+		/*
+		 * For statistics only
+		 */
+		if (lsr & UART_LSR_BI) {
+			lsr &= ~(UART_LSR_FE | UART_LSR_PE);
+			chan->uart.icount.brk++;
+			/*
+			 * We do the SysRQ and SAK checking
+			 * here because otherwise the break
+			 * may get masked by ignore_status_mask
+			 * or read_status_mask.
+			 */
+			if (uart_handle_break(&chan->uart))
+				goto ignore_char;
+		} else if (lsr & UART_LSR_PE)
+			chan->uart.icount.parity++;
+		else if (lsr & UART_LSR_FE)
+			chan->uart.icount.frame++;
+		if (lsr & UART_LSR_OE)
+			chan->uart.icount.overrun++;
+
+		/*
+		 * Mask off conditions which should be ignored.
+		 */
+		lsr &= chan->uart.read_status_mask;
+
+		if (lsr & UART_LSR_BI)
+			flag = TTY_BREAK;
+		else if (lsr & UART_LSR_PE)
+			flag = TTY_PARITY;
+		else if (lsr & UART_LSR_FE)
+			flag = TTY_FRAME;
+	}
+
+	for (i = 1; i < count; i++) {
+		uart->icount.rx++;
+
+		if (!uart_handle_sysrq_char(uart, rxbuf[i]))
+			uart_insert_char(uart, lsr, UART_LSR_OE,
+					rxbuf[i], flag);
+	}
+
+ignore_char:
+	spin_unlock_irqrestore(&uart->lock, flags);
+
+	if (count > 1)
+		tty_flip_buffer_push(tty);
+}
+
+static void sc16is7x2_handle_fifo_tx(struct sc16is7x2_channel *chan)
+{
+	struct uart_port *uart = &chan->uart;
+	struct circ_buf *xmit = &uart->state->xmit;
+	unsigned count = chan->fifo_tx[1].len + chan->fifo_tx[2].len;
+	unsigned long flags;
+
+	BUG_ON(!uart);
+	BUG_ON(!xmit);
+
+	spin_lock_irqsave(&uart->lock, flags);
+
+	uart->icount.tx += count;
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(uart);
+
+	if (uart_circ_empty(xmit))
+		__stop_tx(chan);
+
+	spin_unlock_irqrestore(&uart->lock, flags);
+}
+
+static bool sc16is7x2_msg_add_fifo_rx(struct sc16is7x2_chip *ts, unsigned ch)
+{
+	struct spi_message *m = &(ts->fifo_message);
+	struct spi_transfer *t = &(ts->channel[ch].fifo_rx);
+	int rxlvl = sc16is7x2_read(ts->spi, REG_RXLVL, ch);
+
+	if (rxlvl > 0) {
+		t->len = rxlvl + 1;
+		spi_message_add_tail(t, m);
+		return true;
+	}
+	return false;
+}
+
+static bool sc16is7x2_msg_add_fifo_tx(struct sc16is7x2_chip *ts, unsigned ch)
+{
+	struct sc16is7x2_channel * const chan = &(ts->channel[ch]);
+	struct uart_port *uart = &chan->uart;
+	struct circ_buf *xmit = &uart->state->xmit;
+	unsigned count;
+	bool split_transfer;
+	u8 txlvl;
+
+	if (chan->uart.x_char && chan->lsr & UART_LSR_THRE) {
+		dev_dbg(&ts->spi->dev, "tx: x-char\n");
+		sc16is7x2_write(ts->spi, UART_TX, ch, uart->x_char);
+		uart->icount.tx++;
+		uart->x_char = 0;
+		return false;
+	}
+	if (uart_tx_stopped(&chan->uart)) {
+		dev_dbg(&ts->spi->dev, "tx: stopped!\n");
+		sc16is7x2_stop_tx(uart);
+		return false;
+	}
+	if (uart_circ_empty(xmit)) {
+		__stop_tx(chan);
+		return false;
+	}
+
+	txlvl = sc16is7x2_read(ts->spi, REG_TXLVL, ch);
+	if (txlvl <= 0) {
+		dev_dbg(&ts->spi->dev, " fifo full\n");
+		return false;
+	}
+
+	/* number of bytes to transfer to the fifo */
+	count = min(txlvl, (u8)uart_circ_chars_pending(xmit));
+	split_transfer = (UART_XMIT_SIZE - xmit->tail) <= count;
+
+	/* add command transfer */
+	spi_message_add_tail(&(chan->fifo_tx[0]), &(ts->fifo_message));
+	/* add first fifo transfer */
+	spi_message_add_tail(&(chan->fifo_tx[1]), &(ts->fifo_message));
+
+	chan->fifo_tx[1].tx_buf = xmit->buf + xmit->tail;
+
+	if (!split_transfer) {
+		chan->fifo_tx[1].len = count;
+		chan->fifo_tx[1].cs_change = true;
+
+		chan->fifo_tx[2].len = 0;
+	} else {
+		chan->fifo_tx[1].len = (UART_XMIT_SIZE - 1) - xmit->tail;
+		chan->fifo_tx[1].cs_change = false;
+
+		chan->fifo_tx[2].tx_buf = xmit->buf;
+		chan->fifo_tx[2].cs_change = true;
+		chan->fifo_tx[2].len = count - chan->fifo_tx[1].len;
+		/* add second fifo transfer */
+		spi_message_add_tail(&(chan->fifo_tx[2]), &(ts->fifo_message));
+	}
+
+	xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
+	return true;
+}
+
+static void sc16is7x2_handle_modem(struct sc16is7x2_chip *ts, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
+	struct uart_port *uart = &chan->uart;
+
+	if (chan->msr & UART_MSR_ANY_DELTA
+			&& chan->ier & UART_IER_MSI
+			&& uart->state != NULL) {
+		if (chan->msr & UART_MSR_TERI)
+			uart->icount.rng++;
+		if (chan->msr & UART_MSR_DDSR)
+			uart->icount.dsr++;
+		if (chan->msr & UART_MSR_DDCD)
+			uart_handle_dcd_change(uart, chan->msr & UART_MSR_DCD);
+		if (chan->msr & UART_MSR_DCTS)
+			uart_handle_cts_change(uart, chan->msr & UART_MSR_CTS);
+
+		wake_up_interruptible(&uart->state->port.delta_msr_wait);
+	}
+}
+
+static void sc16is7x2_read_status(struct sc16is7x2_chip *ts, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
+
+	chan->iir = sc16is7x2_read(ts->spi, UART_IIR, ch);
+	chan->msr = sc16is7x2_read(ts->spi, UART_MSR, ch);
+	chan->lsr = sc16is7x2_read(ts->spi, UART_LSR, ch);
+}
+
+static bool sc16is7x2_handle_channel(struct sc16is7x2_chip *ts, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
+	struct spi_message *m = &(ts->fifo_message);
+	bool rx, tx;
+
+	dev_dbg(&ts->spi->dev, "%s (%i)\n", __func__, ch);
+
+	sc16is7x2_read_status(ts, ch);
+	sc16is7x2_handle_modem(ts, ch);
+
+	spi_message_init(m);
+	rx = sc16is7x2_msg_add_fifo_rx(ts, ch);
+	tx = sc16is7x2_msg_add_fifo_tx(ts, ch);
+
+	if (rx || tx)
+		spi_sync(ts->spi, m);
+
+	if (rx)
+		sc16is7x2_handle_fifo_rx(chan);
+	if (tx)
+		sc16is7x2_handle_fifo_tx(chan);
+
+	dev_dbg(&ts->spi->dev, "%s finished (iir = 0x%02x)\n",
+			__func__, chan->iir);
+
+	return (chan->iir & UART_IIR_NO_INT) == 0x00;
+}
+
+static void sc16is7x2_work(struct work_struct *w)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(w, struct sc16is7x2_chip, work);
+	unsigned pending = 0;
+	unsigned ch = 0;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	BUG_ON(!w);
+	BUG_ON(!ts);
+
+
+	if (ts->force_end_work) {
+		dev_dbg(&ts->spi->dev, "%s: force end!\n", __func__);
+		return;
+	}
+
+	if (ts->channel[0].active)
+		pending |= BIT(0);
+	if (ts->channel[1].active)
+		pending |= BIT(1);
+
+	do {
+		mutex_lock(&(ts->channel[ch].lock));
+		if (pending & BIT(ch) && ts->channel[ch].active) {
+			if (!sc16is7x2_handle_channel(ts, ch))
+				pending &= ~BIT(ch);
+		}
+		mutex_unlock(&(ts->channel[ch].lock));
+		ch ^= 1;	/* switch channel */
+	} while (!ts->force_end_work && !freezing(current) && pending);
+
+	dev_dbg(&ts->spi->dev, "%s finished\n", __func__);
+}
+
+static irqreturn_t sc16is7x2_interrupt(int irq, void *dev_id)
+{
+	struct sc16is7x2_chip *ts = dev_id;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	if (!ts->force_end_work && !work_pending(&ts->work) &&
+	    !freezing(current) && !ts->suspending)
+		queue_work(ts->workqueue, &ts->work);
+
+	return IRQ_HANDLED;
+}
+
+/* ******************************** INIT ********************************* */
+
+static struct uart_driver sc16is7x2_uart_driver;
+
+static int sc16is7x2_register_gpio(struct sc16is7x2_chip *ts,
+		struct sc16is7x2_platform_data *pdata)
+{
+	struct sc16is7x2_spi_reg *cmds;
+
+	ts->gpio.label = (pdata->label) ? pdata->label : DRIVER_NAME;
+	ts->gpio.request	= sc16is7x2_gpio_request;
+	ts->gpio.free		= sc16is7x2_gpio_free;
+	ts->gpio.get		= sc16is7x2_get;
+	ts->gpio.set		= sc16is7x2_set;
+	ts->gpio.direction_input = sc16is7x2_direction_input;
+	ts->gpio.direction_output = sc16is7x2_direction_output;
+
+	ts->gpio.base = pdata->gpio_base;
+	ts->gpio.names = pdata->names;
+	ts->gpio.ngpio = SC16IS7X2_NR_GPIOS;
+	ts->gpio.can_sleep = 1;
+	ts->gpio.dev = &ts->spi->dev;
+	ts->gpio.owner = THIS_MODULE;
+
+	/* disable all GPIOs, enable on request */
+	ts->io_dir = 0x0f;
+	ts->io_state = 0;
+	ts->io_gpio = 0;
+	ts->io_control = IOC_GPIO30 | IOC_GPIO74;
+
+	cmds = sc16is7x2_alloc_spi_cmds(4);
+	if (!cmds)
+		return -ENOMEM;
+
+	sc16is7x2_add_write_cmd(&cmds[0], REG_IOI, 0, 0);
+	sc16is7x2_add_write_cmd(&cmds[1], REG_IOC, 0, ts->io_control);
+	sc16is7x2_add_write_cmd(&cmds[2], REG_IOS, 0, ts->io_state);
+	sc16is7x2_add_write_cmd(&cmds[3], REG_IOD, 0, ts->io_dir);
+	sc16is7x2_spi_async(ts->spi, cmds, 4);
+
+	return gpiochip_add(&ts->gpio);
+}
+
+static int sc16is7x2_register_uart_port(struct sc16is7x2_chip *ts,
+		struct sc16is7x2_platform_data *pdata, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
+	struct uart_port *uart = &chan->uart;
+
+	mutex_init(&chan->lock);
+	chan->active = false;	/* will be set in startup */
+	chan->chip = ts;
+
+	chan->rx_buf = kzalloc(FIFO_SIZE+1, GFP_KERNEL);
+		if (chan->rx_buf == NULL)
+			return -ENOMEM;
+
+	chan->read_fifo_cmd = read_cmd(UART_RX, ch);
+	chan->fifo_rx.cs_change = true;
+	chan->fifo_rx.tx_buf = &(chan->read_fifo_cmd);
+	chan->fifo_rx.rx_buf = chan->rx_buf;
+
+
+	chan->write_fifo_cmd = write_cmd(UART_TX, ch);
+	chan->fifo_tx[0].tx_buf = &(chan->write_fifo_cmd);
+	chan->fifo_tx[0].rx_buf = NULL;
+	chan->fifo_tx[0].len = 1;
+	chan->fifo_tx[0].cs_change = false;
+	chan->fifo_tx[1].rx_buf = NULL;
+	chan->fifo_tx[2].rx_buf = NULL;
+
+	uart->irq = ts->spi->irq;
+	uart->uartclk = pdata->uartclk;
+	uart->fifosize = FIFO_SIZE;
+	uart->ops = &sc16is7x2_uart_ops;
+	uart->flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF;
+	uart->line = pdata->uart_base + ch;
+	uart->type = PORT_SC16IS7X2;
+	uart->dev = &ts->spi->dev;
+
+	return uart_add_one_port(&sc16is7x2_uart_driver, uart);
+}
+
+static int sc16is7x2_unregister_uart_port(struct sc16is7x2_chip *ts,
+		unsigned channel)
+{
+	int ret;
+
+	kfree(&ts->channel[channel].rx_buf);
+	ret = uart_remove_one_port(&sc16is7x2_uart_driver,
+			&ts->channel[channel].uart);
+	if (ret)
+		dev_err(&ts->spi->dev, "Failed to remove the UART port %c: %d\n",
+			'A' + channel, ret);
+
+	return ret;
+}
+
+
+static int __devinit sc16is7x2_probe(struct spi_device *spi)
+{
+	struct sc16is7x2_chip *ts;
+	struct sc16is7x2_platform_data *pdata;
+	int ret;
+
+	pdata = spi->dev.platform_data;
+	if (!pdata || !pdata->gpio_base /* || pdata->uart_base */) {
+		dev_dbg(&spi->dev, "incorrect or missing platform data\n");
+		return -EINVAL;
+	}
+
+	ts = kzalloc(sizeof(struct sc16is7x2_chip), GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	mutex_init(&ts->lock);
+	spi_set_drvdata(spi, ts);
+	ts->spi = spi;
+	ts->force_end_work = 1;
+
+	/* Reset the chip TODO: and disable IRQ output */
+	sc16is7x2_write(spi, REG_IOC, 0, IOC_SRESET);
+
+	ret = request_irq(spi->irq, sc16is7x2_interrupt,
+			IRQF_TRIGGER_FALLING | IRQF_SHARED, "sc16is7x2", ts);
+	if (ret) {
+		dev_warn(&ts->spi->dev, "cannot register interrupt\n");
+		goto exit_destroy;
+	}
+
+	ret = sc16is7x2_register_uart_port(ts, pdata, 0);
+	if (ret)
+		goto exit_irq;
+	ret = sc16is7x2_register_uart_port(ts, pdata, 1);
+	if (ret)
+		goto exit_uart0;
+
+	ret = sc16is7x2_register_gpio(ts, pdata);
+	if (ret)
+		goto exit_uart1;
+
+	ts->workqueue = create_freezeable_workqueue(DRIVER_NAME);
+	if (!ts->workqueue) {
+		dev_warn(&ts->spi->dev, "cannot create workqueue\n");
+		ret = -EBUSY;
+		goto exit_gpio;
+	}
+	INIT_WORK(&ts->work, sc16is7x2_work);
+	ts->force_end_work = 0;
+
+	printk(KERN_INFO DRIVER_NAME " at CS%d (irq %d), 2 UARTs, 8 GPIOs\n"
+			"    eser%d, eser%d, gpiochip%d\n",
+			spi->chip_select, spi->irq,
+			pdata->uart_base, pdata->uart_base + 1,
+			pdata->gpio_base);
+
+	return ret;
+
+exit_gpio:
+	ret = gpiochip_remove(&ts->gpio);
+
+exit_uart1:
+	sc16is7x2_unregister_uart_port(ts, 1);
+
+exit_uart0:
+	sc16is7x2_unregister_uart_port(ts, 0);
+
+exit_irq:
+	free_irq(spi->irq, ts);
+
+exit_destroy:
+	dev_set_drvdata(&spi->dev, NULL);
+	mutex_destroy(&ts->lock);
+	kfree(ts);
+	return ret;
+}
+
+static int __devexit sc16is7x2_remove(struct spi_device *spi)
+{
+	struct sc16is7x2_chip *ts = spi_get_drvdata(spi);
+	int ret;
+
+	if (ts == NULL)
+		return -ENODEV;
+
+	free_irq(spi->irq, ts);
+	ts->force_end_work = 1;
+
+	if (ts->workqueue) {
+		flush_workqueue(ts->workqueue);
+		destroy_workqueue(ts->workqueue);
+		ts->workqueue = NULL;
+	}
+
+	ret = sc16is7x2_unregister_uart_port(ts, 0);
+	if (ret)
+		goto exit_error;
+	ret = sc16is7x2_unregister_uart_port(ts, 1);
+	if (ret)
+		goto exit_error;
+	ret = gpiochip_remove(&ts->gpio);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to remove the GPIO controller: %d\n",
+			ret);
+		goto exit_error;
+	}
+
+	mutex_destroy(&ts->lock);
+	kfree(ts);
+
+exit_error:
+	return ret;
+}
+
+static struct uart_driver sc16is7x2_uart_driver = {
+	.owner          = THIS_MODULE,
+	.driver_name    = DRIVER_NAME,
+	.dev_name       = "eser",
+	.major          = SC16IS7X2_MAJOR,
+	.minor          = SC16IS7X2_MINOR,
+	.nr             = MAX_SC16IS7X2,
+};
+
+static struct spi_driver sc16is7x2_spi_driver = {
+	.driver = {
+		.name		= DRIVER_NAME,
+		.owner		= THIS_MODULE,
+	},
+	.probe		= sc16is7x2_probe,
+	.remove		= __devexit_p(sc16is7x2_remove),
+};
+
+static int __init sc16is7x2_init(void)
+{
+	int ret = uart_register_driver(&sc16is7x2_uart_driver);
+
+	if (ret) {
+		printk(KERN_ERR "Couldn't register sc16is7x2 uart driver\n");
+		return ret;
+	}
+
+	return spi_register_driver(&sc16is7x2_spi_driver);
+}
+/* register after spi postcore initcall and before
+ * subsys initcalls that may rely on these GPIOs
+ */
+subsys_initcall(sc16is7x2_init);
+
+static void __exit sc16is7x2_exit(void)
+{
+	uart_unregister_driver(&sc16is7x2_uart_driver);
+	spi_unregister_driver(&sc16is7x2_spi_driver);
+}
+module_exit(sc16is7x2_exit);
+
+MODULE_AUTHOR("Manuel Stahl");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("SC16IS7x2 SPI based UART chip");
+MODULE_ALIAS("spi:" DRIVER_NAME);
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 563e234..7c1da31 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -47,6 +47,9 @@
 #define PORT_U6_16550A	19	/* ST-Ericsson U6xxx internal UART */
 #define PORT_MAX_8250	19	/* max port ID */
 
+/* SC16IS7x2 SPI UART */
+#define PORT_SC16IS7X2	19
+
 /*
  * ARM specific type numbers.  These are not currently guaranteed
  * to be implemented, and will change in the future.  These are
diff --git a/include/linux/spi/sc16is7x2.h b/include/linux/spi/sc16is7x2.h
new file mode 100755
index 0000000..931fe50
--- /dev/null
+++ b/include/linux/spi/sc16is7x2.h
@@ -0,0 +1,17 @@
+#ifndef LINUX_SPI_SC16IS752_H
+#define LINUX_SPI_SC16IS752_H
+
+#define SC16IS7X2_NR_GPIOS 8
+
+struct sc16is7x2_platform_data {
+	unsigned int	uartclk;
+	/* uart line number of the first channel */
+	unsigned	uart_base;
+	/* number assigned to the first GPIO */
+	unsigned	gpio_base;
+	char		*label;
+	/* list of GPIO names (array length = SC16IS7X2_NR_GPIOS) */
+	const char	*const *names;
+};
+
+#endif

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH] Add sc16is7x2 driver
  2010-09-14 13:07   ` Manuel Stahl
@ 2010-09-14 13:27     ` Jonathan Corbet
  2010-09-15 13:42       ` Manuel Stahl
  2010-09-21 20:42     ` Greg KH
  1 sibling, 1 reply; 12+ messages in thread
From: Jonathan Corbet @ 2010-09-14 13:27 UTC (permalink / raw)
  To: Manuel Stahl; +Cc: Andrew Morton, linux-kernel, Greg KH, linux-serial

On Tue, 14 Sep 2010 15:07:13 +0200
Manuel Stahl <manuel.stahl@iis.fraunhofer.de> wrote:

> Where can I find some example how to use the new threaded irqs?

Basic description here: http://lwn.net/Articles/302043/

If you grep request_threaded_irq() in drivers/, you'll certainly find
some real-world examples.

jon

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] Add sc16is7x2 driver
  2010-09-14 13:27     ` Jonathan Corbet
@ 2010-09-15 13:42       ` Manuel Stahl
  2010-09-15 19:17         ` Andrew Morton
  0 siblings, 1 reply; 12+ messages in thread
From: Manuel Stahl @ 2010-09-15 13:42 UTC (permalink / raw)
  To: Jonathan Corbet; +Cc: Andrew Morton, linux-kernel, Greg KH, linux-serial

[-- Attachment #1: Type: text/plain, Size: 525 bytes --]

OK, here is an implementation with threaded irqs. Still not sure about 
the locking stuff. Maybe someone experienced with uart drivers can help?

Am 14.09.2010 15:27, schrieb Jonathan Corbet:
> On Tue, 14 Sep 2010 15:07:13 +0200
> Manuel Stahl<manuel.stahl@iis.fraunhofer.de>  wrote:
>
>> Where can I find some example how to use the new threaded irqs?
>
> Basic description here: http://lwn.net/Articles/302043/
>
> If you grep request_threaded_irq() in drivers/, you'll certainly find
> some real-world examples.
>
> jon



[-- Attachment #2: sc16is7x2-use-threaded-irqs.patch --]
[-- Type: text/plain, Size: 9360 bytes --]

sc16is7x2: use threaded irqs

Signed-off-by: Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
---
 drivers/serial/sc16is7x2.c |  115 +++++++++++++++----------------------------
 1 files changed, 40 insertions(+), 75 deletions(-)

diff --git a/drivers/serial/sc16is7x2.c b/drivers/serial/sc16is7x2.c
index a9bcd03..04cb1ad 100644
--- a/drivers/serial/sc16is7x2.c
+++ b/drivers/serial/sc16is7x2.c
@@ -92,28 +92,13 @@ struct sc16is7x2_channel {
 struct sc16is7x2_chip {
 	struct spi_device *spi;
 	struct gpio_chip gpio;
-	struct mutex	 lock;
 	struct sc16is7x2_channel channel[2];
 
-	/* for handling irqs: need workqueue since we do spi_sync */
-	struct workqueue_struct *workqueue;
-	struct work_struct work;
-	/* set to 1 to make the workhandler exit as soon as possible */
-	int force_end_work;
-	/* need to know we are suspending to avoid deadlock on workqueue */
-	int suspending;
-
+	/* set to true to make the work thread exit as soon as possible */
+	bool force_end_work;
 	struct spi_message fifo_message;
 
-#define UART_BUG_TXEN	BIT(1)	/* UART has buggy TX IIR status */
-#define UART_BUG_NOMSR	BIT(2)	/* UART has buggy MSR status bits (Au1x00) */
-#define UART_BUG_THRE	BIT(3)	/* UART has buggy THRE reassertion */
-	u16		bugs;		/* port bugs */
-
-#define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS
-	u8		lsr_saved_flags;
-#define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA
-	u8		msr_saved_flags;
+	struct mutex	io_lock;	/* lock for GPIO functions */
 	u8		io_dir;		/* cache for IODir register */
 	u8		io_state;	/* cache for IOState register */
 	u8		io_gpio;	/* PIN is GPIO */
@@ -451,7 +436,7 @@ static void sc16is7x2_shutdown(struct uart_port *port)
 	BUG_ON(!chan);
 	BUG_ON(!ts);
 
-	if (ts->suspending)
+	if (ts->force_end_work)
 		return;
 
 	/* Disable interrupts from this port */
@@ -639,7 +624,7 @@ static void sc16is7x2_release_port(struct uart_port *port)
 	struct sc16is7x2_chip *ts = chan->chip;
 
 	dev_dbg(&ts->spi->dev, "%s\n", __func__);
-	ts->force_end_work = 1;
+	ts->force_end_work = true;
 }
 
 static int sc16is7x2_request_port(struct uart_port *port)
@@ -709,7 +694,7 @@ static int sc16is7x2_gpio_request(struct gpio_chip *gpio, unsigned offset)
 	BUG_ON(offset > 8);
 	dev_dbg(&ts->spi->dev, "%s: offset = %d\n", __func__, offset);
 
-	mutex_lock(&ts->lock);
+	mutex_lock(&ts->io_lock);
 
 	/* GPIO 0:3 and 4:7 can only be controlled as block */
 	ts->io_gpio |= BIT(offset);
@@ -720,7 +705,7 @@ static int sc16is7x2_gpio_request(struct gpio_chip *gpio, unsigned offset)
 		ret = sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control);
 	}
 
-	mutex_unlock(&ts->lock);
+	mutex_unlock(&ts->io_lock);
 
 	return ret;
 }
@@ -734,7 +719,7 @@ static void sc16is7x2_gpio_free(struct gpio_chip *gpio, unsigned offset)
 
 	BUG_ON(offset > 8);
 
-	mutex_lock(&ts->lock);
+	mutex_lock(&ts->io_lock);
 
 	/* GPIO 0:3 and 4:7 can only be controlled as block */
 	ts->io_gpio &= ~BIT(offset);
@@ -746,7 +731,7 @@ static void sc16is7x2_gpio_free(struct gpio_chip *gpio, unsigned offset)
 		sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control);
 	}
 
-	mutex_unlock(&ts->lock);
+	mutex_unlock(&ts->io_lock);
 }
 
 static int sc16is7x2_direction_input(struct gpio_chip *gpio, unsigned offset)
@@ -757,12 +742,12 @@ static int sc16is7x2_direction_input(struct gpio_chip *gpio, unsigned offset)
 
 	BUG_ON(offset > 8);
 
-	mutex_lock(&ts->lock);
+	mutex_lock(&ts->io_lock);
 
 	ts->io_dir &= ~BIT(offset);
 	io_dir = ts->io_dir;
 
-	mutex_unlock(&ts->lock);
+	mutex_unlock(&ts->io_lock);
 
 	return sc16is7x2_write_async(ts->spi, REG_IOD, 0, io_dir);
 }
@@ -776,7 +761,7 @@ static int sc16is7x2_direction_output(struct gpio_chip *gpio, unsigned offset,
 
 	BUG_ON(offset > 8);
 
-	mutex_lock(&ts->lock);
+	mutex_lock(&ts->io_lock);
 
 	if (value)
 		ts->io_state |= BIT(offset);
@@ -791,7 +776,7 @@ static int sc16is7x2_direction_output(struct gpio_chip *gpio, unsigned offset,
 		sc16is7x2_add_write_cmd(&cmds[1], REG_IOD, 0, ts->io_dir);
 	}
 
-	mutex_unlock(&ts->lock);
+	mutex_unlock(&ts->io_lock);
 
 	return sc16is7x2_spi_async(ts->spi, cmds, 2);
 }
@@ -804,7 +789,7 @@ static int sc16is7x2_get(struct gpio_chip *gpio, unsigned offset)
 
 	BUG_ON(offset > 8);
 
-	mutex_lock(&ts->lock);
+	mutex_lock(&ts->io_lock);
 
 	if (ts->io_dir & BIT(offset)) {
 		/* Output: return cached level */
@@ -818,7 +803,7 @@ static int sc16is7x2_get(struct gpio_chip *gpio, unsigned offset)
 		}
 	}
 
-	mutex_unlock(&ts->lock);
+	mutex_unlock(&ts->io_lock);
 
 	return level;
 }
@@ -831,7 +816,7 @@ static void sc16is7x2_set(struct gpio_chip *gpio, unsigned offset, int value)
 
 	BUG_ON(offset > 8);
 
-	mutex_lock(&ts->lock);
+	mutex_lock(&ts->io_lock);
 
 	if (value)
 		ts->io_state |= BIT(offset);
@@ -839,7 +824,7 @@ static void sc16is7x2_set(struct gpio_chip *gpio, unsigned offset, int value)
 		ts->io_state &= ~BIT(offset);
 	io_state = ts->io_state;
 
-	mutex_unlock(&ts->lock);
+	mutex_unlock(&ts->io_lock);
 
 	sc16is7x2_write_async(ts->spi, REG_IOS, 0, io_state);
 }
@@ -971,7 +956,7 @@ static bool sc16is7x2_msg_add_fifo_tx(struct sc16is7x2_chip *ts, unsigned ch)
 	}
 
 	txlvl = sc16is7x2_read(ts->spi, REG_TXLVL, ch);
-	if (txlvl <= 0) {
+	if (txlvl == 0) {
 		dev_dbg(&ts->spi->dev, " fifo full\n");
 		return false;
 	}
@@ -1066,21 +1051,18 @@ static bool sc16is7x2_handle_channel(struct sc16is7x2_chip *ts, unsigned ch)
 	return (chan->iir & UART_IIR_NO_INT) == 0x00;
 }
 
-static void sc16is7x2_work(struct work_struct *w)
+static irqreturn_t sc16is7x2_work(int irq, void *data)
 {
-	struct sc16is7x2_chip *ts =
-			container_of(w, struct sc16is7x2_chip, work);
+	struct sc16is7x2_chip *ts = (struct sc16is7x2_chip *)data;
 	unsigned pending = 0;
 	unsigned ch = 0;
 
 	dev_dbg(&ts->spi->dev, "%s\n", __func__);
-	BUG_ON(!w);
 	BUG_ON(!ts);
 
-
 	if (ts->force_end_work) {
 		dev_dbg(&ts->spi->dev, "%s: force end!\n", __func__);
-		return;
+		return IRQ_NONE;
 	}
 
 	if (ts->channel[0].active)
@@ -1089,29 +1071,28 @@ static void sc16is7x2_work(struct work_struct *w)
 		pending |= BIT(1);
 
 	do {
-		mutex_lock(&(ts->channel[ch].lock));
 		if (pending & BIT(ch) && ts->channel[ch].active) {
 			if (!sc16is7x2_handle_channel(ts, ch))
 				pending &= ~BIT(ch);
 		}
-		mutex_unlock(&(ts->channel[ch].lock));
 		ch ^= 1;	/* switch channel */
-	} while (!ts->force_end_work && !freezing(current) && pending);
+	} while (!ts->force_end_work && pending);
 
 	dev_dbg(&ts->spi->dev, "%s finished\n", __func__);
+
+	return IRQ_HANDLED;
 }
 
-static irqreturn_t sc16is7x2_interrupt(int irq, void *dev_id)
+static irqreturn_t sc16is7x2_irq(int irq, void *data)
 {
-	struct sc16is7x2_chip *ts = dev_id;
-
-	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	struct sc16is7x2_chip *ts = (struct sc16is7x2_channel *)data;
 
-	if (!ts->force_end_work && !work_pending(&ts->work) &&
-	    !freezing(current) && !ts->suspending)
-		queue_work(ts->workqueue, &ts->work);
-
-	return IRQ_HANDLED;
+	/* It takes too long to read the regs over SPI,
+	 * so just wake up the thread */
+	if (ts->channel[0].active || ts->channel[1].active)
+		return IRQ_WAKE_THREAD;
+	else
+		return IRQ_NONE;
 }
 
 /* ******************************** INIT ********************************* */
@@ -1229,16 +1210,16 @@ static int __devinit sc16is7x2_probe(struct spi_device *spi)
 	if (!ts)
 		return -ENOMEM;
 
-	mutex_init(&ts->lock);
+	mutex_init(&ts->io_lock);
 	spi_set_drvdata(spi, ts);
 	ts->spi = spi;
-	ts->force_end_work = 1;
+	ts->force_end_work = true;
 
 	/* Reset the chip TODO: and disable IRQ output */
 	sc16is7x2_write(spi, REG_IOC, 0, IOC_SRESET);
 
-	ret = request_irq(spi->irq, sc16is7x2_interrupt,
-			IRQF_TRIGGER_FALLING | IRQF_SHARED, "sc16is7x2", ts);
+	ret = request_threaded_irq(spi->irq, sc16is7x2_irq, sc16is7x2_work,
+			IRQF_TRIGGER_FALLING | IRQF_SHARED, DRIVER_NAME, ts);
 	if (ret) {
 		dev_warn(&ts->spi->dev, "cannot register interrupt\n");
 		goto exit_destroy;
@@ -1255,14 +1236,7 @@ static int __devinit sc16is7x2_probe(struct spi_device *spi)
 	if (ret)
 		goto exit_uart1;
 
-	ts->workqueue = create_freezeable_workqueue(DRIVER_NAME);
-	if (!ts->workqueue) {
-		dev_warn(&ts->spi->dev, "cannot create workqueue\n");
-		ret = -EBUSY;
-		goto exit_gpio;
-	}
-	INIT_WORK(&ts->work, sc16is7x2_work);
-	ts->force_end_work = 0;
+	ts->force_end_work = false;
 
 	printk(KERN_INFO DRIVER_NAME " at CS%d (irq %d), 2 UARTs, 8 GPIOs\n"
 			"    eser%d, eser%d, gpiochip%d\n",
@@ -1272,9 +1246,6 @@ static int __devinit sc16is7x2_probe(struct spi_device *spi)
 
 	return ret;
 
-exit_gpio:
-	ret = gpiochip_remove(&ts->gpio);
-
 exit_uart1:
 	sc16is7x2_unregister_uart_port(ts, 1);
 
@@ -1286,7 +1257,7 @@ exit_irq:
 
 exit_destroy:
 	dev_set_drvdata(&spi->dev, NULL);
-	mutex_destroy(&ts->lock);
+	mutex_destroy(&ts->io_lock);
 	kfree(ts);
 	return ret;
 }
@@ -1299,14 +1270,8 @@ static int __devexit sc16is7x2_remove(struct spi_device *spi)
 	if (ts == NULL)
 		return -ENODEV;
 
+	ts->force_end_work = true;
 	free_irq(spi->irq, ts);
-	ts->force_end_work = 1;
-
-	if (ts->workqueue) {
-		flush_workqueue(ts->workqueue);
-		destroy_workqueue(ts->workqueue);
-		ts->workqueue = NULL;
-	}
 
 	ret = sc16is7x2_unregister_uart_port(ts, 0);
 	if (ret)
@@ -1321,7 +1286,7 @@ static int __devexit sc16is7x2_remove(struct spi_device *spi)
 		goto exit_error;
 	}
 
-	mutex_destroy(&ts->lock);
+	mutex_destroy(&ts->io_lock);
 	kfree(ts);
 
 exit_error:
-- 
1.7.2.3


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH] Add sc16is7x2 driver
  2010-09-15 13:42       ` Manuel Stahl
@ 2010-09-15 19:17         ` Andrew Morton
  0 siblings, 0 replies; 12+ messages in thread
From: Andrew Morton @ 2010-09-15 19:17 UTC (permalink / raw)
  To: Manuel Stahl; +Cc: Jonathan Corbet, linux-kernel, Greg KH, linux-serial

On Wed, 15 Sep 2010 15:42:14 +0200
Manuel Stahl <manuel.stahl@iis.fraunhofer.de> wrote:

> OK, here is an implementation with threaded irqs.

That simplified things nicely.  Does it still work?

> Still not sure about 
> the locking stuff. Maybe someone experienced with uart drivers can help?

The question is too general for anyone to answer, really.  Please
describe the specific issues in resonable detail?  



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] Add sc16is7x2 driver
  2010-09-14 13:07   ` Manuel Stahl
  2010-09-14 13:27     ` Jonathan Corbet
@ 2010-09-21 20:42     ` Greg KH
  2010-09-22  8:01         ` Manuel Stahl
  1 sibling, 1 reply; 12+ messages in thread
From: Greg KH @ 2010-09-21 20:42 UTC (permalink / raw)
  To: Manuel Stahl; +Cc: Andrew Morton, linux-kernel, linux-serial

On Tue, Sep 14, 2010 at 03:07:13PM +0200, Manuel Stahl wrote:
> --- a/include/linux/serial_core.h
> +++ b/include/linux/serial_core.h
> @@ -47,6 +47,9 @@
>  #define PORT_U6_16550A	19	/* ST-Ericsson U6xxx internal UART */
>  #define PORT_MAX_8250	19	/* max port ID */
>  
> +/* SC16IS7x2 SPI UART */
> +#define PORT_SC16IS7X2	19

Hm, didn't you just duplicate the number here?  Shouldn't that be "20"
and you bump the max up?

In the end, we need to just drop those, I don't think we need defines
anymore, but I need to look at it some more to find out.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] Add sc16is7x2 driver
  2010-09-21 20:42     ` Greg KH
@ 2010-09-22  8:01         ` Manuel Stahl
  0 siblings, 0 replies; 12+ messages in thread
From: Manuel Stahl @ 2010-09-22  8:01 UTC (permalink / raw)
  To: Greg KH; +Cc: Andrew Morton, linux-kernel, linux-serial

Am 21.09.2010 22:42, schrieb Greg KH:
> On Tue, Sep 14, 2010 at 03:07:13PM +0200, Manuel Stahl wrote:
>> --- a/include/linux/serial_core.h
>> +++ b/include/linux/serial_core.h
>> @@ -47,6 +47,9 @@
>>   #define PORT_U6_16550A	19	/* ST-Ericsson U6xxx internal UART */
>>   #define PORT_MAX_8250	19	/* max port ID */
>>
>> +/* SC16IS7x2 SPI UART */
>> +#define PORT_SC16IS7X2	19
>
> Hm, didn't you just duplicate the number here?  Shouldn't that be "20"
> and you bump the max up?

Oh, that might be true.

> In the end, we need to just drop those, I don't think we need defines
> anymore, but I need to look at it some more to find out.

Thank you for doing that, Greg.

Regards,
-- 
Dipl.-Inf. Manuel Stahl
Fraunhofer-Institut für Integrierte Schaltungen IIS
- Leistungsoptimierte Systeme -
Nordostpark 93                Telefon  +49 (0)911/58061-6419
90411 Nürnberg                Fax      +49 (0)911/58061-6398
http://www.iis.fraunhofer.de  manuel.stahl@iis.fraunhofer.de

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] Add sc16is7x2 driver
@ 2010-09-22  8:01         ` Manuel Stahl
  0 siblings, 0 replies; 12+ messages in thread
From: Manuel Stahl @ 2010-09-22  8:01 UTC (permalink / raw)
  To: Greg KH; +Cc: Andrew Morton, linux-kernel, linux-serial

Am 21.09.2010 22:42, schrieb Greg KH:
> On Tue, Sep 14, 2010 at 03:07:13PM +0200, Manuel Stahl wrote:
>> --- a/include/linux/serial_core.h
>> +++ b/include/linux/serial_core.h
>> @@ -47,6 +47,9 @@
>>   #define PORT_U6_16550A	19	/* ST-Ericsson U6xxx internal UART */
>>   #define PORT_MAX_8250	19	/* max port ID */
>>
>> +/* SC16IS7x2 SPI UART */
>> +#define PORT_SC16IS7X2	19
>
> Hm, didn't you just duplicate the number here?  Shouldn't that be "20"
> and you bump the max up?

Oh, that might be true.

> In the end, we need to just drop those, I don't think we need defines
> anymore, but I need to look at it some more to find out.

Thank you for doing that, Greg.

Regards,
-- 
Dipl.-Inf. Manuel Stahl
Fraunhofer-Institut für Integrierte Schaltungen IIS
- Leistungsoptimierte Systeme -
Nordostpark 93                Telefon  +49 (0)911/58061-6419
90411 Nürnberg                Fax      +49 (0)911/58061-6398
http://www.iis.fraunhofer.de  manuel.stahl@iis.fraunhofer.de
--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] Add sc16is7x2 driver
  2012-02-05  6:55 Thomas Weber
@ 2012-02-05 19:56 ` Alan Cox
  0 siblings, 0 replies; 12+ messages in thread
From: Alan Cox @ 2012-02-05 19:56 UTC (permalink / raw)
  To: Thomas Weber; +Cc: linux-serial, Manuel Stahl

> +/* ******************************** IRQ ********************************* */
> +
> +static void sc16is7x2_handle_rx(struct sc16is7x2_chip *ts, unsigned ch)
> +{
> +	struct sc16is7x2_channel *chan = &ts->channel[ch];
> +	struct uart_port *uart = &chan->uart;
> +	struct tty_struct *tty = uart->state->port.tty;

What guarantees tty cannot go NULL during this ? - I think you need to
hold references and check ? (tty_port_tty_get)


> +/* Trigger work thread*/
> +static void sc16is7x2_dowork(struct sc16is7x2_channel *chan)
> +{
> +	if (!freezing(current))
> +		queue_work(chan->workqueue, &chan->work);
> +}

Threaded interrupt handler or is the single queue for work in tx and rx
actually a nicer way to do it - just asking the question.


> +static void sc16is7x2_set_mctrl(struct uart_port *port, unsigned int mctrl)
> +{
> +	struct sc16is7x2_channel *chan = to_sc16is7x2_channel(port);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +
> +	dev_dbg(&ts->spi->dev, "%s (0x%02x)\n", __func__, mctrl);
> +
> +	/* TODO: set DCD and DSR
> +	 * CTS/RTS is handled automatically
> +	 */

Looks like it's not yet finished and ready for merging ?

> +static void sc16is7x2_stop_rx(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan = to_sc16is7x2_channel(port);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	chan->ier &= ~UART_IER_RLSI;
> +	chan->uart.read_status_mask &= ~UART_LSR_DR;
> +	chan->handle_regs = true;
> +	/* Trigger work thread for doing the actual configuration change */
> +	sc16is7x2_dowork(chan);
> +}

> +static void sc16is7x2_break_ctl(struct uart_port *port, int break_state)
> +{
> +	/* We don't support break control yet, do nothing */
> +}

More unfinished bits

> +	/* Setup IRQ. Actually we have a low active IRQ, but we want
> +	 * one shot behaviour */
> +	if (request_irq(ts->spi->irq, sc16is7x2_irq,
> +			IRQF_TRIGGER_FALLING | IRQF_SHARED,
> +			"sc16is7x2", chan)) {

Is this a property of the device or of your board ?


> +static void
> +sc16is7x2_set_termios(struct uart_port *port, struct ktermios *termios,
> +		       struct ktermios *old)
> +{

> +#ifdef CMSPAR
> +	if (termios->c_cflag & CMSPAR)
> +		lcr |= UART_LCR_SPAR;
> +#endif

ifdef shouldn't be needed


termios should write back the actual baud (see how 8250.c does it)


> +#ifdef CONFIG_GPIOLIB

I wonder if this should be a separate driver and the device an MFD -
would anyone ever be using it as gpio only ?


Looks basically sound. The GPIO question I think needs figuring out, and
there are a few bits of unfinished code that need glancing it.

Alan

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH] Add sc16is7x2 driver
@ 2012-02-05  6:55 Thomas Weber
  2012-02-05 19:56 ` Alan Cox
  0 siblings, 1 reply; 12+ messages in thread
From: Thomas Weber @ 2012-02-05  6:55 UTC (permalink / raw)
  To: linux-serial; +Cc: Thomas Weber, Manuel Stahl, Thomas Weber

This patch adds support for the sc16is7x2 SPI to UART chips.
Each chip has two UARTs and 8 GPIOs.

Signed-off-by: Manuel Stahl <manuel.stahl@iis.fraunhofer.de>

[weber@corscience.de: Rebased to actual kernel]
Signed-off-by: Thomas Weber <weber@corscience.de>
---
 drivers/tty/serial/Kconfig       |   10 +
 drivers/tty/serial/Makefile      |    1 +
 drivers/tty/serial/sc16is7x2.c   | 1065 ++++++++++++++++++++++++++++++++++++++
 include/linux/serial_core.h      |    3 +
 include/linux/serial_sc16is7x2.h |   17 +
 5 files changed, 1096 insertions(+), 0 deletions(-)
 create mode 100644 drivers/tty/serial/sc16is7x2.c
 create mode 100755 include/linux/serial_sc16is7x2.h

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 2de9924..ab15542 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -9,6 +9,16 @@ source "drivers/tty/serial/8250/Kconfig"
 
 comment "Non-8250 serial port support"
 
+config SERIAL_SC16IS7X2
+	tristate "SC16IS7X2 support"
+	depends on SPI_MASTER
+	select SERIAL_CORE
+	help
+	  Selecting this option will add support for SC16IS7X2 SPI UARTs.
+	  The GPIOs are exported via gpiolib interface.
+
+	  If unsure, say N.
+
 config SERIAL_AMBA_PL010
 	tristate "ARM AMBA PL010 serial port support"
 	depends on ARM_AMBA && (BROKEN || !ARCH_VERSATILE)
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index fef32e1..a6cc55e 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_SERIAL_SUNSAB) += sunsab.o
 # Now bring in any enabled 8250/16450/16550 type drivers.
 obj-$(CONFIG_SERIAL_8250) += 8250/
 
+obj-$(CONFIG_SERIAL_SC16IS7X2) += sc16is7x2.o
 obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
 obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
 obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o
diff --git a/drivers/tty/serial/sc16is7x2.c b/drivers/tty/serial/sc16is7x2.c
new file mode 100644
index 0000000..215874f
--- /dev/null
+++ b/drivers/tty/serial/sc16is7x2.c
@@ -0,0 +1,1065 @@
+/**
+ * drivers/tty/serial/sc16is7x2.c
+ *
+ * Copyright (C) 2009 Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
+ *
+ * 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.
+ *
+ * The SC16IS7x2 device is a SPI driven dual UART with GPIOs.
+ *
+ * The driver exports two uarts and a gpiochip interface.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial_reg.h>
+#include <linux/serial_core.h>
+#include <linux/serial.h>
+#include <linux/gpio.h>
+#include <linux/spi/spi.h>
+#include <linux/freezer.h>
+#include <linux/serial_sc16is7x2.h>
+
+#define MAX_SC16IS7X2		8
+#define FIFO_SIZE		64
+
+#define DRIVER_NAME		"sc16is7x2"
+#define TYPE_NAME		"SC16IS7x2"
+
+
+#define REG_READ	0x80
+#define REG_WRITE	0x00
+
+/* Special registers */
+#define REG_TXLVL	0x08	/* Transmitter FIFO Level register */
+#define REG_RXLVL	0x09	/* Receiver FIFO Level register */
+#define REG_IOD		0x0A	/* IO Direction register */
+#define REG_IOS		0x0B	/* IO State register */
+#define REG_IOI		0x0C	/* IO Interrupt Enable register */
+#define REG_IOC		0x0E	/* IO Control register */
+
+#define IOC_SRESET	0x08	/* Software reset */
+#define IOC_GPIO30	0x04	/* GPIO 3:0 unset: as IO, set: as modem pins */
+#define IOC_GPIO74	0x02	/* GPIO 7:4 unset: as IO, set: as modem pins */
+#define IOC_IOLATCH	0x01	/* Unset: input unlatched, set: input latched */
+
+struct sc16is7x2_chip;
+
+/*
+ * Some registers must be read back to modify.
+ * To save time we cache them here in memory.
+ */
+struct sc16is7x2_channel {
+	struct sc16is7x2_chip	*chip;	/* back link */
+	struct uart_port	uart;
+
+	/* Workqueue that does all the magic */
+	struct workqueue_struct *workqueue;
+	struct work_struct	work;
+
+	u16		quot;		/* baud rate divisor */
+	u8		iir;		/* state of IIR register */
+	u8		lsr;		/* state of LSR register */
+	u8		msr;		/* state of MSR register */
+	u8		ier;		/* cache for IER register */
+	u8		fcr;		/* cache for FCR register */
+	u8		lcr;		/* cache for LCR register */
+	u8		mcr;		/* cache for MCR register */
+	u8		efr;		/* cache for EFR register */
+#ifdef DEBUG
+	bool		handle_irq;
+#endif
+	bool		handle_baud;	/* baud rate needs update */
+	bool		handle_regs;	/* other regs need update */
+	u8		buf[FIFO_SIZE+1]; /* fifo transfer buffer */
+};
+
+struct sc16is7x2_chip {
+	struct spi_device *spi;
+	struct sc16is7x2_channel channel[2];
+
+#ifdef CONFIG_GPIOLIB
+	struct gpio_chip gpio;
+	struct mutex	io_lock;	/* lock for GPIO functions */
+	u8		io_dir;		/* cache for IODir register */
+	u8		io_state;	/* cache for IOState register */
+	u8		io_gpio;	/* PIN is GPIO */
+	u8		io_control;	/* cache for IOControl register */
+#endif
+};
+
+/* ******************************** SPI ********************************* */
+
+static inline u8 write_cmd(u8 reg, u8 ch)
+{
+	return REG_WRITE | (reg & 0xf) << 3 | (ch & 0x1) << 1;
+}
+
+static inline u8 read_cmd(u8 reg, u8 ch)
+{
+	return REG_READ  | (reg & 0xf) << 3 | (ch & 0x1) << 1;
+}
+
+/*
+ * sc16is7x2_write - Write a new register content (sync)
+ * @reg: Register offset
+ * @ch:  Channel (0 or 1)
+ */
+static int sc16is7x2_write(struct sc16is7x2_chip *ts, u8 reg, u8 ch, u8 val)
+{
+	u8 out[2];
+
+	out[0] = write_cmd(reg, ch);
+	out[1] = val;
+	return spi_write(ts->spi, out, sizeof(out));
+}
+
+/**
+ * sc16is7x2_read - Read back register content
+ * @reg: Register offset
+ * @ch:  Channel (0 or 1)
+ *
+ * Returns positive 8 bit value from the device if successful or a
+ * negative value on error
+ */
+static int sc16is7x2_read(struct sc16is7x2_chip *ts, unsigned reg, unsigned ch)
+{
+	return spi_w8r8(ts->spi, read_cmd(reg, ch));
+}
+
+/* ******************************** IRQ ********************************* */
+
+static void sc16is7x2_handle_rx(struct sc16is7x2_chip *ts, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &ts->channel[ch];
+	struct uart_port *uart = &chan->uart;
+	struct tty_struct *tty = uart->state->port.tty;
+	struct spi_message message;
+	struct spi_transfer t[2];
+	unsigned long flags;
+	u8 lsr = chan->lsr;
+	int rxlvl;
+
+	rxlvl = sc16is7x2_read(ts, REG_RXLVL, ch);
+	if (rxlvl <= 0) {
+		return;
+	} else if (rxlvl > FIFO_SIZE) {
+		/* Ensure sanity of RX level */
+		rxlvl = FIFO_SIZE;
+	}
+
+	dev_dbg(&ts->spi->dev, " %s (%i) %d bytes\n", __func__, ch, rxlvl);
+
+	memset(t, 0, sizeof t);
+	chan->buf[0] = read_cmd(UART_RX, ch);
+	t[0].len = 1;
+	t[0].tx_buf = &chan->buf[0];
+	t[1].len = rxlvl;
+	t[1].rx_buf = &chan->buf[1];
+
+	spi_message_init(&message);
+	spi_message_add_tail(&t[0], &message);
+	spi_message_add_tail(&t[1], &message);
+
+	if (spi_sync(ts->spi, &message)) {
+		dev_err(&ts->spi->dev, " SPI transfer RX handling failed\n");
+		return;
+	}
+	chan->buf[rxlvl + 1] = '\0';
+	dev_dbg(&ts->spi->dev, "%s\n", &chan->buf[1]);
+
+	spin_lock_irqsave(&uart->lock, flags);
+
+	if (unlikely(lsr & UART_LSR_BRK_ERROR_BITS)) {
+		/*
+		 * For statistics only
+		 */
+		if (lsr & UART_LSR_BI) {
+			lsr &= ~(UART_LSR_FE | UART_LSR_PE);
+			chan->uart.icount.brk++;
+			/*
+			 * We do the SysRQ and SAK checking
+			 * here because otherwise the break
+			 * may get masked by ignore_status_mask
+			 * or read_status_mask.
+			 */
+			if (uart_handle_break(&chan->uart))
+				goto ignore_char;
+		} else if (lsr & UART_LSR_PE)
+			chan->uart.icount.parity++;
+		else if (lsr & UART_LSR_FE)
+			chan->uart.icount.frame++;
+		if (lsr & UART_LSR_OE)
+			chan->uart.icount.overrun++;
+	}
+
+	/* Insert received data */
+	tty_insert_flip_string(tty, &chan->buf[1], rxlvl);
+	/* Update RX counter */
+	uart->icount.rx += rxlvl;
+
+ignore_char:
+	spin_unlock_irqrestore(&uart->lock, flags);
+
+	/* Push the received data to receivers */
+	if (rxlvl)
+		tty_flip_buffer_push(tty);
+}
+
+static void sc16is7x2_handle_tx(struct sc16is7x2_chip *ts, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &ts->channel[ch];
+	struct uart_port *uart = &chan->uart;
+	struct circ_buf *xmit = &uart->state->xmit;
+	unsigned long flags;
+	unsigned i, len;
+	int txlvl;
+
+	if (chan->uart.x_char && chan->lsr & UART_LSR_THRE) {
+		dev_dbg(&ts->spi->dev, " tx: x-char\n");
+		sc16is7x2_write(ts, UART_TX, ch, uart->x_char);
+		uart->icount.tx++;
+		uart->x_char = 0;
+		return;
+	}
+	if (uart_circ_empty(xmit) || uart_tx_stopped(&chan->uart))
+		/* No data to send or TX is stopped */
+		return;
+
+	txlvl = sc16is7x2_read(ts, REG_TXLVL, ch);
+	if (txlvl <= 0) {
+		dev_dbg(&ts->spi->dev, " %s (%i) fifo full\n", __func__, ch);
+		return;
+	}
+
+	/* number of bytes to transfer to the fifo */
+	len = min(txlvl, (int)uart_circ_chars_pending(xmit));
+
+	dev_dbg(&ts->spi->dev, " %s (%i) %d bytes\n", __func__, ch, len);
+
+	spin_lock_irqsave(&uart->lock, flags);
+	for (i = 1; i <= len ; i++) {
+		chan->buf[i] = xmit->buf[xmit->tail];
+		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+	}
+	uart->icount.tx += len;
+	spin_unlock_irqrestore(&uart->lock, flags);
+
+	chan->buf[0] = write_cmd(UART_TX, ch);
+	if (spi_write(ts->spi, chan->buf, len + 1))
+		dev_err(&ts->spi->dev, " SPI transfer TX handling failed\n");
+
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(uart);
+}
+
+static void sc16is7x2_handle_baud(struct sc16is7x2_chip *ts, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &ts->channel[ch];
+
+	if (!chan->handle_baud)
+		return;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	sc16is7x2_write(ts, UART_IER, ch, 0);
+	sc16is7x2_write(ts, UART_LCR, ch, UART_LCR_DLAB); /* access DLL&DLM */
+	sc16is7x2_write(ts, UART_DLL, ch, chan->quot & 0xff);
+	sc16is7x2_write(ts, UART_DLM, ch, chan->quot >> 8);
+	sc16is7x2_write(ts, UART_LCR, ch, chan->lcr);     /* reset DLAB */
+
+	chan->handle_baud = false;
+}
+
+static void sc16is7x2_handle_regs(struct sc16is7x2_chip *ts, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &ts->channel[ch];
+
+	if (!chan->handle_regs)
+		return;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+        sc16is7x2_write(ts, UART_LCR, ch, 0xBF);  /* access EFR */
+	sc16is7x2_write(ts, UART_EFR, ch, chan->efr);
+	sc16is7x2_write(ts, UART_LCR, ch, chan->lcr);
+	sc16is7x2_write(ts, UART_FCR, ch, chan->fcr);
+	sc16is7x2_write(ts, UART_MCR, ch, chan->mcr);
+	sc16is7x2_write(ts, UART_IER, ch, chan->ier);
+
+	chan->handle_regs = false;
+}
+
+static void sc16is7x2_read_status(struct sc16is7x2_chip *ts, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
+/*	struct spi_message m;
+	struct spi_transfer t;
+	u8 *buf = chan->buf; */
+	u8 ier;
+
+#ifdef DEBUG
+	ier = sc16is7x2_read(ts, UART_IER, ch);
+#endif
+	chan->iir = sc16is7x2_read(ts, UART_IIR, ch);
+	chan->msr = sc16is7x2_read(ts, UART_MSR, ch);
+	chan->lsr = sc16is7x2_read(ts, UART_LSR, ch);
+/*
+	buf[0] = read_cmd(UART_IER, ch);
+	buf[1] = read_cmd(UART_IIR, ch);
+	buf[2] = read_cmd(UART_MSR, ch);
+	buf[3] = read_cmd(UART_LSR, ch);
+
+	t.tx_buf = buf;
+	t.rx_buf = &buf[16];
+	t.len = 5;
+
+	spi_message_init(&m);
+	spi_message_add_tail(&t, &m);
+	spi_sync(ts->spi, &m); */
+
+	dev_dbg(&ts->spi->dev, " %s ier=0x%02x iir=0x%02x msr=0x%02x lsr=0x%02x\n",
+			__func__, ier, chan->iir, chan->msr, chan->lsr);
+/*
+	dev_dbg(&ts->spi->dev, " %s ier=0x%02x iir=0x%02x msr=0x%02x lsr=0x%02x\n",
+			__func__, buf[17], buf[18], buf[19], buf[20]);
+*/
+}
+
+static void sc16is7x2_handle_channel(struct work_struct *w)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(w, struct sc16is7x2_channel, work);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = (chan == ts->channel) ? 0 : 1;
+
+#ifdef DEBUG
+	dev_dbg(&ts->spi->dev, "%s (%i) %s\n", __func__, ch,
+			chan->handle_irq ? "irq" : "");
+	chan->handle_irq = false;
+#endif
+
+	do {
+		sc16is7x2_handle_baud(ts, ch);
+		sc16is7x2_handle_regs(ts, ch);
+
+		sc16is7x2_read_status(ts, ch);
+		sc16is7x2_handle_tx(ts, ch);
+		sc16is7x2_handle_rx(ts, ch);
+	} while (!(chan->iir & UART_IIR_NO_INT));
+
+	dev_dbg(&ts->spi->dev, "%s finished\n", __func__);
+}
+
+/* Trigger work thread*/
+static void sc16is7x2_dowork(struct sc16is7x2_channel *chan)
+{
+	if (!freezing(current))
+		queue_work(chan->workqueue, &chan->work);
+}
+
+static irqreturn_t sc16is7x2_irq(int irq, void *data)
+{
+	struct sc16is7x2_channel *chan = data;
+
+#ifdef DEBUG
+	/* Indicate irq */
+	chan->handle_irq = true;
+#endif
+
+	/* Trigger work thread */
+	sc16is7x2_dowork(chan);
+
+	return IRQ_HANDLED;
+}
+
+/* ******************************** UART ********************************* */
+
+#define to_sc16is7x2_channel(port) \
+		container_of(port, struct sc16is7x2_channel, uart)
+
+
+static unsigned int sc16is7x2_tx_empty(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan = to_sc16is7x2_channel(port);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned lsr;
+
+	dev_dbg(&ts->spi->dev, "%s = %s\n", __func__,
+			chan->lsr & UART_LSR_TEMT ? "yes" : "no");
+
+	lsr = chan->lsr;
+	return lsr & UART_LSR_TEMT ? TIOCSER_TEMT : 0;
+}
+
+static unsigned int sc16is7x2_get_mctrl(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan = to_sc16is7x2_channel(port);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned int status;
+	unsigned int ret;
+
+	dev_dbg(&ts->spi->dev, "%s (0x%02x)\n", __func__, chan->msr);
+
+	status = chan->msr;
+
+	ret = 0;
+	if (status & UART_MSR_DCD)
+		ret |= TIOCM_CAR;
+	if (status & UART_MSR_RI)
+		ret |= TIOCM_RNG;
+	if (status & UART_MSR_DSR)
+		ret |= TIOCM_DSR;
+	if (status & UART_MSR_CTS)
+		ret |= TIOCM_CTS;
+	return ret;
+}
+
+static void sc16is7x2_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+	struct sc16is7x2_channel *chan = to_sc16is7x2_channel(port);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s (0x%02x)\n", __func__, mctrl);
+
+	/* TODO: set DCD and DSR
+	 * CTS/RTS is handled automatically
+	 */
+}
+
+static void sc16is7x2_stop_tx(struct uart_port *port)
+{
+	/* Do nothing */
+}
+
+static void sc16is7x2_start_tx(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan = to_sc16is7x2_channel(port);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	/* Trigger work thread for sending data */
+	sc16is7x2_dowork(chan);
+}
+
+static void sc16is7x2_stop_rx(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan = to_sc16is7x2_channel(port);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	chan->ier &= ~UART_IER_RLSI;
+	chan->uart.read_status_mask &= ~UART_LSR_DR;
+	chan->handle_regs = true;
+	/* Trigger work thread for doing the actual configuration change */
+	sc16is7x2_dowork(chan);
+}
+
+static void sc16is7x2_enable_ms(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan = to_sc16is7x2_channel(port);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	chan->ier |= UART_IER_MSI;
+	chan->handle_regs = true;
+	/* Trigger work thread for doing the actual configuration change */
+	sc16is7x2_dowork(chan);
+}
+
+static void sc16is7x2_break_ctl(struct uart_port *port, int break_state)
+{
+	/* We don't support break control yet, do nothing */
+}
+
+static int sc16is7x2_startup(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan = to_sc16is7x2_channel(port);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = (&ts->channel[1] == chan) ? 1 : 0;
+	unsigned long flags;
+
+	dev_dbg(&ts->spi->dev, "\n%s (%d)\n", __func__, port->line);
+
+	/* Clear the interrupt registers. */
+	sc16is7x2_write(ts, UART_IER, ch, 0);
+	sc16is7x2_read_status(ts, ch);
+
+	/* Initialize work queue */
+	chan->workqueue = create_freezable_workqueue("sc16is7x2");
+	if (!chan->workqueue) {
+		dev_err(&ts->spi->dev, "Workqueue creation failed\n");
+		return -EBUSY;
+	}
+	INIT_WORK(&chan->work, sc16is7x2_handle_channel);
+
+	/* Setup IRQ. Actually we have a low active IRQ, but we want
+	 * one shot behaviour */
+	if (request_irq(ts->spi->irq, sc16is7x2_irq,
+			IRQF_TRIGGER_FALLING | IRQF_SHARED,
+			"sc16is7x2", chan)) {
+		dev_err(&ts->spi->dev, "IRQ request failed\n");
+		destroy_workqueue(chan->workqueue);
+		chan->workqueue = NULL;
+		return -EBUSY;
+	}
+
+
+	spin_lock_irqsave(&chan->uart.lock, flags);
+	chan->lcr = UART_LCR_WLEN8;
+	chan->mcr = 0;
+	chan->fcr = 0;
+	chan->ier = UART_IER_RLSI | UART_IER_RDI | UART_IER_THRI;
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	sc16is7x2_write(ts, UART_FCR, ch, UART_FCR_ENABLE_FIFO |
+		       UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+	sc16is7x2_write(ts, UART_FCR, ch, chan->fcr);
+	/* Now, initialize the UART */
+	sc16is7x2_write(ts, UART_LCR, ch, chan->lcr);
+	sc16is7x2_write(ts, UART_MCR, ch, chan->mcr);
+	sc16is7x2_write(ts, UART_IER, ch, chan->ier);
+
+	return 0;
+}
+
+static void sc16is7x2_shutdown(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan = to_sc16is7x2_channel(port);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned long flags;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	BUG_ON(!chan);
+	BUG_ON(!ts);
+
+	/* Free the interrupt */
+	free_irq(ts->spi->irq, chan);
+
+	if (chan->workqueue) {
+		/* Flush and destroy work queue */
+		flush_workqueue(chan->workqueue);
+		destroy_workqueue(chan->workqueue);
+		chan->workqueue = NULL;
+	}
+
+	/* Suspend HW */
+	spin_lock_irqsave(&chan->uart.lock, flags);
+	chan->ier = UART_IERX_SLEEP;
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+	sc16is7x2_write(ts, UART_IER, ch, chan->ier);
+}
+
+static void
+sc16is7x2_set_termios(struct uart_port *port, struct ktermios *termios,
+		       struct ktermios *old)
+{
+	struct sc16is7x2_channel *chan = to_sc16is7x2_channel(port);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned long flags;
+	unsigned int baud;
+	u8 lcr, fcr = 0;
+
+	/* Ask the core to calculate the divisor for us. */
+	baud = uart_get_baud_rate(port, termios, old,
+				  port->uartclk / 16 / 0xffff,
+				  port->uartclk / 16);
+	chan->quot = uart_get_divisor(port, baud);
+	chan->handle_baud = true;
+
+	dev_dbg(&ts->spi->dev, "%s (baud %u)\n", __func__, baud);
+
+	/* set word length */
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:
+		lcr = UART_LCR_WLEN5;
+		break;
+	case CS6:
+		lcr = UART_LCR_WLEN6;
+		break;
+	case CS7:
+		lcr = UART_LCR_WLEN7;
+		break;
+	default:
+	case CS8:
+		lcr = UART_LCR_WLEN8;
+		break;
+	}
+
+	if (termios->c_cflag & CSTOPB)
+		lcr |= UART_LCR_STOP;
+	if (termios->c_cflag & PARENB)
+		lcr |= UART_LCR_PARITY;
+	if (!(termios->c_cflag & PARODD))
+		lcr |= UART_LCR_EPAR;
+#ifdef CMSPAR
+	if (termios->c_cflag & CMSPAR)
+		lcr |= UART_LCR_SPAR;
+#endif
+
+	fcr = UART_FCR_ENABLE_FIFO;
+	/* configure the fifo */
+	if (baud < 2400)
+		fcr |= UART_FCR_TRIGGER_1;
+	else
+		fcr |= UART_FCR_R_TRIG_01 | UART_FCR_T_TRIG_10;
+
+	chan->efr = UART_EFR_ECB;
+	chan->mcr |= UART_MCR_RTS;
+	if (termios->c_cflag & CRTSCTS)
+		chan->efr |= UART_EFR_CTS | UART_EFR_RTS;
+
+	/*
+	 * Ok, we're now changing the port state.  Do it with
+	 * interrupts disabled.
+	 */
+	spin_lock_irqsave(&chan->uart.lock, flags);
+
+	/* we are sending char from a workqueue so enable */
+	chan->uart.state->port.tty->low_latency = 1;
+
+	/* Update the per-port timeout. */
+	uart_update_timeout(port, termios->c_cflag, baud);
+
+	chan->uart.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+	if (termios->c_iflag & INPCK)
+		chan->uart.read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+	if (termios->c_iflag & (BRKINT | PARMRK))
+		chan->uart.read_status_mask |= UART_LSR_BI;
+
+	/* Characters to ignore */
+	chan->uart.ignore_status_mask = 0;
+	if (termios->c_iflag & IGNPAR)
+		chan->uart.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+	if (termios->c_iflag & IGNBRK) {
+		chan->uart.ignore_status_mask |= UART_LSR_BI;
+		/*
+		 * If we're ignoring parity and break indicators,
+		 * ignore overruns too (for real raw support).
+		 */
+		if (termios->c_iflag & IGNPAR)
+			chan->uart.ignore_status_mask |= UART_LSR_OE;
+	}
+
+	/* ignore all characters if CREAD is not set */
+	if ((termios->c_cflag & CREAD) == 0)
+		chan->uart.ignore_status_mask |= UART_LSR_DR;
+
+	/* CTS flow control flag and modem status interrupts */
+	chan->ier &= ~UART_IER_MSI;
+	if (UART_ENABLE_MS(&chan->uart, termios->c_cflag))
+		chan->ier |= UART_IER_MSI;
+
+	chan->lcr = lcr;	/* Save LCR */
+	chan->fcr = fcr;	/* Save FCR */
+	chan->handle_regs = true;
+
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	/* Trigger work thread for doing the actual configuration change */
+	sc16is7x2_dowork(chan);
+}
+
+static const char * sc16is7x2_type(struct uart_port *port)
+{
+	pr_debug("%s\n", __func__);
+	return TYPE_NAME;
+}
+
+static void sc16is7x2_release_port(struct uart_port *port)
+{
+	pr_debug("%s\n", __func__);
+}
+
+static int sc16is7x2_request_port(struct uart_port *port)
+{
+	pr_debug("%s\n", __func__);
+	return 0;
+}
+
+static void sc16is7x2_config_port(struct uart_port *port, int flags)
+{
+	struct sc16is7x2_channel *chan = to_sc16is7x2_channel(port);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	if (flags & UART_CONFIG_TYPE)
+		chan->uart.type = PORT_SC16IS7X2;
+}
+
+static int
+sc16is7x2_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+	if (ser->type == PORT_UNKNOWN || ser->type == PORT_SC16IS7X2)
+		return 0;
+
+	return -EINVAL;
+}
+
+static struct uart_ops sc16is7x2_uart_ops = {
+	.tx_empty	= sc16is7x2_tx_empty,
+	.set_mctrl	= sc16is7x2_set_mctrl,
+	.get_mctrl	= sc16is7x2_get_mctrl,
+	.stop_tx        = sc16is7x2_stop_tx,
+	.start_tx	= sc16is7x2_start_tx,
+	.stop_rx	= sc16is7x2_stop_rx,
+	.enable_ms      = sc16is7x2_enable_ms,
+	.break_ctl      = sc16is7x2_break_ctl,
+	.startup	= sc16is7x2_startup,
+	.shutdown	= sc16is7x2_shutdown,
+	.set_termios	= sc16is7x2_set_termios,
+	.type		= sc16is7x2_type,
+	.release_port   = sc16is7x2_release_port,
+	.request_port   = sc16is7x2_request_port,
+	.config_port	= sc16is7x2_config_port,
+	.verify_port	= sc16is7x2_verify_port,
+};
+
+
+/* ******************************** GPIO ********************************* */
+
+#ifdef CONFIG_GPIOLIB
+
+static int sc16is7x2_gpio_request(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	int control = (offset < 4) ? IOC_GPIO30 : IOC_GPIO74;
+	int ret = 0;
+
+	BUG_ON(offset > 8);
+	dev_dbg(&ts->spi->dev, "%s: offset = %d\n", __func__, offset);
+
+	mutex_lock(&ts->io_lock);
+
+	/* GPIO 0:3 and 4:7 can only be controlled as block */
+	ts->io_gpio |= BIT(offset);
+	if (ts->io_control & control) {
+		dev_dbg(&ts->spi->dev, "activate GPIOs %s\n",
+				(offset < 4) ? "0-3" : "4-7");
+		ts->io_control &= ~control;
+		ret = sc16is7x2_write(ts, REG_IOC, 0, ts->io_control);
+	}
+
+	mutex_unlock(&ts->io_lock);
+
+	return ret;
+}
+
+static void sc16is7x2_gpio_free(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	int control = (offset < 4) ? IOC_GPIO30 : IOC_GPIO74;
+	int mask = (offset < 4) ? 0x0f : 0xf0;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->io_lock);
+
+	/* GPIO 0:3 and 4:7 can only be controlled as block */
+	ts->io_gpio &= ~BIT(offset);
+	dev_dbg(&ts->spi->dev, "%s: io_gpio = 0x%02X\n", __func__, ts->io_gpio);
+	if (!(ts->io_control & control) && !(ts->io_gpio & mask)) {
+		dev_dbg(&ts->spi->dev, "deactivate GPIOs %s\n",
+				(offset < 4) ? "0-3" : "4-7");
+		ts->io_control |= control;
+		sc16is7x2_write(ts, REG_IOC, 0, ts->io_control);
+	}
+
+	mutex_unlock(&ts->io_lock);
+}
+
+static int sc16is7x2_direction_input(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	unsigned io_dir;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->io_lock);
+
+	ts->io_dir &= ~BIT(offset);
+	io_dir = ts->io_dir;
+
+	mutex_unlock(&ts->io_lock);
+
+	return sc16is7x2_write(ts, REG_IOD, 0, io_dir);
+}
+
+static int sc16is7x2_direction_output(struct gpio_chip *gpio, unsigned offset,
+				    int value)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->io_lock);
+
+	if (value)
+		ts->io_state |= BIT(offset);
+	else
+		ts->io_state &= ~BIT(offset);
+
+	ts->io_dir |= BIT(offset);
+
+	mutex_unlock(&ts->io_lock);
+
+	sc16is7x2_write(ts, REG_IOS, 0, ts->io_state);
+	return sc16is7x2_write(ts, REG_IOD, 0, ts->io_dir);
+}
+
+static int sc16is7x2_get(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	int level = -EINVAL;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->io_lock);
+
+	if (ts->io_dir & BIT(offset)) {
+		/* Output: return cached level */
+		level = (ts->io_state >> offset) & 0x01;
+	} else {
+		/* Input: read out all pins */
+		level = sc16is7x2_read(ts, REG_IOS, 0);
+		if (level >= 0) {
+			ts->io_state = level;
+			level = (ts->io_state >> offset) & 0x01;
+		}
+	}
+
+	mutex_unlock(&ts->io_lock);
+
+	return level;
+}
+
+static void sc16is7x2_set(struct gpio_chip *gpio, unsigned offset, int value)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	unsigned io_state;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->io_lock);
+
+	if (value)
+		ts->io_state |= BIT(offset);
+	else
+		ts->io_state &= ~BIT(offset);
+	io_state = ts->io_state;
+
+	mutex_unlock(&ts->io_lock);
+
+	sc16is7x2_write(ts, REG_IOS, 0, io_state);
+}
+
+#endif /* CONFIG_GPIOLIB */
+
+/* ******************************** INIT ********************************* */
+
+static struct uart_driver sc16is7x2_uart_driver;
+
+static int sc16is7x2_register_gpio(struct sc16is7x2_chip *ts,
+		struct sc16is7x2_platform_data *pdata)
+{
+#ifdef CONFIG_GPIOLIB
+	ts->gpio.label = (pdata->label) ? pdata->label : DRIVER_NAME;
+	ts->gpio.request	= sc16is7x2_gpio_request;
+	ts->gpio.free		= sc16is7x2_gpio_free;
+	ts->gpio.get		= sc16is7x2_get;
+	ts->gpio.set		= sc16is7x2_set;
+	ts->gpio.direction_input = sc16is7x2_direction_input;
+	ts->gpio.direction_output = sc16is7x2_direction_output;
+
+	ts->gpio.base = pdata->gpio_base;
+	ts->gpio.names = pdata->names;
+	ts->gpio.ngpio = SC16IS7X2_NR_GPIOS;
+	ts->gpio.can_sleep = 1;
+	ts->gpio.dev = &ts->spi->dev;
+	ts->gpio.owner = THIS_MODULE;
+
+	mutex_init(&ts->io_lock);
+
+	/* disable all GPIOs, enable on request */
+	ts->io_gpio = 0;
+	ts->io_control = IOC_GPIO30 | IOC_GPIO74;
+	ts->io_state = 0;
+	ts->io_dir = 0;
+
+	sc16is7x2_write(ts, REG_IOI, 0, 0); /* no support for irqs yet */
+	sc16is7x2_write(ts, REG_IOC, 0, ts->io_control);
+	sc16is7x2_write(ts, REG_IOS, 0, ts->io_state);
+	sc16is7x2_write(ts, REG_IOD, 0, ts->io_dir);
+
+	return gpiochip_add(&ts->gpio);
+#else
+	return 0;
+#endif
+}
+
+static int sc16is7x2_register_uart_port(struct sc16is7x2_chip *ts,
+		struct sc16is7x2_platform_data *pdata, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
+	struct uart_port *uart = &chan->uart;
+
+	/* Disable irqs and go to sleep */
+	sc16is7x2_write(ts, UART_IER, ch, UART_IERX_SLEEP);
+
+	chan->chip = ts;
+
+	uart->irq = ts->spi->irq;
+	uart->uartclk = pdata->uartclk;
+	uart->fifosize = FIFO_SIZE;
+	uart->ops = &sc16is7x2_uart_ops;
+	uart->flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF;
+	uart->line = pdata->uart_base + ch;
+	uart->type = PORT_SC16IS7X2;
+	uart->dev = &ts->spi->dev;
+
+	return uart_add_one_port(&sc16is7x2_uart_driver, uart);
+}
+
+static int __devinit sc16is7x2_probe(struct spi_device *spi)
+{
+	struct sc16is7x2_chip *ts;
+	struct sc16is7x2_platform_data *pdata;
+	int ret;
+
+	/* Only even uart base numbers are supported */
+	pdata = spi->dev.platform_data;
+	if (!pdata || !pdata->gpio_base || pdata->uart_base & 1) {
+		dev_dbg(&spi->dev, "incorrect or missing platform data\n");
+		return -EINVAL;
+	}
+
+	ts = kzalloc(sizeof(struct sc16is7x2_chip), GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, ts);
+	ts->spi = spi;
+
+	/* Reset the chip */
+	sc16is7x2_write(ts, REG_IOC, 0, IOC_SRESET);
+
+	ret = sc16is7x2_register_uart_port(ts, pdata, 0);
+	if (ret)
+		goto exit_destroy;
+	ret = sc16is7x2_register_uart_port(ts, pdata, 1);
+	if (ret)
+		goto exit_uart0;
+
+	ret = sc16is7x2_register_gpio(ts, pdata);
+	if (ret)
+		goto exit_uart1;
+
+	dev_info(&spi->dev, DRIVER_NAME " at CS%d (irq %d), 2 UARTs, 8 GPIOs\n"
+			"    eser%d, eser%d, gpiochip%d\n",
+			spi->chip_select, spi->irq,
+			pdata->uart_base, pdata->uart_base + 1,
+			pdata->gpio_base);
+
+	return 0;
+
+exit_uart1:
+	uart_remove_one_port(&sc16is7x2_uart_driver, &ts->channel[1].uart);
+
+exit_uart0:
+	uart_remove_one_port(&sc16is7x2_uart_driver, &ts->channel[0].uart);
+
+exit_destroy:
+	dev_set_drvdata(&spi->dev, NULL);
+	kfree(ts);
+	return ret;
+}
+
+static int __devexit sc16is7x2_remove(struct spi_device *spi)
+{
+	struct sc16is7x2_chip *ts = spi_get_drvdata(spi);
+	int ret;
+
+	if (ts == NULL)
+		return -ENODEV;
+
+	ret = uart_remove_one_port(&sc16is7x2_uart_driver, &ts->channel[0].uart);
+	if (ret)
+		return ret;
+
+	ret = uart_remove_one_port(&sc16is7x2_uart_driver, &ts->channel[1].uart);
+	if (ret)
+		return ret;
+
+#ifdef CONFIG_GPIOLIB
+	ret = gpiochip_remove(&ts->gpio);
+	if (ret)
+		return ret;
+#endif
+
+	kfree(ts);
+
+	return 0;
+}
+
+static struct uart_driver sc16is7x2_uart_driver = {
+	.owner          = THIS_MODULE,
+	.driver_name    = DRIVER_NAME,
+	.dev_name       = "eser",
+	.nr             = MAX_SC16IS7X2,
+};
+
+/* Spi driver data */
+static struct spi_driver sc16is7x2_spi_driver = {
+	.driver = {
+		.name		= DRIVER_NAME,
+		.bus		= &spi_bus_type,
+		.owner		= THIS_MODULE,
+	},
+	.probe		= sc16is7x2_probe,
+	.remove		= __devexit_p(sc16is7x2_remove),
+};
+
+/* Driver init function */
+static int __init sc16is7x2_init(void)
+{
+	int ret = uart_register_driver(&sc16is7x2_uart_driver);
+	if (ret)
+		return ret;
+
+	return spi_register_driver(&sc16is7x2_spi_driver);
+}
+
+/* Driver exit function */
+static void __exit sc16is7x2_exit(void)
+{
+	spi_unregister_driver(&sc16is7x2_spi_driver);
+	uart_unregister_driver(&sc16is7x2_uart_driver);
+}
+
+/* register after spi postcore initcall and before
+ * subsys initcalls that may rely on these GPIOs
+ */
+subsys_initcall(sc16is7x2_init);
+module_exit(sc16is7x2_exit);
+
+MODULE_AUTHOR("Manuel Stahl");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("SC16IS7x2 SPI based UART chip");
+MODULE_ALIAS("spi:" DRIVER_NAME);
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index c91ace7..e535142 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -210,6 +210,9 @@
 /* Atheros AR933X SoC */
 #define PORT_AR933X	99
 
+/* NXP SC16IS7X2 */
+#define PORT_SC16IS7X2 100
+
 
 #ifdef __KERNEL__
 
diff --git a/include/linux/serial_sc16is7x2.h b/include/linux/serial_sc16is7x2.h
new file mode 100755
index 0000000..931fe50
--- /dev/null
+++ b/include/linux/serial_sc16is7x2.h
@@ -0,0 +1,17 @@
+#ifndef LINUX_SPI_SC16IS752_H
+#define LINUX_SPI_SC16IS752_H
+
+#define SC16IS7X2_NR_GPIOS 8
+
+struct sc16is7x2_platform_data {
+	unsigned int	uartclk;
+	/* uart line number of the first channel */
+	unsigned	uart_base;
+	/* number assigned to the first GPIO */
+	unsigned	gpio_base;
+	char		*label;
+	/* list of GPIO names (array length = SC16IS7X2_NR_GPIOS) */
+	const char	*const *names;
+};
+
+#endif
-- 
1.7.9


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH] Add sc16is7x2 driver
@ 2010-08-31 11:26 Manuel Stahl
  0 siblings, 0 replies; 12+ messages in thread
From: Manuel Stahl @ 2010-08-31 11:26 UTC (permalink / raw)
  To: linux-serial

[-- Attachment #1: Type: text/plain, Size: 39379 bytes --]

This patch adds support for the sc16is7x2 chips.

Signed-off-by: Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
---
  drivers/serial/Kconfig        |    7 +
  drivers/serial/Makefile       |    1 +
  drivers/serial/sc16is7x2.c    | 1351 
+++++++++++++++++++++++++++++++++++++++++
  include/linux/serial_core.h   |    3 +
  include/linux/spi/sc16is7x2.h |   17 +
  5 files changed, 1379 insertions(+), 0 deletions(-)

diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 12900f7..c336ea6 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -269,6 +269,13 @@ config SERIAL_8250_RM9K

  comment "Non-8250 serial port support"

+config SERIAL_SC16IS7X2
+	tristate "SC16IS7x2 chips"
+	depends on SPI_MASTER && GPIOLIB
+	select SERIAL_CORE
+	help
+	  gpio driver for SC16IS7x2 SPI UARTs.
+
  config SERIAL_AMBA_PL010
  	tristate "ARM AMBA PL010 serial port support"
  	depends on ARM_AMBA && (BROKEN || !ARCH_VERSATILE)
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index 1ca4fd5..4c6a297 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -28,6 +28,7 @@ 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_MCA) += 8250_mca.o
+obj-$(CONFIG_SERIAL_SC16IS7X2)	+= sc16is7x2.o
  obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
  obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
  obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o
diff --git a/drivers/serial/sc16is7x2.c b/drivers/serial/sc16is7x2.c
new file mode 100644
index 0000000..3ce3099
--- /dev/null
+++ b/drivers/serial/sc16is7x2.c
@@ -0,0 +1,1351 @@
+/**
+ * drivers/serial/sc16is7x2.c
+ *
+ * Copyright (C) 2009 Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
+ *
+ * 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.
+ *
+ * The SC16IS7x2 device is a SPI driven dual UART with GPIOs.
+ *
+ * The driver exports two uarts and a gpiochip interface.
+ */
+
+/* #define DEBUG */
+
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/spi/spi.h>
+#include <linux/freezer.h>
+#include <linux/spi/sc16is7x2.h>
+#include <linux/serial_core.h>
+#include <linux/serial_reg.h>
+#include <linux/gpio.h>
+
+#define SC16IS7X2_MAJOR		204
+#define SC16IS7X2_MINOR		209
+#define MAX_SC16IS7X2		8
+#define FIFO_SIZE		64
+
+#define DRIVER_NAME		"sc16is7x2"
+#define TYPE_NAME		"SC16IS7x2"
+
+
+
+#define REG_READ	0x80
+#define REG_WRITE	0x00
+
+/* Special registers */
+#define REG_TXLVL	0x08	/* Transmitter FIFO Level register */
+#define REG_RXLVL	0x09	/* Receiver FIFO Level register */
+#define REG_IOD		0x0A	/* IO Direction register */
+#define REG_IOS		0x0B	/* IO State register */
+#define REG_IOI		0x0C	/* IO Interrupt Enable register */
+#define REG_IOC		0x0E	/* IO Control register */
+
+#define IOC_SRESET	0x08    /* Software reset */
+#define IOC_GPIO30	0x04    /* GPIO 3:0 unset: as IO, set: as modem pins */
+#define IOC_GPIO74	0x02    /* GPIO 7:4 unset: as IO, set: as modem pins */
+#define IOC_IOLATCH	0x01    /* Unset: input unlatched, set: input 
latched */
+
+/* Redefine some MCR bits */
+#ifdef UART_MCR_TCRTLR
+#undef UART_MCR_TCRTLR
+#endif
+#define UART_MCR_TCRTLR		0x04
+#define UART_MCR_IRDA		0x40
+
+
+#define WRITE_CMD(reg, ch) (REG_WRITE | (reg & 0xf) << 3 | (ch & 0x1) << 1)
+#define READ_CMD(reg, ch)  (REG_READ  | (reg & 0xf) << 3 | (ch & 0x1) << 1)
+
+/* 16bit SPI command to read or write a register */
+struct sc16is7x2_spi_reg {
+	u8 cmd;
+	u8 value;
+} __attribute__ ((packed));
+
+struct sc16is7x2_chip;
+
+/*
+ * Some registers must be read back to modify.
+ * To save time we cache them here in memory
+ */
+struct sc16is7x2_channel {
+	struct sc16is7x2_chip	*chip;	/* back link */
+	struct mutex		lock;
+	struct uart_port 	uart;
+	struct spi_transfer fifo_rx;
+	struct spi_transfer fifo_tx[3];
+	u8		iir;
+	u8		lsr;
+	u8		msr;
+	u8		ier;		/* cache for IER register */
+	u8		fcr;		/* cache for FCR register */
+	u8		lcr;		/* cache for LCR register */
+	u8		mcr;		/* cache for MCR register */
+	u8		rxbuf[FIFO_SIZE+1];
+	u8		write_fifo_cmd;
+	u8		read_fifo_cmd;
+	bool		active;
+};
+
+struct sc16is7x2_chip {
+	struct spi_device *spi;
+	struct gpio_chip gpio;
+	struct mutex	 lock;
+	struct sc16is7x2_channel channel[2];
+
+	/* for handling irqs: need workqueue since we do spi_sync */
+	struct workqueue_struct *workqueue;
+	struct work_struct work;
+	/* set to 1 to make the workhandler exit as soon as possible */
+	int force_end_work;
+	/* need to know we are suspending to avoid deadlock on workqueue */
+	int suspending;
+
+	struct spi_message fifo_message;
+
+#define UART_BUG_TXEN	BIT(1)	/* UART has buggy TX IIR status */
+#define UART_BUG_NOMSR	BIT(2)	/* UART has buggy MSR status bits (Au1x00) */
+#define UART_BUG_THRE	BIT(3)	/* UART has buggy THRE reassertion */
+	u16		bugs;		/* port bugs */
+
+#define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS
+	u8		lsr_saved_flags;
+#define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA
+	u8		msr_saved_flags;
+	u8		io_dir;		/* cache for IODir register */
+	u8		io_state;	/* cache for IOState register */
+	u8		io_gpio;	/* PIN is GPIO */
+	u8		io_control;	/* cache for IOControl register */
+};
+
+/* ******************************** SPI 
********************************* */
+
+
+/*
+ * Reserve memory for command sequence
+ * @param cnt number of commands
+ */
+static inline struct sc16is7x2_spi_reg *
+sc16is7x2_alloc_spi_cmds(unsigned cnt)
+{
+	return kzalloc(sizeof(struct sc16is7x2_spi_reg)*cnt, GFP_KERNEL);
+}
+
+/*
+ * sc16is7x2_add_write_cmd - Add write command to sequence
+ */
+static inline void sc16is7x2_add_write_cmd(struct sc16is7x2_spi_reg *cmd,
+		u8 reg, u8 ch, u8 value)
+{
+	cmd->cmd = WRITE_CMD(reg, ch);
+	cmd->value = value;
+}
+
+/*
+ * sc16is7x2_add_read_cmd - Add read command to sequence
+ */
+static inline void sc16is7x2_add_read_cmd(struct sc16is7x2_spi_reg *cmd,
+		u8 reg, u8 ch)
+{
+	cmd->cmd = READ_CMD(reg, ch);
+	cmd->value = 0;
+}
+
+/*
+ * sc16is7x2_complete - Completion handler for async SPI transfers
+ */
+static void sc16is7x2_complete(void *context)
+{
+	struct spi_message *m = context;
+	u8 *tx_chain = m->state;
+
+	kfree(tx_chain);
+	kfree(m);
+}
+
+/*
+ * sc16is7x2_spi_async - Send command sequence
+ */
+static int sc16is7x2_spi_async(struct spi_device *spi,
+		struct sc16is7x2_spi_reg *cmds, unsigned len)
+{
+	struct spi_transfer *t;
+	struct spi_message *m;
+
+	m = spi_message_alloc(len, GFP_KERNEL);
+	if (!m)
+		return -ENOMEM;
+
+	m->complete = sc16is7x2_complete;
+	m->context = m;
+	m->state = cmds;
+	list_for_each_entry(t, &m->transfers, transfer_list) {
+		t->tx_buf = (u8 *)cmds;
+		t->len = 2;
+		t->cs_change = true;
+		cmds++;
+	}
+
+	return spi_async(spi, m);
+}
+
+/*
+ * sc16is7x2_write_async - Write a new register content (async)
+ */
+static inline int sc16is7x2_write_async(struct spi_device *spi, u8 reg, 
u8 ch,
+		u8 value)
+{
+	struct sc16is7x2_spi_reg *cmd = sc16is7x2_alloc_spi_cmds(1);
+	if (!cmd)
+		return -ENOMEM;
+	sc16is7x2_add_write_cmd(cmd, reg, ch, value);
+	return sc16is7x2_spi_async(spi, cmd, 1);
+}
+
+/*
+ * sc16is7x2_write - Write a new register content (sync)
+ */
+static int sc16is7x2_write(struct spi_device *spi, u8 reg, u8 ch, u8 val)
+{
+	u16 word = REG_WRITE | (reg & 0xf) << 3 | (ch & 0x3) << 1 | val << 8;
+	return spi_write(spi, (const u8 *)&word, sizeof(word));
+}
+
+/**
+ * sc16is7x2_read - Read back register content
+ * @spi: The SPI device
+ * @reg: Register offset
+ *
+ * Returns positive 8 bit value from device if successful or a
+ * negative value on error
+ */
+static int sc16is7x2_read(struct spi_device *spi, unsigned reg, 
unsigned ch)
+{
+	u8 cmd = REG_READ | (reg & 0xf) << 3 | (ch & 0x3) << 1;
+	return spi_w8r8(spi, cmd);
+}
+
+/* ******************************** UART 
********************************* */
+
+/* Uart divisor latch write */
+static inline void sc16is7x2_add_dl_write_cmd(struct sc16is7x2_spi_reg 
*cmd,
+		u8 ch, int value)
+{
+	sc16is7x2_add_write_cmd(&cmd[0], UART_DLL, ch, value & 0xff);
+	sc16is7x2_add_write_cmd(&cmd[1], UART_DLM, ch, value >> 8 & 0xff);
+}
+
+static unsigned int sc16is7x2_tx_empty(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned lsr;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	mutex_lock(&chan->lock);
+	lsr = chan->lsr;
+	mutex_unlock(&chan->lock);
+
+	return lsr & UART_LSR_TEMT ? TIOCSER_TEMT : 0;
+}
+
+static unsigned int sc16is7x2_get_mctrl(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned int status;
+	unsigned int ret;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	status = chan->msr;
+
+	ret = 0;
+	if (status & UART_MSR_DCD)
+		ret |= TIOCM_CAR;
+	if (status & UART_MSR_RI)
+		ret |= TIOCM_RNG;
+	if (status & UART_MSR_DSR)
+		ret |= TIOCM_DSR;
+	if (status & UART_MSR_CTS)
+		ret |= TIOCM_CTS;
+	return ret;
+}
+
+static unsigned int __set_mctrl(unsigned int mctrl)
+{
+	unsigned char mcr = 0;
+
+	if (mctrl & TIOCM_RTS)
+		mcr |= UART_MCR_RTS;
+	if (mctrl & TIOCM_DTR)
+		mcr |= UART_MCR_DTR;
+	if (mctrl & TIOCM_LOOP)
+		mcr |= UART_MCR_LOOP;
+
+	return mcr;
+}
+
+static void sc16is7x2_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	sc16is7x2_write_async(ts->spi, UART_MCR, ch, __set_mctrl(mctrl));
+}
+
+static inline void __stop_tx(struct sc16is7x2_channel *chan)
+{
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = chan->uart.line & 0x01;
+	if (chan->ier & UART_IER_THRI) {
+		chan->ier &= ~UART_IER_THRI;
+		sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
+	}
+}
+
+static void sc16is7x2_stop_tx(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	__stop_tx(chan);
+}
+
+static void sc16is7x2_start_tx(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	if (!(chan->ier & UART_IER_THRI)) {
+		chan->ier |= UART_IER_THRI;
+		sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
+	}
+}
+
+static void sc16is7x2_stop_rx(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	chan->ier &= ~UART_IER_RLSI;
+	chan->uart.read_status_mask &= ~UART_LSR_DR;
+	sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
+}
+
+static void sc16is7x2_enable_ms(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	chan->ier |= UART_IER_MSI;
+	sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
+}
+
+static void sc16is7x2_break_ctl(struct uart_port *port, int break_state)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+	unsigned long flags;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	spin_lock_irqsave(&chan->uart.lock, flags);
+	if (break_state == -1)
+		chan->lcr |= UART_LCR_SBC;
+	else
+		chan->lcr &= ~UART_LCR_SBC;
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	sc16is7x2_write_async(ts->spi, UART_LCR, ch, chan->lcr);
+}
+
+static int sc16is7x2_startup(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned ch = port->line & 0x01;
+	struct sc16is7x2_spi_reg *cmds, *cmd;
+	unsigned long flags;
+
+	dev_dbg(&ts->spi->dev, "%s (line %d)\n", __func__, port->line);
+
+	spin_lock_irqsave(&chan->uart.lock, flags);
+	chan->lcr = UART_LCR_WLEN8;
+	chan->mcr = __set_mctrl(chan->uart.mctrl);
+	chan->fcr = 0;
+	chan->ier = UART_IER_RLSI | UART_IER_RDI;
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	cmds = sc16is7x2_alloc_spi_cmds(8);
+	if (!cmds)
+		return -ENOMEM;
+
+	cmd = cmds;
+	/* Clear the interrupt registers. */
+	sc16is7x2_add_write_cmd(cmd, UART_IER, ch, 0);
+	sc16is7x2_add_read_cmd(++cmd, UART_IIR, ch);
+	sc16is7x2_add_read_cmd(++cmd, UART_LSR, ch);
+	sc16is7x2_add_read_cmd(++cmd, UART_MSR, ch);
+
+	sc16is7x2_add_write_cmd(++cmd, UART_FCR, ch, UART_FCR_ENABLE_FIFO |
+		       UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+	sc16is7x2_add_write_cmd(++cmd, UART_FCR, ch, chan->fcr);
+	/* Now, initialize the UART */
+	sc16is7x2_add_write_cmd(++cmd, UART_LCR, ch, chan->lcr);
+	sc16is7x2_add_write_cmd(++cmd, UART_MCR, ch, chan->mcr);
+
+	sc16is7x2_spi_async(ts->spi, cmds, 8);
+
+	chan->active = true;
+	return 0;
+}
+
+static void sc16is7x2_shutdown(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	unsigned long flags;
+	unsigned ch = port->line & 0x01;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	BUG_ON(!chan);
+	BUG_ON(!ts);
+
+	if (ts->suspending)
+		return;
+
+	/* Disable interrupts from this port */
+	chan->ier = 0;
+	chan->active = false;
+	sc16is7x2_write(ts->spi, UART_IER, ch, chan->ier);
+
+	/* Wait for worker of this channel to finish */
+	mutex_lock(&chan->lock);
+
+	spin_lock_irqsave(&chan->uart.lock, flags);
+	chan->mcr = __set_mctrl(chan->uart.mctrl);
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	/* Disable break condition and FIFOs */
+	chan->lcr &= ~UART_LCR_SBC;
+
+	sc16is7x2_write(ts->spi, UART_MCR, ch, chan->mcr);
+	sc16is7x2_write(ts->spi, UART_LCR, ch, chan->lcr);
+
+	mutex_unlock(&chan->lock);
+}
+
+static void
+sc16is7x2_set_termios(struct uart_port *port, struct ktermios *termios,
+		       struct ktermios *old)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	struct sc16is7x2_spi_reg *cmds;
+	unsigned ch = port->line & 0x01;
+	unsigned long flags;
+	unsigned int baud, quot;
+	u8 ier, mcr, lcr, fcr = 0;
+	u8 efr = UART_EFR_ECB;
+
+	/* set word length */
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:
+		lcr = UART_LCR_WLEN5;
+		break;
+	case CS6:
+		lcr = UART_LCR_WLEN6;
+		break;
+	case CS7:
+		lcr = UART_LCR_WLEN7;
+		break;
+	default:
+	case CS8:
+		lcr = UART_LCR_WLEN8;
+		break;
+	}
+
+	if (termios->c_cflag & CSTOPB)
+		lcr |= UART_LCR_STOP;
+	if (termios->c_cflag & PARENB)
+		lcr |= UART_LCR_PARITY;
+	if (!(termios->c_cflag & PARODD))
+		lcr |= UART_LCR_EPAR;
+#ifdef CMSPAR
+	if (termios->c_cflag & CMSPAR)
+		lcr |= UART_LCR_SPAR;
+#endif
+
+	/* Ask the core to calculate the divisor for us. */
+	baud = uart_get_baud_rate(port, termios, old,
+				  port->uartclk / 16 / 0xffff,
+				  port->uartclk / 16);
+	quot = uart_get_divisor(port, baud);
+
+	dev_dbg(&ts->spi->dev, "%s (baud %u)\n", __func__, baud);
+
+
+	/* configure the fifo */
+	if (baud < 2400)
+		fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1;
+	else
+		fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_01;
+
+	/*
+	 * MCR-based auto flow control.  When AFE is enabled, RTS will be
+	 * deasserted when the receive FIFO contains more characters than
+	 * the trigger, or the MCR RTS bit is cleared.  In the case where
+	 * the remote UART is not using CTS auto flow control, we must
+	 * have sufficient FIFO entries for the latency of the remote
+	 * UART to respond.  IOW, at least 32 bytes of FIFO.
+	 */
+	chan->mcr &= ~UART_MCR_AFE;
+	if (termios->c_cflag & CRTSCTS)
+		chan->mcr |= UART_MCR_AFE;
+
+	/*
+	 * Ok, we're now changing the port state.  Do it with
+	 * interrupts disabled.
+	 */
+	spin_lock_irqsave(&chan->uart.lock, flags);
+
+	/* we are sending char from a workqueue so enable */
+	chan->uart.state->port.tty->low_latency = 1;
+
+	/* Update the per-port timeout. */
+	uart_update_timeout(port, termios->c_cflag, baud);
+
+	chan->uart.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+	if (termios->c_iflag & INPCK)
+		chan->uart.read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+	if (termios->c_iflag & (BRKINT | PARMRK))
+		chan->uart.read_status_mask |= UART_LSR_BI;
+
+	/* Characters to ignore */
+	chan->uart.ignore_status_mask = 0;
+	if (termios->c_iflag & IGNPAR)
+		chan->uart.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+	if (termios->c_iflag & IGNBRK) {
+		chan->uart.ignore_status_mask |= UART_LSR_BI;
+		/*
+		 * If we're ignoring parity and break indicators,
+		 * ignore overruns too (for real raw support).
+		 */
+		if (termios->c_iflag & IGNPAR)
+			chan->uart.ignore_status_mask |= UART_LSR_OE;
+	}
+
+	/* ignore all characters if CREAD is not set */
+	if ((termios->c_cflag & CREAD) == 0)
+		chan->uart.ignore_status_mask |= UART_LSR_DR;
+
+	/* CTS flow control flag and modem status interrupts */
+	chan->ier &= ~UART_IER_MSI;
+	if (UART_ENABLE_MS(&chan->uart, termios->c_cflag))
+		chan->ier |= UART_IER_MSI;
+
+	if (termios->c_cflag & CRTSCTS)
+		efr |= UART_EFR_CTS | UART_EFR_RTS;
+
+	mcr = __set_mctrl(chan->uart.mctrl);
+	ier = chan->ier;
+	chan->lcr = lcr;				/* Save LCR */
+	chan->fcr = fcr;				/* Save FCR */
+	chan->mcr = mcr;				/* Save MCR */
+
+	fcr |= UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT;
+
+	spin_unlock_irqrestore(&chan->uart.lock, flags);
+
+	/* build a compound spi message to set all registers */
+	cmds = sc16is7x2_alloc_spi_cmds(9);
+	if (!cmds)
+		return;
+
+	/* set DLAB */
+	sc16is7x2_add_write_cmd(&cmds[0], UART_LCR, ch, UART_LCR_DLAB);
+	/* set divisor, must be set before UART_EFR_ECB */
+	sc16is7x2_add_dl_write_cmd(&cmds[1], ch, quot);
+	sc16is7x2_add_write_cmd(&cmds[3], UART_LCR, ch, 0xBF);	/* access EFR */
+	sc16is7x2_add_write_cmd(&cmds[4], UART_EFR, ch, efr);
+	sc16is7x2_add_write_cmd(&cmds[5], UART_LCR, ch, lcr);	/* reset DLAB */
+	sc16is7x2_add_write_cmd(&cmds[6], UART_FCR, ch, fcr);
+	sc16is7x2_add_write_cmd(&cmds[7], UART_MCR, ch, mcr);
+	sc16is7x2_add_write_cmd(&cmds[8], UART_IER, ch, ier);
+
+	sc16is7x2_spi_async(ts->spi, cmds, 9);
+
+	/* Don't rewrite B0 */
+	if (tty_termios_baud_rate(termios))
+		tty_termios_encode_baud_rate(termios, baud, baud);
+}
+
+static const char *
+sc16is7x2_type(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	return TYPE_NAME;
+}
+
+static void sc16is7x2_release_port(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	ts->force_end_work = 1;
+}
+
+static int sc16is7x2_request_port(struct uart_port *port)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	return 0;
+}
+
+static void sc16is7x2_config_port(struct uart_port *port, int flags)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	if (flags & UART_CONFIG_TYPE)
+		chan->uart.type = PORT_SC16IS7X2;
+}
+
+static int
+sc16is7x2_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+	struct sc16is7x2_channel *chan =
+			container_of(port, struct sc16is7x2_channel, uart);
+	struct sc16is7x2_chip *ts = chan->chip;
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	if (ser->irq < 0 || ser->baud_base < 9600 ||
+			ser->type != PORT_SC16IS7X2)
+		return -EINVAL;
+	return 0;
+}
+
+static struct uart_ops sc16is7x2_uart_ops = {
+	.tx_empty	= sc16is7x2_tx_empty,
+	.set_mctrl	= sc16is7x2_set_mctrl,
+	.get_mctrl	= sc16is7x2_get_mctrl,
+	.stop_tx        = sc16is7x2_stop_tx,
+	.start_tx	= sc16is7x2_start_tx,
+	.stop_rx	= sc16is7x2_stop_rx,
+	.enable_ms      = sc16is7x2_enable_ms,
+	.break_ctl      = sc16is7x2_break_ctl,
+	.startup	= sc16is7x2_startup,
+	.shutdown	= sc16is7x2_shutdown,
+	.set_termios	= sc16is7x2_set_termios,
+	.type		= sc16is7x2_type,
+	.release_port   = sc16is7x2_release_port,
+	.request_port   = sc16is7x2_request_port,
+	.config_port	= sc16is7x2_config_port,
+	.verify_port	= sc16is7x2_verify_port,
+};
+
+
+#define MIN(a, b) ((a < b) ? (a) : (b))
+
+/* ******************************** GPIO 
********************************* */
+
+static int sc16is7x2_gpio_request(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	int control = (offset < 4) ? IOC_GPIO30 : IOC_GPIO74;
+	int ret = 0;
+
+	BUG_ON(offset > 8);
+	dev_dbg(&ts->spi->dev, "%s: offset = %d\n", __func__, offset);
+
+	mutex_lock(&ts->lock);
+
+	/* GPIO 0:3 and 4:7 can only be controlled as block */
+	ts->io_gpio |= BIT(offset);
+	if (ts->io_control & control) {
+		dev_dbg(&ts->spi->dev, "activate GPIOs %s\n",
+				(offset < 4) ? "0-3" : "4-7");
+		ts->io_control &= ~control;
+		ret = sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control);
+	}
+
+	mutex_unlock(&ts->lock);
+
+	return ret;
+}
+
+static void sc16is7x2_gpio_free(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	int control = (offset < 4) ? IOC_GPIO30 : IOC_GPIO74;
+	int mask = (offset < 4) ? 0x0f : 0xf0;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	/* GPIO 0:3 and 4:7 can only be controlled as block */
+	ts->io_gpio &= ~BIT(offset);
+	dev_dbg(&ts->spi->dev, "%s: io_gpio = 0x%02X\n", __func__, ts->io_gpio);
+	if (!(ts->io_control & control) && !(ts->io_gpio & mask)) {
+		dev_dbg(&ts->spi->dev, "deactivate GPIOs %s\n",
+				(offset < 4) ? "0-3" : "4-7");
+		ts->io_control |= control;
+		sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control);
+	}
+
+	mutex_unlock(&ts->lock);
+}
+
+static int sc16is7x2_direction_input(struct gpio_chip *gpio, unsigned 
offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	unsigned io_dir;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	ts->io_dir &= ~BIT(offset);
+	io_dir = ts->io_dir;
+
+	mutex_unlock(&ts->lock);
+
+	return sc16is7x2_write_async(ts->spi, REG_IOD, 0, io_dir);
+}
+
+static int sc16is7x2_direction_output(struct gpio_chip *gpio, unsigned 
offset,
+				    int value)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	struct sc16is7x2_spi_reg *cmds;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	if (value)
+		ts->io_state |= BIT(offset);
+	else
+		ts->io_state &= ~BIT(offset);
+
+	ts->io_dir |= BIT(offset);
+
+	cmds = sc16is7x2_alloc_spi_cmds(2);
+	if (cmds) {
+		sc16is7x2_add_write_cmd(&cmds[0], REG_IOS, 0, ts->io_state);
+		sc16is7x2_add_write_cmd(&cmds[1], REG_IOD, 0, ts->io_dir);
+	}
+
+	mutex_unlock(&ts->lock);
+
+	return sc16is7x2_spi_async(ts->spi, cmds, 2);
+}
+
+static int sc16is7x2_get(struct gpio_chip *gpio, unsigned offset)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	int level = -EINVAL;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	if (ts->io_dir & BIT(offset)) {
+		/* Output: return cached level */
+		level = (ts->io_state >> offset) & 0x01;
+	} else {
+		/* Input: read out all pins */
+		level = sc16is7x2_read(ts->spi, REG_IOS, 0);
+		if (level >= 0) {
+			ts->io_state = level;
+			level = (ts->io_state >> offset) & 0x01;
+		}
+	}
+
+	mutex_unlock(&ts->lock);
+
+	return level;
+}
+
+static void sc16is7x2_set(struct gpio_chip *gpio, unsigned offset, int 
value)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(gpio, struct sc16is7x2_chip, gpio);
+	unsigned io_state;
+
+	BUG_ON(offset > 8);
+
+	mutex_lock(&ts->lock);
+
+	if (value)
+		ts->io_state |= BIT(offset);
+	else
+		ts->io_state &= ~BIT(offset);
+	io_state = ts->io_state;
+
+	mutex_unlock(&ts->lock);
+
+	sc16is7x2_write_async(ts->spi, REG_IOS, 0, io_state);
+}
+
+/* ******************************** IRQ 
********************************* */
+
+static void sc16is7x2_handle_fifo_rx(struct sc16is7x2_channel *chan)
+{
+	struct uart_port *uart = &chan->uart;
+	struct tty_struct *tty = uart->state->port.tty;
+	u8 *rxbuf = chan->rxbuf;
+	u8 lsr = chan->lsr;
+	unsigned i, count = chan->fifo_rx.len;
+	unsigned long flags;
+	char flag = TTY_NORMAL;
+
+	spin_lock_irqsave(&uart->lock, flags);
+
+	if (unlikely(lsr & UART_LSR_BRK_ERROR_BITS)) {
+		/*
+		 * For statistics only
+		 */
+		if (lsr & UART_LSR_BI) {
+			lsr &= ~(UART_LSR_FE | UART_LSR_PE);
+			chan->uart.icount.brk++;
+			/*
+			 * We do the SysRQ and SAK checking
+			 * here because otherwise the break
+			 * may get masked by ignore_status_mask
+			 * or read_status_mask.
+			 */
+			if (uart_handle_break(&chan->uart))
+				goto ignore_char;
+		} else if (lsr & UART_LSR_PE)
+			chan->uart.icount.parity++;
+		else if (lsr & UART_LSR_FE)
+			chan->uart.icount.frame++;
+		if (lsr & UART_LSR_OE)
+			chan->uart.icount.overrun++;
+
+		/*
+		 * Mask off conditions which should be ignored.
+		 */
+		lsr &= chan->uart.read_status_mask;
+
+		if (lsr & UART_LSR_BI)
+			flag = TTY_BREAK;
+		else if (lsr & UART_LSR_PE)
+			flag = TTY_PARITY;
+		else if (lsr & UART_LSR_FE)
+			flag = TTY_FRAME;
+	}
+
+	for (i = 1; i < count; i++) {
+		uart->icount.rx++;
+
+		if (!uart_handle_sysrq_char(uart, rxbuf[i]))
+			uart_insert_char(uart, lsr, UART_LSR_OE,
+					rxbuf[i], flag);
+	}
+
+ignore_char:
+	spin_unlock_irqrestore(&uart->lock, flags);
+
+	if (count > 1)
+		tty_flip_buffer_push(tty);
+}
+
+static void sc16is7x2_handle_fifo_tx(struct sc16is7x2_channel *chan)
+{
+	struct uart_port *uart = &chan->uart;
+	struct circ_buf *xmit = &uart->state->xmit;
+	unsigned count = chan->fifo_tx[1].len + chan->fifo_tx[2].len;
+	unsigned long flags;
+
+	BUG_ON(!uart);
+	BUG_ON(!xmit);
+
+	spin_lock_irqsave(&uart->lock, flags);
+
+	uart->icount.tx += count;
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(uart);
+
+	if (uart_circ_empty(xmit))
+		__stop_tx(chan);
+
+	spin_unlock_irqrestore(&uart->lock, flags);
+}
+
+static bool sc16is7x2_msg_add_fifo_rx(struct sc16is7x2_chip *ts, 
unsigned ch)
+{
+	struct spi_message *m = &(ts->fifo_message);
+	struct spi_transfer *t = &(ts->channel[ch].fifo_rx);
+	int rxlvl = sc16is7x2_read(ts->spi, REG_RXLVL, ch);
+	if (rxlvl > 0) {
+		t->len = rxlvl + 1;
+		spi_message_add_tail(t, m);
+		return true;
+	}
+	return false;
+}
+
+static bool sc16is7x2_msg_add_fifo_tx(struct sc16is7x2_chip *ts, 
unsigned ch)
+{
+	struct sc16is7x2_channel * const chan = &(ts->channel[ch]);
+	struct uart_port *uart = &chan->uart;
+	struct circ_buf *xmit = &uart->state->xmit;
+	unsigned count;
+	bool split_transfer;
+	u8 txlvl;
+
+	if (chan->uart.x_char && chan->lsr & UART_LSR_THRE) {
+		dev_dbg(&ts->spi->dev, "tx: x-char\n");
+		sc16is7x2_write(ts->spi, UART_TX, ch, uart->x_char);
+		uart->icount.tx++;
+		uart->x_char = 0;
+		return false;
+	}
+	if (uart_tx_stopped(&chan->uart)) {
+		dev_dbg(&ts->spi->dev, "tx: stopped!\n");
+		sc16is7x2_stop_tx(uart);
+		return false;
+	}
+	if (uart_circ_empty(xmit)) {
+		__stop_tx(chan);
+		return false;
+	}
+
+	txlvl = sc16is7x2_read(ts->spi, REG_TXLVL, ch);
+	if (txlvl <= 0) {
+		dev_dbg(&ts->spi->dev, " fifo full\n");
+		return false;
+	}
+
+	/* number of bytes to transfer to the fifo */
+	count = MIN(txlvl, uart_circ_chars_pending(xmit));
+	split_transfer = (UART_XMIT_SIZE - xmit->tail) <= count;
+
+	/* add command transfer */
+	spi_message_add_tail(&(chan->fifo_tx[0]), &(ts->fifo_message));
+	/* add first fifo transfer */
+	spi_message_add_tail(&(chan->fifo_tx[1]), &(ts->fifo_message));
+
+	chan->fifo_tx[1].tx_buf = xmit->buf + xmit->tail;
+
+	if (!split_transfer) {
+		chan->fifo_tx[1].len = count;
+		chan->fifo_tx[1].cs_change = true;
+
+		chan->fifo_tx[2].len = 0;
+	} else {
+		chan->fifo_tx[1].len = (UART_XMIT_SIZE - 1) - xmit->tail;
+		chan->fifo_tx[1].cs_change = false;
+
+		chan->fifo_tx[2].tx_buf = xmit->buf;
+		chan->fifo_tx[2].cs_change = true;
+		chan->fifo_tx[2].len = count - chan->fifo_tx[1].len;
+		/* add second fifo transfer */
+		spi_message_add_tail(&(chan->fifo_tx[2]), &(ts->fifo_message));
+	}
+
+	xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
+	return true;
+}
+
+static void sc16is7x2_handle_modem(struct sc16is7x2_chip *ts, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
+	struct uart_port *uart = &chan->uart;
+
+	if (chan->msr & UART_MSR_ANY_DELTA
+			&& chan->ier & UART_IER_MSI
+			&& uart->state != NULL) {
+		if (chan->msr & UART_MSR_TERI)
+			uart->icount.rng++;
+		if (chan->msr & UART_MSR_DDSR)
+			uart->icount.dsr++;
+		if (chan->msr & UART_MSR_DDCD)
+			uart_handle_dcd_change(uart, chan->msr & UART_MSR_DCD);
+		if (chan->msr & UART_MSR_DCTS)
+			uart_handle_cts_change(uart, chan->msr & UART_MSR_CTS);
+
+		wake_up_interruptible(&uart->state->port.delta_msr_wait);
+	}
+}
+
+static bool sc16is7x2_handle_channel(struct sc16is7x2_chip *ts, 
unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
+	struct spi_message *m = &(ts->fifo_message);
+	bool rx, tx;
+
+	dev_dbg(&ts->spi->dev, "%s (%i)\n", __func__, ch);
+
+	chan->iir = sc16is7x2_read(ts->spi, UART_IIR, ch);
+	chan->msr = sc16is7x2_read(ts->spi, UART_MSR, ch);
+	chan->lsr = sc16is7x2_read(ts->spi, UART_LSR, ch);
+
+	sc16is7x2_handle_modem(ts, ch);
+
+	spi_message_init(m);
+	rx = sc16is7x2_msg_add_fifo_rx(ts, ch);
+	tx = sc16is7x2_msg_add_fifo_tx(ts, ch);
+
+	if (rx || tx)
+		spi_sync(ts->spi, m);
+
+	if (rx)
+		sc16is7x2_handle_fifo_rx(chan);
+	if (tx)
+		sc16is7x2_handle_fifo_tx(chan);
+
+	dev_dbg(&ts->spi->dev, "%s finished (iir = 0x%02x)\n",
+			__func__, chan->iir);
+
+	return (chan->iir & UART_IIR_NO_INT) == 0x00;
+}
+
+static void sc16is7x2_work(struct work_struct *w)
+{
+	struct sc16is7x2_chip *ts =
+			container_of(w, struct sc16is7x2_chip, work);
+	unsigned pending = 0;
+	unsigned ch = 0;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+	BUG_ON(!w);
+	BUG_ON(!ts);
+
+
+	if (ts->force_end_work) {
+		dev_dbg(&ts->spi->dev, "%s: force end!\n", __func__);
+		return;
+	}
+
+	if (ts->channel[0].active)
+		pending |= BIT(0);
+	if (ts->channel[1].active)
+		pending |= BIT(1);
+
+	do {
+		mutex_lock(&(ts->channel[ch].lock));
+		if (pending & BIT(ch) && ts->channel[ch].active) {
+			if (!sc16is7x2_handle_channel(ts, ch))
+				pending &= ~BIT(ch);
+		}
+		mutex_unlock(&(ts->channel[ch].lock));
+		ch ^= 1;	/* switch channel */
+	} while (!ts->force_end_work && !freezing(current) && pending);
+
+	dev_dbg(&ts->spi->dev, "%s finished\n", __func__);
+}
+
+static irqreturn_t sc16is7x2_interrupt(int irq, void *dev_id)
+{
+	struct sc16is7x2_chip *ts = dev_id;
+
+	dev_dbg(&ts->spi->dev, "%s\n", __func__);
+
+	if (!ts->force_end_work && !work_pending(&ts->work) &&
+	    !freezing(current) && !ts->suspending)
+		queue_work(ts->workqueue, &ts->work);
+
+	return IRQ_HANDLED;
+}
+
+/* ******************************** INIT 
********************************* */
+
+static struct uart_driver sc16is7x2_uart_driver;
+
+static int sc16is7x2_register_gpio(struct sc16is7x2_chip *ts,
+		struct sc16is7x2_platform_data *pdata)
+{
+	struct sc16is7x2_spi_reg *cmds;
+
+	ts->gpio.label = (pdata->label) ? pdata->label : DRIVER_NAME;
+	ts->gpio.request	= sc16is7x2_gpio_request;
+	ts->gpio.free		= sc16is7x2_gpio_free;
+	ts->gpio.get		= sc16is7x2_get;
+	ts->gpio.set		= sc16is7x2_set;
+	ts->gpio.direction_input = sc16is7x2_direction_input;
+	ts->gpio.direction_output = sc16is7x2_direction_output;
+
+	ts->gpio.base = pdata->gpio_base;
+	ts->gpio.names = pdata->names;
+	ts->gpio.ngpio = SC16IS7X2_NR_GPIOS;
+	ts->gpio.can_sleep = 1;
+	ts->gpio.dev = &ts->spi->dev;
+	ts->gpio.owner = THIS_MODULE;
+
+	/* disable all GPIOs, enable on request */
+	ts->io_dir = 0x0f;
+	ts->io_state = 0;
+	ts->io_gpio = 0;
+	ts->io_control = IOC_GPIO30 | IOC_GPIO74;
+
+	cmds = sc16is7x2_alloc_spi_cmds(4);
+	if (!cmds)
+		return -ENOMEM;
+
+	sc16is7x2_add_write_cmd(&cmds[0], REG_IOI, 0, 0);
+	sc16is7x2_add_write_cmd(&cmds[1], REG_IOC, 0, ts->io_control);
+	sc16is7x2_add_write_cmd(&cmds[2], REG_IOS, 0, ts->io_state);
+	sc16is7x2_add_write_cmd(&cmds[3], REG_IOD, 0, ts->io_dir);
+	sc16is7x2_spi_async(ts->spi, cmds, 4);
+
+	return gpiochip_add(&ts->gpio);
+}
+
+static int sc16is7x2_register_uart_port(struct sc16is7x2_chip *ts,
+		struct sc16is7x2_platform_data *pdata, unsigned ch)
+{
+	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
+	struct uart_port *uart = &chan->uart;
+
+	mutex_init(&chan->lock);
+	chan->active = false;	/* will be set in startup */
+	chan->chip = ts;
+
+	chan->read_fifo_cmd = READ_CMD(UART_RX, ch);
+	chan->fifo_rx.tx_buf = &(chan->read_fifo_cmd);
+	chan->fifo_rx.rx_buf = chan->rxbuf;
+	chan->fifo_rx.cs_change = true;
+
+	chan->write_fifo_cmd = WRITE_CMD(UART_TX, ch);
+	chan->fifo_tx[0].tx_buf = &(chan->write_fifo_cmd);
+	chan->fifo_tx[0].rx_buf = NULL;
+	chan->fifo_tx[0].len = 1;
+	chan->fifo_tx[0].cs_change = false;
+	chan->fifo_tx[1].rx_buf = NULL;
+	chan->fifo_tx[2].rx_buf = NULL;
+
+	uart->irq = ts->spi->irq;
+	uart->uartclk = pdata->uartclk;
+	uart->fifosize = FIFO_SIZE;
+	uart->ops = &sc16is7x2_uart_ops;
+	uart->flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF;
+	uart->line = pdata->uart_base + ch;
+	uart->type = PORT_SC16IS7X2;
+	uart->dev = &ts->spi->dev;
+
+	return uart_add_one_port(&sc16is7x2_uart_driver, uart);
+}
+
+static int __devinit sc16is7x2_probe(struct spi_device *spi)
+{
+	struct sc16is7x2_chip *ts;
+	struct sc16is7x2_platform_data *pdata;
+	int ret;
+
+	pdata = spi->dev.platform_data;
+	if (!pdata || !pdata->gpio_base /* || pdata->uart_base */) {
+		dev_dbg(&spi->dev, "incorrect or missing platform data\n");
+		return -EINVAL;
+	}
+
+	printk(KERN_INFO DRIVER_NAME " at CS%d (irq %d), 2 UARTs, 8 GPIOs\n"
+			"    eser%d, eser%d, gpiochip%d\n",
+			spi->chip_select, spi->irq,
+			pdata->uart_base, pdata->uart_base + 1,
+			pdata->gpio_base);
+
+	ret = spi_setup(spi);
+	if (ret < 0)
+		return ret;
+
+	ts = kzalloc(sizeof(struct sc16is7x2_chip), GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	mutex_init(&ts->lock);
+	dev_set_drvdata(&spi->dev, ts);
+	ts->spi = spi;
+	ts->force_end_work = 1;
+
+	/* Reset the chip TODO: and disable IRQ output */
+	sc16is7x2_write(spi, REG_IOC, 0, IOC_SRESET);
+
+	ret = request_irq(spi->irq, sc16is7x2_interrupt,
+			IRQF_TRIGGER_FALLING | IRQF_SHARED, "sc16is7x2", ts);
+	if (ret) {
+		dev_warn(&ts->spi->dev, "cannot register interrupt\n");
+		goto exit_destroy;
+	}
+
+	ret = sc16is7x2_register_uart_port(ts, pdata, 0);
+	if (ret)
+		goto exit_irq;
+	ret = sc16is7x2_register_uart_port(ts, pdata, 1);
+	if (ret)
+		goto exit_uart0;
+
+	ret = sc16is7x2_register_gpio(ts, pdata);
+	if (ret)
+		goto exit_uart1;
+
+	ts->workqueue = create_freezeable_workqueue(DRIVER_NAME);
+	if (!ts->workqueue) {
+		dev_warn(&ts->spi->dev, "cannot create workqueue\n");
+		ret = -EBUSY;
+		goto exit_gpio;
+	}
+	INIT_WORK(&ts->work, sc16is7x2_work);
+	ts->force_end_work = 0;
+
+	return ret;
+
+exit_gpio:
+	ret = gpiochip_remove(&ts->gpio);
+
+exit_uart1:
+	uart_remove_one_port(&sc16is7x2_uart_driver, &ts->channel[1].uart);
+
+exit_uart0:
+	uart_remove_one_port(&sc16is7x2_uart_driver, &ts->channel[0].uart);
+
+exit_irq:
+	free_irq(spi->irq, ts);
+
+exit_destroy:
+	dev_set_drvdata(&spi->dev, NULL);
+	mutex_destroy(&ts->lock);
+	kfree(ts);
+	return ret;
+}
+
+static int __devexit sc16is7x2_remove(struct spi_device *spi)
+{
+	struct sc16is7x2_chip *ts;
+	int ret;
+
+	ts = dev_get_drvdata(&spi->dev);
+	if (ts == NULL)
+		return -ENODEV;
+
+	free_irq(spi->irq, ts);
+	ts->force_end_work = 1;
+
+	if (ts->workqueue) {
+		flush_workqueue(ts->workqueue);
+		destroy_workqueue(ts->workqueue);
+		ts->workqueue = NULL;
+	}
+
+	dev_set_drvdata(&spi->dev, NULL);
+
+	ret = uart_remove_one_port(&sc16is7x2_uart_driver,
+			&ts->channel[0].uart);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to remove the UART port A: %d\n",
+			ret);
+		goto exit_error;
+	}
+	ret = uart_remove_one_port(&sc16is7x2_uart_driver,
+			&ts->channel[1].uart);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to remove the UART port B: %d\n",
+			ret);
+		goto exit_error;
+	}
+	ret = gpiochip_remove(&ts->gpio);
+	if (ret) {
+		dev_err(&spi->dev, "Failed to remove the GPIO controller: %d\n",
+			ret);
+		goto exit_error;
+	}
+
+	mutex_destroy(&ts->lock);
+	kfree(ts);
+
+exit_error:
+	return ret;
+}
+
+static struct uart_driver sc16is7x2_uart_driver = {
+	.owner          = THIS_MODULE,
+	.driver_name    = DRIVER_NAME,
+	.dev_name       = "eser",
+	.major          = SC16IS7X2_MAJOR,
+	.minor          = SC16IS7X2_MINOR,
+	.nr             = MAX_SC16IS7X2,
+};
+
+static struct spi_driver sc16is7x2_spi_driver = {
+	.driver = {
+		.name		= DRIVER_NAME,
+		.owner		= THIS_MODULE,
+	},
+	.probe		= sc16is7x2_probe,
+	.remove		= __devexit_p(sc16is7x2_remove),
+};
+
+static int __init sc16is7x2_init(void)
+{
+	int ret;
+	ret = uart_register_driver(&sc16is7x2_uart_driver);
+	if (ret) {
+		printk(KERN_ERR "Couldn't register sc16is7x2 uart driver\n");
+		return ret;
+	}
+
+	return spi_register_driver(&sc16is7x2_spi_driver);
+}
+/* register after spi postcore initcall and before
+ * subsys initcalls that may rely on these GPIOs
+ */
+subsys_initcall(sc16is7x2_init);
+
+static void __exit sc16is7x2_exit(void)
+{
+	uart_unregister_driver(&sc16is7x2_uart_driver);
+	spi_unregister_driver(&sc16is7x2_spi_driver);
+}
+module_exit(sc16is7x2_exit);
+
+MODULE_AUTHOR("Manuel Stahl");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("SC16IS7x2 SPI based UART chip");
+MODULE_ALIAS("spi:" DRIVER_NAME);
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 64458a9..1869dcf 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -46,6 +46,9 @@
  #define PORT_AR7	18	/* Texas Instruments AR7 internal UART */
  #define PORT_MAX_8250	18	/* max port ID */

+/* SC16IS7x2 SPI UART */
+#define PORT_SC16IS7X2	19
+
  /*
   * ARM specific type numbers.  These are not currently guaranteed
   * to be implemented, and will change in the future.  These are
diff --git a/include/linux/spi/sc16is7x2.h b/include/linux/spi/sc16is7x2.h
new file mode 100755
index 0000000..931fe50
--- /dev/null
+++ b/include/linux/spi/sc16is7x2.h
@@ -0,0 +1,17 @@
+#ifndef LINUX_SPI_SC16IS752_H
+#define LINUX_SPI_SC16IS752_H
+
+#define SC16IS7X2_NR_GPIOS 8
+
+struct sc16is7x2_platform_data {
+	unsigned int	uartclk;
+	/* uart line number of the first channel */
+	unsigned	uart_base;
+	/* number assigned to the first GPIO */
+	unsigned	gpio_base;
+	char		*label;
+	/* list of GPIO names (array length = SC16IS7X2_NR_GPIOS) */
+	const char	*const *names;
+};
+
+#endif

[-- Attachment #2: manuel_stahl.vcf --]
[-- Type: text/x-vcard, Size: 170 bytes --]

begin:vcard
fn:Manuel Stahl
n:Stahl;Manuel
email;internet:manuel.stahl@iis.fraunhofer.de
tel;work:+49 911 58061-6419
x-mozilla-html:FALSE
version:2.1
end:vcard


^ permalink raw reply related	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2012-02-05 19:55 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-09-03 13:11 [PATCH] Add sc16is7x2 driver Manuel Stahl
2010-09-14  0:25 ` Andrew Morton
2010-09-14 13:07   ` Manuel Stahl
2010-09-14 13:27     ` Jonathan Corbet
2010-09-15 13:42       ` Manuel Stahl
2010-09-15 19:17         ` Andrew Morton
2010-09-21 20:42     ` Greg KH
2010-09-22  8:01       ` Manuel Stahl
2010-09-22  8:01         ` Manuel Stahl
  -- strict thread matches above, loose matches on Subject: below --
2012-02-05  6:55 Thomas Weber
2012-02-05 19:56 ` Alan Cox
2010-08-31 11:26 Manuel Stahl

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.