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