All of lore.kernel.org
 help / color / mirror / Atom feed
From: Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
To: linux-serial@vger.kernel.org
Subject: [PATCH] Add sc16is7x2 driver
Date: Tue, 31 Aug 2010 13:26:45 +0200	[thread overview]
Message-ID: <4C7CE6F5.2040704@iis.fraunhofer.de> (raw)

[-- 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


             reply	other threads:[~2010-08-31 11:36 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-08-31 11:26 Manuel Stahl [this message]
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
2012-02-05  6:55 Thomas Weber
2012-02-05 19:56 ` Alan Cox

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4C7CE6F5.2040704@iis.fraunhofer.de \
    --to=manuel.stahl@iis.fraunhofer.de \
    --cc=linux-serial@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.