LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
* [PATCH resend 2] Add sc16is7x2 driver
@ 2011-01-07 15:14 Manuel Stahl
  2011-01-07 17:05 ` Randy Dunlap
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Manuel Stahl @ 2011-01-07 15:14 UTC (permalink / raw)
  To: linux-serial; +Cc: LKML, Thomas Weber, Johannes Reif, changgx, miguelangel


[-- Attachment #1.1: Type: text/plain, Size: 341 bytes --]

IRQ is now implemented with work queue (like max3100).
The problem is, that the sc16is7x2 has a low active irq. But with active 
low threaded_irqs, shared irqs are not allowed.

Please try this one and report errors.

Regards,
Manuel Stahl

PS: sorry that the patch is not inline, not sure how to configure 
thunderbird correctly

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.2: sc16is7x2_work_queue.patch --]
[-- Type: text/x-patch; name="sc16is7x2_work_queue.patch", Size: 32773 bytes --]

Add sc16is7x2 driver

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

 drivers/serial/Kconfig           |    9 +
 drivers/serial/Makefile          |    1 +
 drivers/serial/sc16is7x2.c       | 1063 ++++++++++++++++++++++++++++++++++++++
 include/linux/serial_core.h      |    6 +-
 include/linux/serial_sc16is7x2.h |   17 +
 5 files changed, 1095 insertions(+), 1 deletions(-)

diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 188aff6..a070345 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
+	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 8ea92e9..6ff1f0f 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..d3156b4
--- /dev/null
+++ b/drivers/serial/sc16is7x2.c
@@ -0,0 +1,1063 @@
+/**
+ * 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/delay.h>
+#include <linux/device.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_freezeable_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 a23fa29..28b9e85 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -45,7 +45,8 @@
 #define PORT_OCTEON	17	/* Cavium OCTEON internal UART */
 #define PORT_AR7	18	/* Texas Instruments AR7 internal UART */
 #define PORT_U6_16550A	19	/* ST-Ericsson U6xxx internal UART */
-#define PORT_MAX_8250	19	/* max port ID */
+#define PORT_SC16IS7X2	20	/* SC16IS7x2 SPI UART */
+#define PORT_MAX_8250	20	/* max port ID */
 
 /*
  * ARM specific type numbers.  These are not currently guaranteed
@@ -202,6 +203,9 @@
 /* VIA VT8500 SoC */
 #define PORT_VT8500	97
 
+/* SC16IS7x2 SPI UART */
+#define PORT_SC16IS7X2	98
+
 #ifdef __KERNEL__
 
 #include <linux/compiler.h>
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

[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 6148 bytes --]

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

end of thread, other threads:[~2011-01-31 21:34 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-01-07 15:14 [PATCH resend 2] Add sc16is7x2 driver Manuel Stahl
2011-01-07 17:05 ` Randy Dunlap
2011-01-07 19:37 ` Greg KH
2011-01-12 11:05 ` Thomas Weber
2011-01-12 12:01   ` Manuel Stahl
2011-01-31 21:34     ` Thomas Weber

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).