lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:	Fri, 03 Sep 2010 15:11:53 +0200
From:	Manuel Stahl <manuel.stahl@....fraunhofer.de>
To:	linux-kernel@...r.kernel.org
Subject: [PATCH] Add sc16is7x2 driver

This patch adds support for the sc16is7x2 chips.

Signed-off-by: Manuel Stahl <manuel.stahl@....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@....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

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ