Re: [patch 1/3] serial: add sc16is7x2 driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Oct 01, 2010 at 02:18:03PM -0700, akpm@xxxxxxxxxxxxxxxxxxxx wrote:
> From: Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>
>
> Add support for the sc16is7x2 chips.
>
> [akpm@xxxxxxxxxxxxxxxxxxxx: fix broken indenting]
> Signed-off-by: Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>
> Cc: Greg KH <greg@xxxxxxxxx>
> Cc: Grant Likely <grant.likely@xxxxxxxxxxxx>
> Signed-off-by: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
> ---
>
>  drivers/serial/Kconfig        |    9
>  drivers/serial/Makefile       |    1
>  drivers/serial/sc16is7x2.c    | 1375 ++++++++++++++++++++++++++++++++
>  include/linux/serial_core.h   |    3
>  include/linux/spi/sc16is7x2.h |   17

The driver's platform data header file shouldn't be in the linux/spi
subdirectory since it is a serial device that happens to be
spi-attached.  Please name it include/linux/serial_sc16is7x2.h
instead.

>  5 files changed, 1405 insertions(+)
>
> diff -puN drivers/serial/Kconfig~serial-add-sc16is7x2-driver drivers/serial/Kconfig
> --- a/drivers/serial/Kconfig~serial-add-sc16is7x2-driver
> +++ a/drivers/serial/Kconfig
> @@ -269,6 +269,15 @@ config SERIAL_8250_RM9K
>
>  comment "Non-8250 serial port support"
>
> +config SERIAL_SC16IS7X2
> +	tristate "SC16IS7x2 chips"
> +	depends on SPI_MASTER && GPIOLIB
> +	select SERIAL_CORE
> +	help
> +	  Selecting this option will add support for SC16IS7x2 SPI UARTs.
> +	  The GPIOs are exported via gpiolib interface.
> +	  If unsure, say N.
> +
>  config SERIAL_AMBA_PL010
>  	tristate "ARM AMBA PL010 serial port support"
>  	depends on ARM_AMBA && (BROKEN || !ARCH_VERSATILE)
> diff -puN drivers/serial/Makefile~serial-add-sc16is7x2-driver drivers/serial/Makefile
> --- a/drivers/serial/Makefile~serial-add-sc16is7x2-driver
> +++ a/drivers/serial/Makefile
> @@ -28,6 +28,7 @@ obj-$(CONFIG_SERIAL_8250_BOCA) += 8250_b
>  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

stray tab character

>  obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
>  obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
>  obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o
> diff -puN /dev/null drivers/serial/sc16is7x2.c
> --- /dev/null
> +++ a/drivers/serial/sc16is7x2.c
> @@ -0,0 +1,1375 @@
> +/**
> + * drivers/serial/sc16is7x2.c
> + *
> + * Copyright (C) 2009 Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>
> + *
> + * 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

Don't steal a minor number from another device.  Major/minor numbers
can be dynamically allocated by the driver, or you should get a new
number assigned and added to Documentation/devices.txt.  Even better
if you're able to use a dynamically assigned number.

> +#define MAX_SC16IS7X2		8
> +#define FIFO_SIZE		64
> +
> +#define DRIVER_NAME		"sc16is7x2"
> +#define TYPE_NAME		"SC16IS7x2"
> +
> +
> +
> +#define REG_READ	0x80
> +#define REG_WRITE	0x00
> +
> +/* Special registers */
> +#define REG_TXLVL	0x08	/* Transmitter FIFO Level register */
> +#define REG_RXLVL	0x09	/* Receiver FIFO Level register */
> +#define REG_IOD		0x0A	/* IO Direction register */
> +#define REG_IOS		0x0B	/* IO State register */
> +#define REG_IOI		0x0C	/* IO Interrupt Enable register */
> +#define REG_IOC		0x0E	/* IO Control register */
> +
> +#define IOC_SRESET	0x08    /* Software reset */
> +#define IOC_GPIO30	0x04    /* GPIO 3:0 unset: as IO, set: as modem pins */
> +#define IOC_GPIO74	0x02    /* GPIO 7:4 unset: as IO, set: as modem pins */
> +#define IOC_IOLATCH	0x01    /* Unset: input unlatched, set: input latched */
> +
> +/* Redefine some MCR bits */
> +#ifdef UART_MCR_TCRTLR
> +#undef UART_MCR_TCRTLR
> +#endif
> +#define UART_MCR_TCRTLR		0x04
> +#define UART_MCR_IRDA		0x40
> +
> +/* 16bit SPI command to read or write a register */
> +struct sc16is7x2_spi_reg {
> +	u8 cmd;
> +	u8 value;
> +} __packed;

Given the usage in this file, this would probably be better named,
"sc16is7x2_spi_cmd"

> +
> +struct sc16is7x2_chip;
> +
> +/*
> + * Some registers must be read back to modify.
> + * To save time we cache them here in memory.
> + * The @lock mutex is there to protect them.
> + */
> +struct sc16is7x2_channel {
> +	struct sc16is7x2_chip	*chip;	/* back link */
> +	struct mutex		lock;
> +	struct uart_port	uart;
> +	struct spi_transfer fifo_rx;
> +	struct spi_transfer fifo_tx[3];
> +	u8		iir;
> +	u8		lsr;
> +	u8		msr;
> +	u8		ier;		/* cache for IER register */
> +	u8		fcr;		/* cache for FCR register */
> +	u8		lcr;		/* cache for LCR register */
> +	u8		mcr;		/* cache for MCR register */
> +	u8		*rx_buf;
> +	u8		write_fifo_cmd;
> +	u8		read_fifo_cmd;
> +	bool		active;
> +};
> +
> +struct sc16is7x2_chip {
> +	struct spi_device *spi;
> +	struct gpio_chip gpio;
> +	struct mutex	 lock;
> +	struct sc16is7x2_channel channel[2];
> +
> +	/* for handling irqs: need workqueue since we do spi_sync */
> +	struct workqueue_struct *workqueue;
> +	struct work_struct work;
> +	/* set to 1 to make the workhandler exit as soon as possible */
> +	int force_end_work;
> +	/* need to know we are suspending to avoid deadlock on workqueue */
> +	int suspending;
> +
> +	struct spi_message fifo_message;
> +
> +#define UART_BUG_TXEN	BIT(1)	/* UART has buggy TX IIR status */
> +#define UART_BUG_NOMSR	BIT(2)	/* UART has buggy MSR status bits (Au1x00) */
> +#define UART_BUG_THRE	BIT(3)	/* UART has buggy THRE reassertion */
> +	u16		bugs;		/* port bugs */
> +
> +#define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS
> +	u8		lsr_saved_flags;
> +#define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA
> +	u8		msr_saved_flags;
> +	u8		io_dir;		/* cache for IODir register */
> +	u8		io_state;	/* cache for IOState register */
> +	u8		io_gpio;	/* PIN is GPIO */
> +	u8		io_control;	/* cache for IOControl register */
> +};
> +
> +/* ******************************** SPI ********************************* */
> +
> +static u8 write_cmd(u8 reg, u8 ch)
> +{
> +	return REG_WRITE | (reg & 0xf) << 3 | (ch & 0x1) << 1;
> +}
> +
> +static u8 read_cmd(u8 reg, u8 ch)
> +{
> +	return REG_READ  | (reg & 0xf) << 3 | (ch & 0x1) << 1;
> +}

These two should probably be tagged as inline

> +
> +/*
> + * Reserve memory for command sequence
> + * @cnt number of commands
> + */
> +static struct sc16is7x2_spi_reg *
> +sc16is7x2_alloc_spi_cmds(unsigned cnt)

No reason for the line break

> +{
> +	return kcalloc(cnt, sizeof(struct sc16is7x2_spi_reg), GFP_KERNEL);
> +}
> +
> +/*
> + * sc16is7x2_add_write_cmd - Add write command to sequence
> + */
> +static void sc16is7x2_add_write_cmd(struct sc16is7x2_spi_reg *cmd,
> +		u8 reg, u8 ch, u8 value)
> +{
> +	cmd->cmd = write_cmd(reg, ch);
> +	cmd->value = value;
> +}
> +
> +/*
> + * sc16is7x2_add_read_cmd - Add read command to sequence
> + */
> +static void sc16is7x2_add_read_cmd(struct sc16is7x2_spi_reg *cmd,
> +		u8 reg, u8 ch)
> +{
> +	cmd->cmd = read_cmd(reg, ch);
> +	cmd->value = 0;
> +}
> +
> +/*
> + * sc16is7x2_complete - Completion handler for async SPI transfers
> + */
> +static void sc16is7x2_complete(void *context)
> +{
> +	struct spi_message *m = context;
> +	u8 *tx_chain = m->state;
> +
> +	kfree(tx_chain);
> +	kfree(m);
> +}
> +
> +/*
> + * sc16is7x2_spi_async - Send command sequence
> + */
> +static int sc16is7x2_spi_async(struct spi_device *spi,
> +		struct sc16is7x2_spi_reg *cmds, unsigned len)
> +{
> +	struct spi_transfer *t;
> +	struct spi_message *m;
> +
> +	m = spi_message_alloc(len, GFP_KERNEL);
> +	if (!m)
> +		return -ENOMEM;
> +
> +	m->complete = sc16is7x2_complete;
> +	m->context = m;
> +	m->state = cmds;
> +	list_for_each_entry(t, &m->transfers, transfer_list) {
> +		t->tx_buf = (u8 *)cmds;
> +		t->len = 2;
> +		t->cs_change = true;
> +		cmds++;
> +	}
> +
> +	return spi_async(spi, m);
> +}
> +
> +/*
> + * sc16is7x2_write_async - Write a new register content (async)
> + */
> +static int sc16is7x2_write_async(struct spi_device *spi, u8 reg, u8 ch,
> +		u8 value)
> +{
> +	struct sc16is7x2_spi_reg *cmd = sc16is7x2_alloc_spi_cmds(1);
> +
> +	if (!cmd)
> +		return -ENOMEM;
> +	sc16is7x2_add_write_cmd(cmd, reg, ch, value);
> +	return sc16is7x2_spi_async(spi, cmd, 1);
> +}
> +
> +/*
> + * sc16is7x2_write - Write a new register content (sync)
> + */
> +static int sc16is7x2_write(struct spi_device *spi, u8 reg, u8 ch, u8 val)
> +{
> +	struct sc16is7x2_spi_reg out;
> +
> +	out.cmd = write_cmd(reg, ch);
> +	out.value = val;
> +	return spi_write(spi, (const u8 *)&out, sizeof(out));
> +}
> +
> +/**
> + * sc16is7x2_read - Read back register content
> + * @spi: The SPI device
> + * @reg: Register offset
> + * @ch:  Channel (0 or 1)
> + *
> + * Returns positive 8 bit value from device if successful or a
> + * negative value on error
> + */
> +static int sc16is7x2_read(struct spi_device *spi, unsigned reg, unsigned ch)
> +{
> +	return spi_w8r8(spi, read_cmd(reg, ch));
> +}
> +
> +/* ******************************** UART ********************************* */
> +
> +/* Uart divisor latch write */
> +static void sc16is7x2_add_dl_write_cmd(struct sc16is7x2_spi_reg *cmd,
> +		u8 ch, int value)
> +{
> +	sc16is7x2_add_write_cmd(&cmd[0], UART_DLL, ch, value & 0xff);
> +	sc16is7x2_add_write_cmd(&cmd[1], UART_DLM, ch, value >> 8 & 0xff);
> +}
> +
> +static unsigned int sc16is7x2_tx_empty(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);

A 'to_sc16is7x2_channel()' macro would make this more readable here
and through the rest of the file.

> +	struct sc16is7x2_chip *ts = chan->chip;
> +	unsigned lsr;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	lsr = chan->lsr;
> +	return lsr & UART_LSR_TEMT ? TIOCSER_TEMT : 0;
> +}
> +
> +static unsigned int sc16is7x2_get_mctrl(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +	unsigned int status;
> +	unsigned int ret;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	status = chan->msr;
> +
> +	ret = 0;
> +	if (status & UART_MSR_DCD)
> +		ret |= TIOCM_CAR;
> +	if (status & UART_MSR_RI)
> +		ret |= TIOCM_RNG;
> +	if (status & UART_MSR_DSR)
> +		ret |= TIOCM_DSR;
> +	if (status & UART_MSR_CTS)
> +		ret |= TIOCM_CTS;
> +	return ret;
> +}
> +
> +static unsigned int __set_mctrl(unsigned int mctrl)
> +{
> +	unsigned char mcr = 0;
> +
> +	if (mctrl & TIOCM_RTS)
> +		mcr |= UART_MCR_RTS;
> +	if (mctrl & TIOCM_DTR)
> +		mcr |= UART_MCR_DTR;
> +	if (mctrl & TIOCM_LOOP)
> +		mcr |= UART_MCR_LOOP;
> +
> +	return mcr;
> +}
> +
> +static void sc16is7x2_set_mctrl(struct uart_port *port, unsigned int mctrl)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +	unsigned ch = port->line & 0x01;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +	sc16is7x2_write_async(ts->spi, UART_MCR, ch, __set_mctrl(mctrl));
> +}
> +
> +static void __stop_tx(struct sc16is7x2_channel *chan)
> +{
> +	struct sc16is7x2_chip *ts = chan->chip;
> +	unsigned ch = chan->uart.line & 0x01;
> +
> +	if (chan->ier & UART_IER_THRI) {
> +		chan->ier &= ~UART_IER_THRI;
> +		sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
> +	}
> +}
> +
> +static void sc16is7x2_stop_tx(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	__stop_tx(chan);
> +}
> +
> +static void sc16is7x2_start_tx(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +	unsigned ch = port->line & 0x01;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	if (!(chan->ier & UART_IER_THRI)) {
> +		chan->ier |= UART_IER_THRI;
> +		sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
> +	}
> +}
> +
> +static void sc16is7x2_stop_rx(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +	unsigned ch = port->line & 0x01;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	chan->ier &= ~UART_IER_RLSI;
> +	chan->uart.read_status_mask &= ~UART_LSR_DR;
> +	sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
> +}
> +
> +static void sc16is7x2_enable_ms(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +	unsigned ch = port->line & 0x01;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	chan->ier |= UART_IER_MSI;
> +	sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier);
> +}
> +
> +static void sc16is7x2_break_ctl(struct uart_port *port, int break_state)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +	unsigned ch = port->line & 0x01;
> +	unsigned long flags;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	spin_lock_irqsave(&chan->uart.lock, flags);
> +	if (break_state == -1)
> +		chan->lcr |= UART_LCR_SBC;
> +	else
> +		chan->lcr &= ~UART_LCR_SBC;
> +	spin_unlock_irqrestore(&chan->uart.lock, flags);
> +
> +	sc16is7x2_write_async(ts->spi, UART_LCR, ch, chan->lcr);
> +}
> +
> +static int sc16is7x2_startup(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +	unsigned ch = port->line & 0x01;
> +	struct sc16is7x2_spi_reg *cmds, *cmd;
> +	unsigned long flags;
> +
> +	dev_dbg(&ts->spi->dev, "%s (line %d)\n", __func__, port->line);
> +
> +	spin_lock_irqsave(&chan->uart.lock, flags);
> +	chan->lcr = UART_LCR_WLEN8;
> +	chan->mcr = __set_mctrl(chan->uart.mctrl);
> +	chan->fcr = 0;
> +	chan->ier = UART_IER_RLSI | UART_IER_RDI;
> +	spin_unlock_irqrestore(&chan->uart.lock, flags);
> +
> +	cmds = sc16is7x2_alloc_spi_cmds(8);
> +	if (!cmds)
> +		return -ENOMEM;
> +
> +	cmd = cmds;
> +	/* Clear the interrupt registers. */
> +	sc16is7x2_add_write_cmd(cmd, UART_IER, ch, 0);
> +	sc16is7x2_add_read_cmd(++cmd, UART_IIR, ch);
> +	sc16is7x2_add_read_cmd(++cmd, UART_LSR, ch);
> +	sc16is7x2_add_read_cmd(++cmd, UART_MSR, ch);
> +
> +	sc16is7x2_add_write_cmd(++cmd, UART_FCR, ch, UART_FCR_ENABLE_FIFO |
> +		       UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
> +	sc16is7x2_add_write_cmd(++cmd, UART_FCR, ch, chan->fcr);
> +	/* Now, initialize the UART */
> +	sc16is7x2_add_write_cmd(++cmd, UART_LCR, ch, chan->lcr);
> +	sc16is7x2_add_write_cmd(++cmd, UART_MCR, ch, chan->mcr);
> +
> +	sc16is7x2_spi_async(ts->spi, cmds, 8);
> +
> +	chan->active = true;
> +	return 0;
> +}
> +
> +static void sc16is7x2_shutdown(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +	unsigned long flags;
> +	unsigned ch = port->line & 0x01;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	BUG_ON(!chan);
> +	BUG_ON(!ts);
> +
> +	if (ts->suspending)
> +		return;
> +
> +	/* Disable interrupts from this port */
> +	chan->ier = 0;
> +	chan->active = false;
> +	sc16is7x2_write(ts->spi, UART_IER, ch, chan->ier);
> +
> +	/* Wait for worker of this channel to finish */
> +	mutex_lock(&chan->lock);
> +
> +	spin_lock_irqsave(&chan->uart.lock, flags);
> +	chan->mcr = __set_mctrl(chan->uart.mctrl);
> +	spin_unlock_irqrestore(&chan->uart.lock, flags);
> +
> +	/* Disable break condition and FIFOs */
> +	chan->lcr &= ~UART_LCR_SBC;
> +
> +	sc16is7x2_write(ts->spi, UART_MCR, ch, chan->mcr);
> +	sc16is7x2_write(ts->spi, UART_LCR, ch, chan->lcr);
> +
> +	mutex_unlock(&chan->lock);
> +}
> +
> +static void
> +sc16is7x2_set_termios(struct uart_port *port, struct ktermios *termios,
> +		       struct ktermios *old)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +	struct sc16is7x2_spi_reg *cmds;
> +	unsigned ch = port->line & 0x01;
> +	unsigned long flags;
> +	unsigned int baud, quot;
> +	u8 ier, mcr, lcr, fcr = 0;
> +	u8 efr = UART_EFR_ECB;
> +
> +	/* set word length */
> +	switch (termios->c_cflag & CSIZE) {
> +	case CS5:
> +		lcr = UART_LCR_WLEN5;
> +		break;
> +	case CS6:
> +		lcr = UART_LCR_WLEN6;
> +		break;
> +	case CS7:
> +		lcr = UART_LCR_WLEN7;
> +		break;
> +	default:
> +	case CS8:
> +		lcr = UART_LCR_WLEN8;
> +		break;
> +	}
> +
> +	if (termios->c_cflag & CSTOPB)
> +		lcr |= UART_LCR_STOP;
> +	if (termios->c_cflag & PARENB)
> +		lcr |= UART_LCR_PARITY;
> +	if (!(termios->c_cflag & PARODD))
> +		lcr |= UART_LCR_EPAR;
> +#ifdef CMSPAR
> +	if (termios->c_cflag & CMSPAR)
> +		lcr |= UART_LCR_SPAR;
> +#endif
> +
> +	/* Ask the core to calculate the divisor for us. */
> +	baud = uart_get_baud_rate(port, termios, old,
> +				  port->uartclk / 16 / 0xffff,
> +				  port->uartclk / 16);
> +	quot = uart_get_divisor(port, baud);
> +
> +	dev_dbg(&ts->spi->dev, "%s (baud %u)\n", __func__, baud);
> +
> +
> +	/* configure the fifo */
> +	if (baud < 2400)
> +		fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1;
> +	else
> +		fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_01;
> +
> +	/*
> +	 * MCR-based auto flow control.  When AFE is enabled, RTS will be
> +	 * deasserted when the receive FIFO contains more characters than
> +	 * the trigger, or the MCR RTS bit is cleared.  In the case where
> +	 * the remote UART is not using CTS auto flow control, we must
> +	 * have sufficient FIFO entries for the latency of the remote
> +	 * UART to respond.  IOW, at least 32 bytes of FIFO.
> +	 */
> +	chan->mcr &= ~UART_MCR_AFE;
> +	if (termios->c_cflag & CRTSCTS)
> +		chan->mcr |= UART_MCR_AFE;
> +
> +	/*
> +	 * Ok, we're now changing the port state.  Do it with
> +	 * interrupts disabled.
> +	 */
> +	spin_lock_irqsave(&chan->uart.lock, flags);
> +
> +	/* we are sending char from a workqueue so enable */
> +	chan->uart.state->port.tty->low_latency = 1;
> +
> +	/* Update the per-port timeout. */
> +	uart_update_timeout(port, termios->c_cflag, baud);
> +
> +	chan->uart.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
> +	if (termios->c_iflag & INPCK)
> +		chan->uart.read_status_mask |= UART_LSR_FE | UART_LSR_PE;
> +	if (termios->c_iflag & (BRKINT | PARMRK))
> +		chan->uart.read_status_mask |= UART_LSR_BI;
> +
> +	/* Characters to ignore */
> +	chan->uart.ignore_status_mask = 0;
> +	if (termios->c_iflag & IGNPAR)
> +		chan->uart.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
> +	if (termios->c_iflag & IGNBRK) {
> +		chan->uart.ignore_status_mask |= UART_LSR_BI;
> +		/*
> +		 * If we're ignoring parity and break indicators,
> +		 * ignore overruns too (for real raw support).
> +		 */
> +		if (termios->c_iflag & IGNPAR)
> +			chan->uart.ignore_status_mask |= UART_LSR_OE;
> +	}
> +
> +	/* ignore all characters if CREAD is not set */
> +	if ((termios->c_cflag & CREAD) == 0)
> +		chan->uart.ignore_status_mask |= UART_LSR_DR;
> +
> +	/* CTS flow control flag and modem status interrupts */
> +	chan->ier &= ~UART_IER_MSI;
> +	if (UART_ENABLE_MS(&chan->uart, termios->c_cflag))
> +		chan->ier |= UART_IER_MSI;
> +
> +	if (termios->c_cflag & CRTSCTS)
> +		efr |= UART_EFR_CTS | UART_EFR_RTS;
> +
> +	mcr = __set_mctrl(chan->uart.mctrl);
> +	ier = chan->ier;
> +	chan->lcr = lcr;				/* Save LCR */
> +	chan->fcr = fcr;				/* Save FCR */
> +	chan->mcr = mcr;				/* Save MCR */
> +
> +	fcr |= UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT;
> +
> +	spin_unlock_irqrestore(&chan->uart.lock, flags);
> +
> +	/* build a compound spi message to set all registers */
> +	cmds = sc16is7x2_alloc_spi_cmds(9);
> +	if (!cmds)
> +		return;
> +
> +	/* set DLAB */
> +	sc16is7x2_add_write_cmd(&cmds[0], UART_LCR, ch, UART_LCR_DLAB);
> +	/* set divisor, must be set before UART_EFR_ECB */
> +	sc16is7x2_add_dl_write_cmd(&cmds[1], ch, quot);
> +	sc16is7x2_add_write_cmd(&cmds[3], UART_LCR, ch, 0xBF);	/* access EFR */
> +	sc16is7x2_add_write_cmd(&cmds[4], UART_EFR, ch, efr);
> +	sc16is7x2_add_write_cmd(&cmds[5], UART_LCR, ch, lcr);	/* reset DLAB */
> +	sc16is7x2_add_write_cmd(&cmds[6], UART_FCR, ch, fcr);
> +	sc16is7x2_add_write_cmd(&cmds[7], UART_MCR, ch, mcr);
> +	sc16is7x2_add_write_cmd(&cmds[8], UART_IER, ch, ier);
> +
> +	sc16is7x2_spi_async(ts->spi, cmds, 9);
> +
> +	/* Don't rewrite B0 */
> +	if (tty_termios_baud_rate(termios))
> +		tty_termios_encode_baud_rate(termios, baud, baud);
> +}
> +
> +static const char *
> +sc16is7x2_type(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +	return TYPE_NAME;
> +}
> +
> +static void sc16is7x2_release_port(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +	ts->force_end_work = 1;
> +}
> +
> +static int sc16is7x2_request_port(struct uart_port *port)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +	return 0;
> +}
> +
> +static void sc16is7x2_config_port(struct uart_port *port, int flags)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +	if (flags & UART_CONFIG_TYPE)
> +		chan->uart.type = PORT_SC16IS7X2;
> +}
> +
> +static int
> +sc16is7x2_verify_port(struct uart_port *port, struct serial_struct *ser)
> +{
> +	struct sc16is7x2_channel *chan =
> +			container_of(port, struct sc16is7x2_channel, uart);
> +	struct sc16is7x2_chip *ts = chan->chip;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +	if (ser->irq < 0 || ser->baud_base < 9600 ||
> +			ser->type != PORT_SC16IS7X2)
> +		return -EINVAL;
> +	return 0;
> +}
> +
> +static struct uart_ops sc16is7x2_uart_ops = {
> +	.tx_empty	= sc16is7x2_tx_empty,
> +	.set_mctrl	= sc16is7x2_set_mctrl,
> +	.get_mctrl	= sc16is7x2_get_mctrl,
> +	.stop_tx        = sc16is7x2_stop_tx,
> +	.start_tx	= sc16is7x2_start_tx,
> +	.stop_rx	= sc16is7x2_stop_rx,
> +	.enable_ms      = sc16is7x2_enable_ms,
> +	.break_ctl      = sc16is7x2_break_ctl,
> +	.startup	= sc16is7x2_startup,
> +	.shutdown	= sc16is7x2_shutdown,
> +	.set_termios	= sc16is7x2_set_termios,
> +	.type		= sc16is7x2_type,
> +	.release_port   = sc16is7x2_release_port,
> +	.request_port   = sc16is7x2_request_port,
> +	.config_port	= sc16is7x2_config_port,
> +	.verify_port	= sc16is7x2_verify_port,
> +};
> +
> +
> +/* ******************************** GPIO ********************************* */
> +
> +static int sc16is7x2_gpio_request(struct gpio_chip *gpio, unsigned offset)
> +{
> +	struct sc16is7x2_chip *ts =
> +			container_of(gpio, struct sc16is7x2_chip, gpio);
> +	int control = (offset < 4) ? IOC_GPIO30 : IOC_GPIO74;
> +	int ret = 0;
> +
> +	BUG_ON(offset > 8);
> +	dev_dbg(&ts->spi->dev, "%s: offset = %d\n", __func__, offset);
> +
> +	mutex_lock(&ts->lock);
> +
> +	/* GPIO 0:3 and 4:7 can only be controlled as block */
> +	ts->io_gpio |= BIT(offset);
> +	if (ts->io_control & control) {
> +		dev_dbg(&ts->spi->dev, "activate GPIOs %s\n",
> +				(offset < 4) ? "0-3" : "4-7");
> +		ts->io_control &= ~control;
> +		ret = sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control);
> +	}
> +
> +	mutex_unlock(&ts->lock);
> +
> +	return ret;
> +}
> +
> +static void sc16is7x2_gpio_free(struct gpio_chip *gpio, unsigned offset)
> +{
> +	struct sc16is7x2_chip *ts =
> +			container_of(gpio, struct sc16is7x2_chip, gpio);
> +	int control = (offset < 4) ? IOC_GPIO30 : IOC_GPIO74;
> +	int mask = (offset < 4) ? 0x0f : 0xf0;
> +
> +	BUG_ON(offset > 8);
> +
> +	mutex_lock(&ts->lock);
> +
> +	/* GPIO 0:3 and 4:7 can only be controlled as block */
> +	ts->io_gpio &= ~BIT(offset);
> +	dev_dbg(&ts->spi->dev, "%s: io_gpio = 0x%02X\n", __func__, ts->io_gpio);
> +	if (!(ts->io_control & control) && !(ts->io_gpio & mask)) {
> +		dev_dbg(&ts->spi->dev, "deactivate GPIOs %s\n",
> +				(offset < 4) ? "0-3" : "4-7");
> +		ts->io_control |= control;
> +		sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control);
> +	}
> +
> +	mutex_unlock(&ts->lock);
> +}
> +
> +static int sc16is7x2_direction_input(struct gpio_chip *gpio, unsigned offset)
> +{
> +	struct sc16is7x2_chip *ts =
> +			container_of(gpio, struct sc16is7x2_chip, gpio);
> +	unsigned io_dir;
> +
> +	BUG_ON(offset > 8);
> +
> +	mutex_lock(&ts->lock);
> +
> +	ts->io_dir &= ~BIT(offset);
> +	io_dir = ts->io_dir;
> +
> +	mutex_unlock(&ts->lock);
> +
> +	return sc16is7x2_write_async(ts->spi, REG_IOD, 0, io_dir);
> +}
> +
> +static int sc16is7x2_direction_output(struct gpio_chip *gpio, unsigned offset,
> +				    int value)
> +{
> +	struct sc16is7x2_chip *ts =
> +			container_of(gpio, struct sc16is7x2_chip, gpio);
> +	struct sc16is7x2_spi_reg *cmds;
> +
> +	BUG_ON(offset > 8);
> +
> +	mutex_lock(&ts->lock);
> +
> +	if (value)
> +		ts->io_state |= BIT(offset);
> +	else
> +		ts->io_state &= ~BIT(offset);
> +
> +	ts->io_dir |= BIT(offset);
> +
> +	cmds = sc16is7x2_alloc_spi_cmds(2);
> +	if (cmds) {
> +		sc16is7x2_add_write_cmd(&cmds[0], REG_IOS, 0, ts->io_state);
> +		sc16is7x2_add_write_cmd(&cmds[1], REG_IOD, 0, ts->io_dir);
> +	}
> +
> +	mutex_unlock(&ts->lock);
> +
> +	return sc16is7x2_spi_async(ts->spi, cmds, 2);
> +}
> +
> +static int sc16is7x2_get(struct gpio_chip *gpio, unsigned offset)
> +{
> +	struct sc16is7x2_chip *ts =
> +			container_of(gpio, struct sc16is7x2_chip, gpio);
> +	int level = -EINVAL;
> +
> +	BUG_ON(offset > 8);
> +
> +	mutex_lock(&ts->lock);
> +
> +	if (ts->io_dir & BIT(offset)) {
> +		/* Output: return cached level */
> +		level = (ts->io_state >> offset) & 0x01;
> +	} else {
> +		/* Input: read out all pins */
> +		level = sc16is7x2_read(ts->spi, REG_IOS, 0);
> +		if (level >= 0) {
> +			ts->io_state = level;
> +			level = (ts->io_state >> offset) & 0x01;
> +		}
> +	}
> +
> +	mutex_unlock(&ts->lock);
> +
> +	return level;
> +}
> +
> +static void sc16is7x2_set(struct gpio_chip *gpio, unsigned offset, int value)
> +{
> +	struct sc16is7x2_chip *ts =
> +			container_of(gpio, struct sc16is7x2_chip, gpio);
> +	unsigned io_state;
> +
> +	BUG_ON(offset > 8);
> +
> +	mutex_lock(&ts->lock);
> +
> +	if (value)
> +		ts->io_state |= BIT(offset);
> +	else
> +		ts->io_state &= ~BIT(offset);
> +	io_state = ts->io_state;
> +
> +	mutex_unlock(&ts->lock);
> +
> +	sc16is7x2_write_async(ts->spi, REG_IOS, 0, io_state);
> +}
> +
> +/* ******************************** IRQ ********************************* */
> +
> +static void sc16is7x2_handle_fifo_rx(struct sc16is7x2_channel *chan)
> +{
> +	struct uart_port *uart = &chan->uart;
> +	struct tty_struct *tty = uart->state->port.tty;
> +	u8 *rxbuf = chan->fifo_rx.rx_buf;
> +	u8 lsr = chan->lsr;
> +	unsigned i, count = chan->fifo_rx.len;
> +	unsigned long flags;
> +	char flag = TTY_NORMAL;
> +
> +	spin_lock_irqsave(&uart->lock, flags);
> +
> +	if (unlikely(lsr & UART_LSR_BRK_ERROR_BITS)) {
> +		/*
> +		 * For statistics only
> +		 */
> +		if (lsr & UART_LSR_BI) {
> +			lsr &= ~(UART_LSR_FE | UART_LSR_PE);
> +			chan->uart.icount.brk++;
> +			/*
> +			 * We do the SysRQ and SAK checking
> +			 * here because otherwise the break
> +			 * may get masked by ignore_status_mask
> +			 * or read_status_mask.
> +			 */
> +			if (uart_handle_break(&chan->uart))
> +				goto ignore_char;
> +		} else if (lsr & UART_LSR_PE)
> +			chan->uart.icount.parity++;
> +		else if (lsr & UART_LSR_FE)
> +			chan->uart.icount.frame++;
> +		if (lsr & UART_LSR_OE)
> +			chan->uart.icount.overrun++;
> +
> +		/*
> +		 * Mask off conditions which should be ignored.
> +		 */
> +		lsr &= chan->uart.read_status_mask;
> +
> +		if (lsr & UART_LSR_BI)
> +			flag = TTY_BREAK;
> +		else if (lsr & UART_LSR_PE)
> +			flag = TTY_PARITY;
> +		else if (lsr & UART_LSR_FE)
> +			flag = TTY_FRAME;
> +	}
> +
> +	for (i = 1; i < count; i++) {
> +		uart->icount.rx++;
> +
> +		if (!uart_handle_sysrq_char(uart, rxbuf[i]))
> +			uart_insert_char(uart, lsr, UART_LSR_OE,
> +					rxbuf[i], flag);
> +	}
> +
> +ignore_char:
> +	spin_unlock_irqrestore(&uart->lock, flags);
> +
> +	if (count > 1)
> +		tty_flip_buffer_push(tty);
> +}
> +
> +static void sc16is7x2_handle_fifo_tx(struct sc16is7x2_channel *chan)
> +{
> +	struct uart_port *uart = &chan->uart;
> +	struct circ_buf *xmit = &uart->state->xmit;
> +	unsigned count = chan->fifo_tx[1].len + chan->fifo_tx[2].len;
> +	unsigned long flags;
> +
> +	BUG_ON(!uart);
> +	BUG_ON(!xmit);
> +
> +	spin_lock_irqsave(&uart->lock, flags);
> +
> +	uart->icount.tx += count;
> +	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
> +		uart_write_wakeup(uart);
> +
> +	if (uart_circ_empty(xmit))
> +		__stop_tx(chan);
> +
> +	spin_unlock_irqrestore(&uart->lock, flags);
> +}
> +
> +static bool sc16is7x2_msg_add_fifo_rx(struct sc16is7x2_chip *ts, unsigned ch)
> +{
> +	struct spi_message *m = &(ts->fifo_message);
> +	struct spi_transfer *t = &(ts->channel[ch].fifo_rx);
> +	int rxlvl = sc16is7x2_read(ts->spi, REG_RXLVL, ch);
> +
> +	if (rxlvl > 0) {
> +		t->len = rxlvl + 1;
> +		spi_message_add_tail(t, m);
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static bool sc16is7x2_msg_add_fifo_tx(struct sc16is7x2_chip *ts, unsigned ch)
> +{
> +	struct sc16is7x2_channel * const chan = &(ts->channel[ch]);
> +	struct uart_port *uart = &chan->uart;
> +	struct circ_buf *xmit = &uart->state->xmit;
> +	unsigned count;
> +	bool split_transfer;
> +	u8 txlvl;
> +
> +	if (chan->uart.x_char && chan->lsr & UART_LSR_THRE) {
> +		dev_dbg(&ts->spi->dev, "tx: x-char\n");
> +		sc16is7x2_write(ts->spi, UART_TX, ch, uart->x_char);
> +		uart->icount.tx++;
> +		uart->x_char = 0;
> +		return false;
> +	}
> +	if (uart_tx_stopped(&chan->uart)) {
> +		dev_dbg(&ts->spi->dev, "tx: stopped!\n");
> +		sc16is7x2_stop_tx(uart);
> +		return false;
> +	}
> +	if (uart_circ_empty(xmit)) {
> +		__stop_tx(chan);
> +		return false;
> +	}
> +
> +	txlvl = sc16is7x2_read(ts->spi, REG_TXLVL, ch);
> +	if (txlvl <= 0) {
> +		dev_dbg(&ts->spi->dev, " fifo full\n");
> +		return false;
> +	}
> +
> +	/* number of bytes to transfer to the fifo */
> +	count = min(txlvl, (u8)uart_circ_chars_pending(xmit));
> +	split_transfer = (UART_XMIT_SIZE - xmit->tail) <= count;
> +
> +	/* add command transfer */
> +	spi_message_add_tail(&(chan->fifo_tx[0]), &(ts->fifo_message));
> +	/* add first fifo transfer */
> +	spi_message_add_tail(&(chan->fifo_tx[1]), &(ts->fifo_message));
> +
> +	chan->fifo_tx[1].tx_buf = xmit->buf + xmit->tail;
> +
> +	if (!split_transfer) {
> +		chan->fifo_tx[1].len = count;
> +		chan->fifo_tx[1].cs_change = true;
> +
> +		chan->fifo_tx[2].len = 0;
> +	} else {
> +		chan->fifo_tx[1].len = (UART_XMIT_SIZE - 1) - xmit->tail;
> +		chan->fifo_tx[1].cs_change = false;
> +
> +		chan->fifo_tx[2].tx_buf = xmit->buf;
> +		chan->fifo_tx[2].cs_change = true;
> +		chan->fifo_tx[2].len = count - chan->fifo_tx[1].len;
> +		/* add second fifo transfer */
> +		spi_message_add_tail(&(chan->fifo_tx[2]), &(ts->fifo_message));
> +	}
> +
> +	xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
> +	return true;
> +}
> +
> +static void sc16is7x2_handle_modem(struct sc16is7x2_chip *ts, unsigned ch)
> +{
> +	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
> +	struct uart_port *uart = &chan->uart;
> +
> +	if (chan->msr & UART_MSR_ANY_DELTA
> +			&& chan->ier & UART_IER_MSI
> +			&& uart->state != NULL) {
> +		if (chan->msr & UART_MSR_TERI)
> +			uart->icount.rng++;
> +		if (chan->msr & UART_MSR_DDSR)
> +			uart->icount.dsr++;
> +		if (chan->msr & UART_MSR_DDCD)
> +			uart_handle_dcd_change(uart, chan->msr & UART_MSR_DCD);
> +		if (chan->msr & UART_MSR_DCTS)
> +			uart_handle_cts_change(uart, chan->msr & UART_MSR_CTS);
> +
> +		wake_up_interruptible(&uart->state->port.delta_msr_wait);
> +	}
> +}
> +
> +static void sc16is7x2_read_status(struct sc16is7x2_chip *ts, unsigned ch)
> +{
> +	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
> +
> +	chan->iir = sc16is7x2_read(ts->spi, UART_IIR, ch);
> +	chan->msr = sc16is7x2_read(ts->spi, UART_MSR, ch);
> +	chan->lsr = sc16is7x2_read(ts->spi, UART_LSR, ch);
> +}
> +
> +static bool sc16is7x2_handle_channel(struct sc16is7x2_chip *ts, unsigned ch)
> +{
> +	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
> +	struct spi_message *m = &(ts->fifo_message);
> +	bool rx, tx;
> +
> +	dev_dbg(&ts->spi->dev, "%s (%i)\n", __func__, ch);
> +
> +	sc16is7x2_read_status(ts, ch);
> +	sc16is7x2_handle_modem(ts, ch);
> +
> +	spi_message_init(m);
> +	rx = sc16is7x2_msg_add_fifo_rx(ts, ch);
> +	tx = sc16is7x2_msg_add_fifo_tx(ts, ch);
> +
> +	if (rx || tx)
> +		spi_sync(ts->spi, m);
> +
> +	if (rx)
> +		sc16is7x2_handle_fifo_rx(chan);
> +	if (tx)
> +		sc16is7x2_handle_fifo_tx(chan);
> +
> +	dev_dbg(&ts->spi->dev, "%s finished (iir = 0x%02x)\n",
> +			__func__, chan->iir);
> +
> +	return (chan->iir & UART_IIR_NO_INT) == 0x00;
> +}
> +
> +static void sc16is7x2_work(struct work_struct *w)
> +{
> +	struct sc16is7x2_chip *ts =
> +			container_of(w, struct sc16is7x2_chip, work);
> +	unsigned pending = 0;
> +	unsigned ch = 0;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +	BUG_ON(!w);
> +	BUG_ON(!ts);
> +
> +
> +	if (ts->force_end_work) {
> +		dev_dbg(&ts->spi->dev, "%s: force end!\n", __func__);
> +		return;
> +	}
> +
> +	if (ts->channel[0].active)
> +		pending |= BIT(0);
> +	if (ts->channel[1].active)
> +		pending |= BIT(1);
> +
> +	do {
> +		mutex_lock(&(ts->channel[ch].lock));
> +		if (pending & BIT(ch) && ts->channel[ch].active) {
> +			if (!sc16is7x2_handle_channel(ts, ch))
> +				pending &= ~BIT(ch);
> +		}
> +		mutex_unlock(&(ts->channel[ch].lock));
> +		ch ^= 1;	/* switch channel */
> +	} while (!ts->force_end_work && !freezing(current) && pending);
> +
> +	dev_dbg(&ts->spi->dev, "%s finished\n", __func__);
> +}
> +
> +static irqreturn_t sc16is7x2_interrupt(int irq, void *dev_id)
> +{
> +	struct sc16is7x2_chip *ts = dev_id;
> +
> +	dev_dbg(&ts->spi->dev, "%s\n", __func__);
> +
> +	if (!ts->force_end_work && !work_pending(&ts->work) &&
> +	    !freezing(current) && !ts->suspending)
> +		queue_work(ts->workqueue, &ts->work);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +/* ******************************** INIT ********************************* */
> +
> +static struct uart_driver sc16is7x2_uart_driver;
> +
> +static int sc16is7x2_register_gpio(struct sc16is7x2_chip *ts,
> +		struct sc16is7x2_platform_data *pdata)
> +{
> +	struct sc16is7x2_spi_reg *cmds;
> +
> +	ts->gpio.label = (pdata->label) ? pdata->label : DRIVER_NAME;
> +	ts->gpio.request	= sc16is7x2_gpio_request;
> +	ts->gpio.free		= sc16is7x2_gpio_free;
> +	ts->gpio.get		= sc16is7x2_get;
> +	ts->gpio.set		= sc16is7x2_set;
> +	ts->gpio.direction_input = sc16is7x2_direction_input;
> +	ts->gpio.direction_output = sc16is7x2_direction_output;
> +
> +	ts->gpio.base = pdata->gpio_base;
> +	ts->gpio.names = pdata->names;
> +	ts->gpio.ngpio = SC16IS7X2_NR_GPIOS;
> +	ts->gpio.can_sleep = 1;
> +	ts->gpio.dev = &ts->spi->dev;
> +	ts->gpio.owner = THIS_MODULE;
> +
> +	/* disable all GPIOs, enable on request */
> +	ts->io_dir = 0x0f;
> +	ts->io_state = 0;
> +	ts->io_gpio = 0;
> +	ts->io_control = IOC_GPIO30 | IOC_GPIO74;
> +
> +	cmds = sc16is7x2_alloc_spi_cmds(4);
> +	if (!cmds)
> +		return -ENOMEM;
> +
> +	sc16is7x2_add_write_cmd(&cmds[0], REG_IOI, 0, 0);
> +	sc16is7x2_add_write_cmd(&cmds[1], REG_IOC, 0, ts->io_control);
> +	sc16is7x2_add_write_cmd(&cmds[2], REG_IOS, 0, ts->io_state);
> +	sc16is7x2_add_write_cmd(&cmds[3], REG_IOD, 0, ts->io_dir);
> +	sc16is7x2_spi_async(ts->spi, cmds, 4);
> +
> +	return gpiochip_add(&ts->gpio);
> +}
> +
> +static int sc16is7x2_register_uart_port(struct sc16is7x2_chip *ts,
> +		struct sc16is7x2_platform_data *pdata, unsigned ch)
> +{
> +	struct sc16is7x2_channel *chan = &(ts->channel[ch]);
> +	struct uart_port *uart = &chan->uart;
> +
> +	mutex_init(&chan->lock);
> +	chan->active = false;	/* will be set in startup */
> +	chan->chip = ts;
> +
> +	chan->rx_buf = kzalloc(FIFO_SIZE+1, GFP_KERNEL);
> +	if (chan->rx_buf == NULL)
> +		return -ENOMEM;
> +
> +	chan->read_fifo_cmd = read_cmd(UART_RX, ch);
> +	chan->fifo_rx.cs_change = true;
> +	chan->fifo_rx.tx_buf = &(chan->read_fifo_cmd);
> +	chan->fifo_rx.rx_buf = chan->rx_buf;
> +
> +
> +	chan->write_fifo_cmd = write_cmd(UART_TX, ch);
> +	chan->fifo_tx[0].tx_buf = &(chan->write_fifo_cmd);
> +	chan->fifo_tx[0].rx_buf = NULL;
> +	chan->fifo_tx[0].len = 1;
> +	chan->fifo_tx[0].cs_change = false;
> +	chan->fifo_tx[1].rx_buf = NULL;
> +	chan->fifo_tx[2].rx_buf = NULL;
> +
> +	uart->irq = ts->spi->irq;
> +	uart->uartclk = pdata->uartclk;
> +	uart->fifosize = FIFO_SIZE;
> +	uart->ops = &sc16is7x2_uart_ops;
> +	uart->flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF;
> +	uart->line = pdata->uart_base + ch;
> +	uart->type = PORT_SC16IS7X2;
> +	uart->dev = &ts->spi->dev;
> +
> +	return uart_add_one_port(&sc16is7x2_uart_driver, uart);
> +}
> +
> +static int sc16is7x2_unregister_uart_port(struct sc16is7x2_chip *ts,
> +		unsigned channel)
> +{
> +	int ret;
> +
> +	kfree(&ts->channel[channel].rx_buf);
> +	ret = uart_remove_one_port(&sc16is7x2_uart_driver,
> +			&ts->channel[channel].uart);

Shouldn't the uart be unregistered *before* deallocating the buffers
it uses?

> +	if (ret)
> +		dev_err(&ts->spi->dev, "Failed to remove the UART port %c: %d\n",
> +			'A' + channel, ret);
> +
> +	return ret;
> +}
> +
> +
> +static int __devinit sc16is7x2_probe(struct spi_device *spi)
> +{
> +	struct sc16is7x2_chip *ts;
> +	struct sc16is7x2_platform_data *pdata;
> +	int ret;
> +
> +	pdata = spi->dev.platform_data;
> +	if (!pdata || !pdata->gpio_base /* || pdata->uart_base */) {

Remove the commented out block.  Also, the driver really should be
able to dynamically assign a uart_base (not a show-stopper though).

> +		dev_dbg(&spi->dev, "incorrect or missing platform data\n");
> +		return -EINVAL;
> +	}
> +
> +	ts = kzalloc(sizeof(struct sc16is7x2_chip), GFP_KERNEL);
> +	if (!ts)
> +		return -ENOMEM;
> +
> +	mutex_init(&ts->lock);
> +	spi_set_drvdata(spi, ts);
> +	ts->spi = spi;
> +	ts->force_end_work = 1;
> +
> +	/* Reset the chip TODO: and disable IRQ output */
> +	sc16is7x2_write(spi, REG_IOC, 0, IOC_SRESET);
> +
> +	ret = request_irq(spi->irq, sc16is7x2_interrupt,
> +			IRQF_TRIGGER_FALLING | IRQF_SHARED, "sc16is7x2", ts);
> +	if (ret) {
> +		dev_warn(&ts->spi->dev, "cannot register interrupt\n");
> +		goto exit_destroy;
> +	}
> +
> +	ret = sc16is7x2_register_uart_port(ts, pdata, 0);
> +	if (ret)
> +		goto exit_irq;
> +	ret = sc16is7x2_register_uart_port(ts, pdata, 1);
> +	if (ret)
> +		goto exit_uart0;
> +
> +	ret = sc16is7x2_register_gpio(ts, pdata);
> +	if (ret)
> +		goto exit_uart1;
> +
> +	ts->workqueue = create_freezeable_workqueue(DRIVER_NAME);
> +	if (!ts->workqueue) {
> +		dev_warn(&ts->spi->dev, "cannot create workqueue\n");
> +		ret = -EBUSY;
> +		goto exit_gpio;
> +	}
> +	INIT_WORK(&ts->work, sc16is7x2_work);
> +	ts->force_end_work = 0;
> +
> +	printk(KERN_INFO DRIVER_NAME " at CS%d (irq %d), 2 UARTs, 8 GPIOs\n"
> +			"    eser%d, eser%d, gpiochip%d\n",
> +			spi->chip_select, spi->irq,
> +			pdata->uart_base, pdata->uart_base + 1,
> +			pdata->gpio_base);

Use dev_info() instead of printk(KERN_INFO ...)

> +
> +	return ret;
> +
> +exit_gpio:
> +	ret = gpiochip_remove(&ts->gpio);
> +
> +exit_uart1:
> +	sc16is7x2_unregister_uart_port(ts, 1);
> +
> +exit_uart0:
> +	sc16is7x2_unregister_uart_port(ts, 0);
> +
> +exit_irq:
> +	free_irq(spi->irq, ts);
> +
> +exit_destroy:
> +	dev_set_drvdata(&spi->dev, NULL);
> +	mutex_destroy(&ts->lock);
> +	kfree(ts);
> +	return ret;
> +}
> +
> +static int __devexit sc16is7x2_remove(struct spi_device *spi)
> +{
> +	struct sc16is7x2_chip *ts = spi_get_drvdata(spi);
> +	int ret;
> +
> +	if (ts == NULL)
> +		return -ENODEV;
> +
> +	free_irq(spi->irq, ts);
> +	ts->force_end_work = 1;
> +
> +	if (ts->workqueue) {
> +		flush_workqueue(ts->workqueue);
> +		destroy_workqueue(ts->workqueue);
> +		ts->workqueue = NULL;
> +	}
> +
> +	ret = sc16is7x2_unregister_uart_port(ts, 0);
> +	if (ret)
> +		goto exit_error;
> +	ret = sc16is7x2_unregister_uart_port(ts, 1);
> +	if (ret)
> +		goto exit_error;
> +	ret = gpiochip_remove(&ts->gpio);
> +	if (ret) {
> +		dev_err(&spi->dev, "Failed to remove the GPIO controller: %d\n",
> +			ret);
> +		goto exit_error;
> +	}
> +
> +	mutex_destroy(&ts->lock);
> +	kfree(ts);
> +
> +exit_error:
> +	return ret;
> +}
> +
> +static struct uart_driver sc16is7x2_uart_driver = {
> +	.owner          = THIS_MODULE,
> +	.driver_name    = DRIVER_NAME,
> +	.dev_name       = "eser",
> +	.major          = SC16IS7X2_MAJOR,
> +	.minor          = SC16IS7X2_MINOR,
> +	.nr             = MAX_SC16IS7X2,
> +};
> +
> +static struct spi_driver sc16is7x2_spi_driver = {
> +	.driver = {
> +		.name		= DRIVER_NAME,
> +		.owner		= THIS_MODULE,
> +	},
> +	.probe		= sc16is7x2_probe,
> +	.remove		= __devexit_p(sc16is7x2_remove),
> +};
> +
> +static int __init sc16is7x2_init(void)
> +{
> +	int ret = uart_register_driver(&sc16is7x2_uart_driver);
> +
> +	if (ret) {
> +		printk(KERN_ERR "Couldn't register sc16is7x2 uart driver\n");

dev_err() instead of printk()

> +		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);

Ordering issue.  The drivers must be unregistered in the reverse order
from how they are registered.  So, unregister spi first; and then
unregister uart.

> +}
> +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 -puN include/linux/serial_core.h~serial-add-sc16is7x2-driver include/linux/serial_core.h
> --- a/include/linux/serial_core.h~serial-add-sc16is7x2-driver
> +++ a/include/linux/serial_core.h
> @@ -47,6 +47,9 @@
>  #define PORT_U6_16550A	19	/* ST-Ericsson U6xxx internal UART */
>  #define PORT_MAX_8250	19	/* max port ID */
>
> +/* SC16IS7x2 SPI UART */
> +#define PORT_SC16IS7X2	19
> +

As Greg mentioned, this is wrong.  You need to define a new uart type
number.  However, this is *not* a 8250 style UART, so it shouldn't be
in the 1-18 range of numbers.  Add a new number to the end of the
list.  Looks like # 96 is free.

>  /*
>   * ARM specific type numbers.  These are not currently guaranteed
>   * to be implemented, and will change in the future.  These are
> diff -puN /dev/null include/linux/spi/sc16is7x2.h
> --- /dev/null
> +++ a/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

As mentioned above, get this file out of the linux/spi include
directory since it is a uart driver, not an spi driver.  It just
happens to be spi attached.

g.
--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux PPP]     [Linux FS]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Linmodem]     [Device Mapper]     [Linux Kernel for ARM]

  Powered by Linux