From: Grant Likely <grant.likely@secretlab.ca>
To: akpm@linux-foundation.org
Cc: greg@kroah.com, linux-serial@vger.kernel.org,
manuel.stahl@iis.fraunhofer.de
Subject: Re: [patch 1/3] serial: add sc16is7x2 driver
Date: Thu, 7 Oct 2010 07:55:44 -0600 [thread overview]
Message-ID: <AANLkTikN6oi8PGAP6yDT6U_FhdykT8E68oED242cPEqS@mail.gmail.com> (raw)
In-Reply-To: <201010012118.o91LI3dx021213@imap1.linux-foundation.org>
On Fri, Oct 01, 2010 at 02:18:03PM -0700, akpm@linux-foundation.org wrote:
> From: Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
>
> Add support for the sc16is7x2 chips.
>
> [akpm@linux-foundation.org: fix broken indenting]
> Signed-off-by: Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
> Cc: Greg KH <greg@kroah.com>
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> ---
>
> 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@iis.fraunhofer.de>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * The SC16IS7x2 device is a SPI driven dual UART with GPIOs.
> + *
> + * The driver exports two uarts and a gpiochip interface.
> + */
> +
> +/* #define DEBUG */
> +
> +#include <linux/init.h>
> +#include <linux/platform_device.h>
> +#include <linux/mutex.h>
> +#include <linux/spi/spi.h>
> +#include <linux/freezer.h>
> +#include <linux/spi/sc16is7x2.h>
> +#include <linux/serial_core.h>
> +#include <linux/serial_reg.h>
> +#include <linux/gpio.h>
> +
> +#define SC16IS7X2_MAJOR 204
> +#define SC16IS7X2_MINOR 209
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.
prev parent reply other threads:[~2010-10-07 13:56 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2010-10-01 21:18 [patch 1/3] serial: add sc16is7x2 driver akpm
2010-10-06 20:19 ` Greg KH
2010-10-07 13:55 ` Grant Likely [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=AANLkTikN6oi8PGAP6yDT6U_FhdykT8E68oED242cPEqS@mail.gmail.com \
--to=grant.likely@secretlab.ca \
--cc=akpm@linux-foundation.org \
--cc=greg@kroah.com \
--cc=linux-serial@vger.kernel.org \
--cc=manuel.stahl@iis.fraunhofer.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is 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).