* [PATCH] Add sc16is7x2 driver @ 2010-09-03 13:11 Manuel Stahl 2010-09-14 0:25 ` Andrew Morton 0 siblings, 1 reply; 8+ messages in thread From: Manuel Stahl @ 2010-09-03 13:11 UTC (permalink / raw) To: linux-kernel This patch adds support for the sc16is7x2 chips. Signed-off-by: Manuel Stahl <manuel.stahl@iis.fraunhofer.de> --- drivers/serial/Kconfig | 7 + drivers/serial/Makefile | 1 + drivers/serial/sc16is7x2.c | 1351 +++++++++++++++++++++++++++++++++++++++++ include/linux/serial_core.h | 3 + include/linux/spi/sc16is7x2.h | 17 + 5 files changed, 1379 insertions(+), 0 deletions(-) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 12900f7..c336ea6 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -269,6 +269,13 @@ config SERIAL_8250_RM9K comment "Non-8250 serial port support" +config SERIAL_SC16IS7X2 + tristate "SC16IS7x2 chips" + depends on SPI_MASTER && GPIOLIB + select SERIAL_CORE + help + gpio driver for SC16IS7x2 SPI UARTs. + config SERIAL_AMBA_PL010 tristate "ARM AMBA PL010 serial port support" depends on ARM_AMBA && (BROKEN || !ARCH_VERSATILE) diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 1ca4fd5..4c6a297 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_SERIAL_8250_BOCA) += 8250_boca.o obj-$(CONFIG_SERIAL_8250_EXAR_ST16C554) += 8250_exar_st16c554.o obj-$(CONFIG_SERIAL_8250_HUB6) += 8250_hub6.o obj-$(CONFIG_SERIAL_8250_MCA) += 8250_mca.o +obj-$(CONFIG_SERIAL_SC16IS7X2) += sc16is7x2.o obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o diff --git a/drivers/serial/sc16is7x2.c b/drivers/serial/sc16is7x2.c new file mode 100644 index 0000000..3ce3099 --- /dev/null +++ b/drivers/serial/sc16is7x2.c @@ -0,0 +1,1351 @@ +/** + * drivers/serial/sc16is7x2.c + * + * Copyright (C) 2009 Manuel Stahl <manuel.stahl@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 +#define MAX_SC16IS7X2 8 +#define FIFO_SIZE 64 + +#define DRIVER_NAME "sc16is7x2" +#define TYPE_NAME "SC16IS7x2" + + + +#define REG_READ 0x80 +#define REG_WRITE 0x00 + +/* Special registers */ +#define REG_TXLVL 0x08 /* Transmitter FIFO Level register */ +#define REG_RXLVL 0x09 /* Receiver FIFO Level register */ +#define REG_IOD 0x0A /* IO Direction register */ +#define REG_IOS 0x0B /* IO State register */ +#define REG_IOI 0x0C /* IO Interrupt Enable register */ +#define REG_IOC 0x0E /* IO Control register */ + +#define IOC_SRESET 0x08 /* Software reset */ +#define IOC_GPIO30 0x04 /* GPIO 3:0 unset: as IO, set: as modem pins */ +#define IOC_GPIO74 0x02 /* GPIO 7:4 unset: as IO, set: as modem pins */ +#define IOC_IOLATCH 0x01 /* Unset: input unlatched, set: input latched */ + +/* Redefine some MCR bits */ +#ifdef UART_MCR_TCRTLR +#undef UART_MCR_TCRTLR +#endif +#define UART_MCR_TCRTLR 0x04 +#define UART_MCR_IRDA 0x40 + + +#define WRITE_CMD(reg, ch) (REG_WRITE | (reg & 0xf) << 3 | (ch & 0x1) << 1) +#define READ_CMD(reg, ch) (REG_READ | (reg & 0xf) << 3 | (ch & 0x1) << 1) + +/* 16bit SPI command to read or write a register */ +struct sc16is7x2_spi_reg { + u8 cmd; + u8 value; +} __attribute__ ((packed)); + +struct sc16is7x2_chip; + +/* + * Some registers must be read back to modify. + * To save time we cache them here in memory + */ +struct sc16is7x2_channel { + struct sc16is7x2_chip *chip; /* back link */ + struct mutex lock; + struct uart_port uart; + struct spi_transfer fifo_rx; + struct spi_transfer fifo_tx[3]; + u8 iir; + u8 lsr; + u8 msr; + u8 ier; /* cache for IER register */ + u8 fcr; /* cache for FCR register */ + u8 lcr; /* cache for LCR register */ + u8 mcr; /* cache for MCR register */ + u8 rxbuf[FIFO_SIZE+1]; + u8 write_fifo_cmd; + u8 read_fifo_cmd; + bool active; +}; + +struct sc16is7x2_chip { + struct spi_device *spi; + struct gpio_chip gpio; + struct mutex lock; + struct sc16is7x2_channel channel[2]; + + /* for handling irqs: need workqueue since we do spi_sync */ + struct workqueue_struct *workqueue; + struct work_struct work; + /* set to 1 to make the workhandler exit as soon as possible */ + int force_end_work; + /* need to know we are suspending to avoid deadlock on workqueue */ + int suspending; + + struct spi_message fifo_message; + +#define UART_BUG_TXEN BIT(1) /* UART has buggy TX IIR status */ +#define UART_BUG_NOMSR BIT(2) /* UART has buggy MSR status bits (Au1x00) */ +#define UART_BUG_THRE BIT(3) /* UART has buggy THRE reassertion */ + u16 bugs; /* port bugs */ + +#define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS + u8 lsr_saved_flags; +#define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA + u8 msr_saved_flags; + u8 io_dir; /* cache for IODir register */ + u8 io_state; /* cache for IOState register */ + u8 io_gpio; /* PIN is GPIO */ + u8 io_control; /* cache for IOControl register */ +}; + +/* ******************************** SPI ********************************* */ + + +/* + * Reserve memory for command sequence + * @param cnt number of commands + */ +static inline struct sc16is7x2_spi_reg * +sc16is7x2_alloc_spi_cmds(unsigned cnt) +{ + return kzalloc(sizeof(struct sc16is7x2_spi_reg)*cnt, GFP_KERNEL); +} + +/* + * sc16is7x2_add_write_cmd - Add write command to sequence + */ +static inline void sc16is7x2_add_write_cmd(struct sc16is7x2_spi_reg *cmd, + u8 reg, u8 ch, u8 value) +{ + cmd->cmd = WRITE_CMD(reg, ch); + cmd->value = value; +} + +/* + * sc16is7x2_add_read_cmd - Add read command to sequence + */ +static inline void sc16is7x2_add_read_cmd(struct sc16is7x2_spi_reg *cmd, + u8 reg, u8 ch) +{ + cmd->cmd = READ_CMD(reg, ch); + cmd->value = 0; +} + +/* + * sc16is7x2_complete - Completion handler for async SPI transfers + */ +static void sc16is7x2_complete(void *context) +{ + struct spi_message *m = context; + u8 *tx_chain = m->state; + + kfree(tx_chain); + kfree(m); +} + +/* + * sc16is7x2_spi_async - Send command sequence + */ +static int sc16is7x2_spi_async(struct spi_device *spi, + struct sc16is7x2_spi_reg *cmds, unsigned len) +{ + struct spi_transfer *t; + struct spi_message *m; + + m = spi_message_alloc(len, GFP_KERNEL); + if (!m) + return -ENOMEM; + + m->complete = sc16is7x2_complete; + m->context = m; + m->state = cmds; + list_for_each_entry(t, &m->transfers, transfer_list) { + t->tx_buf = (u8 *)cmds; + t->len = 2; + t->cs_change = true; + cmds++; + } + + return spi_async(spi, m); +} + +/* + * sc16is7x2_write_async - Write a new register content (async) + */ +static inline int sc16is7x2_write_async(struct spi_device *spi, u8 reg, u8 ch, + u8 value) +{ + struct sc16is7x2_spi_reg *cmd = sc16is7x2_alloc_spi_cmds(1); + if (!cmd) + return -ENOMEM; + sc16is7x2_add_write_cmd(cmd, reg, ch, value); + return sc16is7x2_spi_async(spi, cmd, 1); +} + +/* + * sc16is7x2_write - Write a new register content (sync) + */ +static int sc16is7x2_write(struct spi_device *spi, u8 reg, u8 ch, u8 val) +{ + u16 word = REG_WRITE | (reg & 0xf) << 3 | (ch & 0x3) << 1 | val << 8; + return spi_write(spi, (const u8 *)&word, sizeof(word)); +} + +/** + * sc16is7x2_read - Read back register content + * @spi: The SPI device + * @reg: Register offset + * + * Returns positive 8 bit value from device if successful or a + * negative value on error + */ +static int sc16is7x2_read(struct spi_device *spi, unsigned reg, unsigned ch) +{ + u8 cmd = REG_READ | (reg & 0xf) << 3 | (ch & 0x3) << 1; + return spi_w8r8(spi, cmd); +} + +/* ******************************** UART ********************************* */ + +/* Uart divisor latch write */ +static inline void sc16is7x2_add_dl_write_cmd(struct sc16is7x2_spi_reg *cmd, + u8 ch, int value) +{ + sc16is7x2_add_write_cmd(&cmd[0], UART_DLL, ch, value & 0xff); + sc16is7x2_add_write_cmd(&cmd[1], UART_DLM, ch, value >> 8 & 0xff); +} + +static unsigned int sc16is7x2_tx_empty(struct uart_port *port) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + unsigned lsr; + + dev_dbg(&ts->spi->dev, "%s\n", __func__); + + mutex_lock(&chan->lock); + lsr = chan->lsr; + mutex_unlock(&chan->lock); + + return lsr & UART_LSR_TEMT ? TIOCSER_TEMT : 0; +} + +static unsigned int sc16is7x2_get_mctrl(struct uart_port *port) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + unsigned int status; + unsigned int ret; + + dev_dbg(&ts->spi->dev, "%s\n", __func__); + + status = chan->msr; + + ret = 0; + if (status & UART_MSR_DCD) + ret |= TIOCM_CAR; + if (status & UART_MSR_RI) + ret |= TIOCM_RNG; + if (status & UART_MSR_DSR) + ret |= TIOCM_DSR; + if (status & UART_MSR_CTS) + ret |= TIOCM_CTS; + return ret; +} + +static unsigned int __set_mctrl(unsigned int mctrl) +{ + unsigned char mcr = 0; + + if (mctrl & TIOCM_RTS) + mcr |= UART_MCR_RTS; + if (mctrl & TIOCM_DTR) + mcr |= UART_MCR_DTR; + if (mctrl & TIOCM_LOOP) + mcr |= UART_MCR_LOOP; + + return mcr; +} + +static void sc16is7x2_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + unsigned ch = port->line & 0x01; + + dev_dbg(&ts->spi->dev, "%s\n", __func__); + sc16is7x2_write_async(ts->spi, UART_MCR, ch, __set_mctrl(mctrl)); +} + +static inline void __stop_tx(struct sc16is7x2_channel *chan) +{ + struct sc16is7x2_chip *ts = chan->chip; + unsigned ch = chan->uart.line & 0x01; + if (chan->ier & UART_IER_THRI) { + chan->ier &= ~UART_IER_THRI; + sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier); + } +} + +static void sc16is7x2_stop_tx(struct uart_port *port) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + + dev_dbg(&ts->spi->dev, "%s\n", __func__); + + __stop_tx(chan); +} + +static void sc16is7x2_start_tx(struct uart_port *port) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + unsigned ch = port->line & 0x01; + + dev_dbg(&ts->spi->dev, "%s\n", __func__); + + if (!(chan->ier & UART_IER_THRI)) { + chan->ier |= UART_IER_THRI; + sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier); + } +} + +static void sc16is7x2_stop_rx(struct uart_port *port) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + unsigned ch = port->line & 0x01; + + dev_dbg(&ts->spi->dev, "%s\n", __func__); + + chan->ier &= ~UART_IER_RLSI; + chan->uart.read_status_mask &= ~UART_LSR_DR; + sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier); +} + +static void sc16is7x2_enable_ms(struct uart_port *port) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + unsigned ch = port->line & 0x01; + + dev_dbg(&ts->spi->dev, "%s\n", __func__); + + chan->ier |= UART_IER_MSI; + sc16is7x2_write_async(ts->spi, UART_IER, ch, chan->ier); +} + +static void sc16is7x2_break_ctl(struct uart_port *port, int break_state) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + unsigned ch = port->line & 0x01; + unsigned long flags; + + dev_dbg(&ts->spi->dev, "%s\n", __func__); + + spin_lock_irqsave(&chan->uart.lock, flags); + if (break_state == -1) + chan->lcr |= UART_LCR_SBC; + else + chan->lcr &= ~UART_LCR_SBC; + spin_unlock_irqrestore(&chan->uart.lock, flags); + + sc16is7x2_write_async(ts->spi, UART_LCR, ch, chan->lcr); +} + +static int sc16is7x2_startup(struct uart_port *port) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + unsigned ch = port->line & 0x01; + struct sc16is7x2_spi_reg *cmds, *cmd; + unsigned long flags; + + dev_dbg(&ts->spi->dev, "%s (line %d)\n", __func__, port->line); + + spin_lock_irqsave(&chan->uart.lock, flags); + chan->lcr = UART_LCR_WLEN8; + chan->mcr = __set_mctrl(chan->uart.mctrl); + chan->fcr = 0; + chan->ier = UART_IER_RLSI | UART_IER_RDI; + spin_unlock_irqrestore(&chan->uart.lock, flags); + + cmds = sc16is7x2_alloc_spi_cmds(8); + if (!cmds) + return -ENOMEM; + + cmd = cmds; + /* Clear the interrupt registers. */ + sc16is7x2_add_write_cmd(cmd, UART_IER, ch, 0); + sc16is7x2_add_read_cmd(++cmd, UART_IIR, ch); + sc16is7x2_add_read_cmd(++cmd, UART_LSR, ch); + sc16is7x2_add_read_cmd(++cmd, UART_MSR, ch); + + sc16is7x2_add_write_cmd(++cmd, UART_FCR, ch, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); + sc16is7x2_add_write_cmd(++cmd, UART_FCR, ch, chan->fcr); + /* Now, initialize the UART */ + sc16is7x2_add_write_cmd(++cmd, UART_LCR, ch, chan->lcr); + sc16is7x2_add_write_cmd(++cmd, UART_MCR, ch, chan->mcr); + + sc16is7x2_spi_async(ts->spi, cmds, 8); + + chan->active = true; + return 0; +} + +static void sc16is7x2_shutdown(struct uart_port *port) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + unsigned long flags; + unsigned ch = port->line & 0x01; + + dev_dbg(&ts->spi->dev, "%s\n", __func__); + + BUG_ON(!chan); + BUG_ON(!ts); + + if (ts->suspending) + return; + + /* Disable interrupts from this port */ + chan->ier = 0; + chan->active = false; + sc16is7x2_write(ts->spi, UART_IER, ch, chan->ier); + + /* Wait for worker of this channel to finish */ + mutex_lock(&chan->lock); + + spin_lock_irqsave(&chan->uart.lock, flags); + chan->mcr = __set_mctrl(chan->uart.mctrl); + spin_unlock_irqrestore(&chan->uart.lock, flags); + + /* Disable break condition and FIFOs */ + chan->lcr &= ~UART_LCR_SBC; + + sc16is7x2_write(ts->spi, UART_MCR, ch, chan->mcr); + sc16is7x2_write(ts->spi, UART_LCR, ch, chan->lcr); + + mutex_unlock(&chan->lock); +} + +static void +sc16is7x2_set_termios(struct uart_port *port, struct ktermios *termios, + struct ktermios *old) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + struct sc16is7x2_spi_reg *cmds; + unsigned ch = port->line & 0x01; + unsigned long flags; + unsigned int baud, quot; + u8 ier, mcr, lcr, fcr = 0; + u8 efr = UART_EFR_ECB; + + /* set word length */ + switch (termios->c_cflag & CSIZE) { + case CS5: + lcr = UART_LCR_WLEN5; + break; + case CS6: + lcr = UART_LCR_WLEN6; + break; + case CS7: + lcr = UART_LCR_WLEN7; + break; + default: + case CS8: + lcr = UART_LCR_WLEN8; + break; + } + + if (termios->c_cflag & CSTOPB) + lcr |= UART_LCR_STOP; + if (termios->c_cflag & PARENB) + lcr |= UART_LCR_PARITY; + if (!(termios->c_cflag & PARODD)) + lcr |= UART_LCR_EPAR; +#ifdef CMSPAR + if (termios->c_cflag & CMSPAR) + lcr |= UART_LCR_SPAR; +#endif + + /* Ask the core to calculate the divisor for us. */ + baud = uart_get_baud_rate(port, termios, old, + port->uartclk / 16 / 0xffff, + port->uartclk / 16); + quot = uart_get_divisor(port, baud); + + dev_dbg(&ts->spi->dev, "%s (baud %u)\n", __func__, baud); + + + /* configure the fifo */ + if (baud < 2400) + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1; + else + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_01; + + /* + * MCR-based auto flow control. When AFE is enabled, RTS will be + * deasserted when the receive FIFO contains more characters than + * the trigger, or the MCR RTS bit is cleared. In the case where + * the remote UART is not using CTS auto flow control, we must + * have sufficient FIFO entries for the latency of the remote + * UART to respond. IOW, at least 32 bytes of FIFO. + */ + chan->mcr &= ~UART_MCR_AFE; + if (termios->c_cflag & CRTSCTS) + chan->mcr |= UART_MCR_AFE; + + /* + * Ok, we're now changing the port state. Do it with + * interrupts disabled. + */ + spin_lock_irqsave(&chan->uart.lock, flags); + + /* we are sending char from a workqueue so enable */ + chan->uart.state->port.tty->low_latency = 1; + + /* Update the per-port timeout. */ + uart_update_timeout(port, termios->c_cflag, baud); + + chan->uart.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR; + if (termios->c_iflag & INPCK) + chan->uart.read_status_mask |= UART_LSR_FE | UART_LSR_PE; + if (termios->c_iflag & (BRKINT | PARMRK)) + chan->uart.read_status_mask |= UART_LSR_BI; + + /* Characters to ignore */ + chan->uart.ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + chan->uart.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE; + if (termios->c_iflag & IGNBRK) { + chan->uart.ignore_status_mask |= UART_LSR_BI; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + chan->uart.ignore_status_mask |= UART_LSR_OE; + } + + /* ignore all characters if CREAD is not set */ + if ((termios->c_cflag & CREAD) == 0) + chan->uart.ignore_status_mask |= UART_LSR_DR; + + /* CTS flow control flag and modem status interrupts */ + chan->ier &= ~UART_IER_MSI; + if (UART_ENABLE_MS(&chan->uart, termios->c_cflag)) + chan->ier |= UART_IER_MSI; + + if (termios->c_cflag & CRTSCTS) + efr |= UART_EFR_CTS | UART_EFR_RTS; + + mcr = __set_mctrl(chan->uart.mctrl); + ier = chan->ier; + chan->lcr = lcr; /* Save LCR */ + chan->fcr = fcr; /* Save FCR */ + chan->mcr = mcr; /* Save MCR */ + + fcr |= UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT; + + spin_unlock_irqrestore(&chan->uart.lock, flags); + + /* build a compound spi message to set all registers */ + cmds = sc16is7x2_alloc_spi_cmds(9); + if (!cmds) + return; + + /* set DLAB */ + sc16is7x2_add_write_cmd(&cmds[0], UART_LCR, ch, UART_LCR_DLAB); + /* set divisor, must be set before UART_EFR_ECB */ + sc16is7x2_add_dl_write_cmd(&cmds[1], ch, quot); + sc16is7x2_add_write_cmd(&cmds[3], UART_LCR, ch, 0xBF); /* access EFR */ + sc16is7x2_add_write_cmd(&cmds[4], UART_EFR, ch, efr); + sc16is7x2_add_write_cmd(&cmds[5], UART_LCR, ch, lcr); /* reset DLAB */ + sc16is7x2_add_write_cmd(&cmds[6], UART_FCR, ch, fcr); + sc16is7x2_add_write_cmd(&cmds[7], UART_MCR, ch, mcr); + sc16is7x2_add_write_cmd(&cmds[8], UART_IER, ch, ier); + + sc16is7x2_spi_async(ts->spi, cmds, 9); + + /* Don't rewrite B0 */ + if (tty_termios_baud_rate(termios)) + tty_termios_encode_baud_rate(termios, baud, baud); +} + +static const char * +sc16is7x2_type(struct uart_port *port) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + dev_dbg(&ts->spi->dev, "%s\n", __func__); + return TYPE_NAME; +} + +static void sc16is7x2_release_port(struct uart_port *port) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + dev_dbg(&ts->spi->dev, "%s\n", __func__); + ts->force_end_work = 1; +} + +static int sc16is7x2_request_port(struct uart_port *port) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + dev_dbg(&ts->spi->dev, "%s\n", __func__); + return 0; +} + +static void sc16is7x2_config_port(struct uart_port *port, int flags) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + dev_dbg(&ts->spi->dev, "%s\n", __func__); + if (flags & UART_CONFIG_TYPE) + chan->uart.type = PORT_SC16IS7X2; +} + +static int +sc16is7x2_verify_port(struct uart_port *port, struct serial_struct *ser) +{ + struct sc16is7x2_channel *chan = + container_of(port, struct sc16is7x2_channel, uart); + struct sc16is7x2_chip *ts = chan->chip; + dev_dbg(&ts->spi->dev, "%s\n", __func__); + if (ser->irq < 0 || ser->baud_base < 9600 || + ser->type != PORT_SC16IS7X2) + return -EINVAL; + return 0; +} + +static struct uart_ops sc16is7x2_uart_ops = { + .tx_empty = sc16is7x2_tx_empty, + .set_mctrl = sc16is7x2_set_mctrl, + .get_mctrl = sc16is7x2_get_mctrl, + .stop_tx = sc16is7x2_stop_tx, + .start_tx = sc16is7x2_start_tx, + .stop_rx = sc16is7x2_stop_rx, + .enable_ms = sc16is7x2_enable_ms, + .break_ctl = sc16is7x2_break_ctl, + .startup = sc16is7x2_startup, + .shutdown = sc16is7x2_shutdown, + .set_termios = sc16is7x2_set_termios, + .type = sc16is7x2_type, + .release_port = sc16is7x2_release_port, + .request_port = sc16is7x2_request_port, + .config_port = sc16is7x2_config_port, + .verify_port = sc16is7x2_verify_port, +}; + + +#define MIN(a, b) ((a < b) ? (a) : (b)) + +/* ******************************** GPIO ********************************* */ + +static int sc16is7x2_gpio_request(struct gpio_chip *gpio, unsigned offset) +{ + struct sc16is7x2_chip *ts = + container_of(gpio, struct sc16is7x2_chip, gpio); + int control = (offset < 4) ? IOC_GPIO30 : IOC_GPIO74; + int ret = 0; + + BUG_ON(offset > 8); + dev_dbg(&ts->spi->dev, "%s: offset = %d\n", __func__, offset); + + mutex_lock(&ts->lock); + + /* GPIO 0:3 and 4:7 can only be controlled as block */ + ts->io_gpio |= BIT(offset); + if (ts->io_control & control) { + dev_dbg(&ts->spi->dev, "activate GPIOs %s\n", + (offset < 4) ? "0-3" : "4-7"); + ts->io_control &= ~control; + ret = sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control); + } + + mutex_unlock(&ts->lock); + + return ret; +} + +static void sc16is7x2_gpio_free(struct gpio_chip *gpio, unsigned offset) +{ + struct sc16is7x2_chip *ts = + container_of(gpio, struct sc16is7x2_chip, gpio); + int control = (offset < 4) ? IOC_GPIO30 : IOC_GPIO74; + int mask = (offset < 4) ? 0x0f : 0xf0; + + BUG_ON(offset > 8); + + mutex_lock(&ts->lock); + + /* GPIO 0:3 and 4:7 can only be controlled as block */ + ts->io_gpio &= ~BIT(offset); + dev_dbg(&ts->spi->dev, "%s: io_gpio = 0x%02X\n", __func__, ts->io_gpio); + if (!(ts->io_control & control) && !(ts->io_gpio & mask)) { + dev_dbg(&ts->spi->dev, "deactivate GPIOs %s\n", + (offset < 4) ? "0-3" : "4-7"); + ts->io_control |= control; + sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control); + } + + mutex_unlock(&ts->lock); +} + +static int sc16is7x2_direction_input(struct gpio_chip *gpio, unsigned offset) +{ + struct sc16is7x2_chip *ts = + container_of(gpio, struct sc16is7x2_chip, gpio); + unsigned io_dir; + + BUG_ON(offset > 8); + + mutex_lock(&ts->lock); + + ts->io_dir &= ~BIT(offset); + io_dir = ts->io_dir; + + mutex_unlock(&ts->lock); + + return sc16is7x2_write_async(ts->spi, REG_IOD, 0, io_dir); +} + +static int sc16is7x2_direction_output(struct gpio_chip *gpio, unsigned offset, + int value) +{ + struct sc16is7x2_chip *ts = + container_of(gpio, struct sc16is7x2_chip, gpio); + struct sc16is7x2_spi_reg *cmds; + + BUG_ON(offset > 8); + + mutex_lock(&ts->lock); + + if (value) + ts->io_state |= BIT(offset); + else + ts->io_state &= ~BIT(offset); + + ts->io_dir |= BIT(offset); + + cmds = sc16is7x2_alloc_spi_cmds(2); + if (cmds) { + sc16is7x2_add_write_cmd(&cmds[0], REG_IOS, 0, ts->io_state); + sc16is7x2_add_write_cmd(&cmds[1], REG_IOD, 0, ts->io_dir); + } + + mutex_unlock(&ts->lock); + + return sc16is7x2_spi_async(ts->spi, cmds, 2); +} + +static int sc16is7x2_get(struct gpio_chip *gpio, unsigned offset) +{ + struct sc16is7x2_chip *ts = + container_of(gpio, struct sc16is7x2_chip, gpio); + int level = -EINVAL; + + BUG_ON(offset > 8); + + mutex_lock(&ts->lock); + + if (ts->io_dir & BIT(offset)) { + /* Output: return cached level */ + level = (ts->io_state >> offset) & 0x01; + } else { + /* Input: read out all pins */ + level = sc16is7x2_read(ts->spi, REG_IOS, 0); + if (level >= 0) { + ts->io_state = level; + level = (ts->io_state >> offset) & 0x01; + } + } + + mutex_unlock(&ts->lock); + + return level; +} + +static void sc16is7x2_set(struct gpio_chip *gpio, unsigned offset, int value) +{ + struct sc16is7x2_chip *ts = + container_of(gpio, struct sc16is7x2_chip, gpio); + unsigned io_state; + + BUG_ON(offset > 8); + + mutex_lock(&ts->lock); + + if (value) + ts->io_state |= BIT(offset); + else + ts->io_state &= ~BIT(offset); + io_state = ts->io_state; + + mutex_unlock(&ts->lock); + + sc16is7x2_write_async(ts->spi, REG_IOS, 0, io_state); +} + +/* ******************************** IRQ ********************************* */ + +static void sc16is7x2_handle_fifo_rx(struct sc16is7x2_channel *chan) +{ + struct uart_port *uart = &chan->uart; + struct tty_struct *tty = uart->state->port.tty; + u8 *rxbuf = chan->rxbuf; + u8 lsr = chan->lsr; + unsigned i, count = chan->fifo_rx.len; + unsigned long flags; + char flag = TTY_NORMAL; + + spin_lock_irqsave(&uart->lock, flags); + + if (unlikely(lsr & UART_LSR_BRK_ERROR_BITS)) { + /* + * For statistics only + */ + if (lsr & UART_LSR_BI) { + lsr &= ~(UART_LSR_FE | UART_LSR_PE); + chan->uart.icount.brk++; + /* + * We do the SysRQ and SAK checking + * here because otherwise the break + * may get masked by ignore_status_mask + * or read_status_mask. + */ + if (uart_handle_break(&chan->uart)) + goto ignore_char; + } else if (lsr & UART_LSR_PE) + chan->uart.icount.parity++; + else if (lsr & UART_LSR_FE) + chan->uart.icount.frame++; + if (lsr & UART_LSR_OE) + chan->uart.icount.overrun++; + + /* + * Mask off conditions which should be ignored. + */ + lsr &= chan->uart.read_status_mask; + + if (lsr & UART_LSR_BI) + flag = TTY_BREAK; + else if (lsr & UART_LSR_PE) + flag = TTY_PARITY; + else if (lsr & UART_LSR_FE) + flag = TTY_FRAME; + } + + for (i = 1; i < count; i++) { + uart->icount.rx++; + + if (!uart_handle_sysrq_char(uart, rxbuf[i])) + uart_insert_char(uart, lsr, UART_LSR_OE, + rxbuf[i], flag); + } + +ignore_char: + spin_unlock_irqrestore(&uart->lock, flags); + + if (count > 1) + tty_flip_buffer_push(tty); +} + +static void sc16is7x2_handle_fifo_tx(struct sc16is7x2_channel *chan) +{ + struct uart_port *uart = &chan->uart; + struct circ_buf *xmit = &uart->state->xmit; + unsigned count = chan->fifo_tx[1].len + chan->fifo_tx[2].len; + unsigned long flags; + + BUG_ON(!uart); + BUG_ON(!xmit); + + spin_lock_irqsave(&uart->lock, flags); + + uart->icount.tx += count; + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(uart); + + if (uart_circ_empty(xmit)) + __stop_tx(chan); + + spin_unlock_irqrestore(&uart->lock, flags); +} + +static bool sc16is7x2_msg_add_fifo_rx(struct sc16is7x2_chip *ts, unsigned ch) +{ + struct spi_message *m = &(ts->fifo_message); + struct spi_transfer *t = &(ts->channel[ch].fifo_rx); + int rxlvl = sc16is7x2_read(ts->spi, REG_RXLVL, ch); + if (rxlvl > 0) { + t->len = rxlvl + 1; + spi_message_add_tail(t, m); + return true; + } + return false; +} + +static bool sc16is7x2_msg_add_fifo_tx(struct sc16is7x2_chip *ts, unsigned ch) +{ + struct sc16is7x2_channel * const chan = &(ts->channel[ch]); + struct uart_port *uart = &chan->uart; + struct circ_buf *xmit = &uart->state->xmit; + unsigned count; + bool split_transfer; + u8 txlvl; + + if (chan->uart.x_char && chan->lsr & UART_LSR_THRE) { + dev_dbg(&ts->spi->dev, "tx: x-char\n"); + sc16is7x2_write(ts->spi, UART_TX, ch, uart->x_char); + uart->icount.tx++; + uart->x_char = 0; + return false; + } + if (uart_tx_stopped(&chan->uart)) { + dev_dbg(&ts->spi->dev, "tx: stopped!\n"); + sc16is7x2_stop_tx(uart); + return false; + } + if (uart_circ_empty(xmit)) { + __stop_tx(chan); + return false; + } + + txlvl = sc16is7x2_read(ts->spi, REG_TXLVL, ch); + if (txlvl <= 0) { + dev_dbg(&ts->spi->dev, " fifo full\n"); + return false; + } + + /* number of bytes to transfer to the fifo */ + count = MIN(txlvl, uart_circ_chars_pending(xmit)); + split_transfer = (UART_XMIT_SIZE - xmit->tail) <= count; + + /* add command transfer */ + spi_message_add_tail(&(chan->fifo_tx[0]), &(ts->fifo_message)); + /* add first fifo transfer */ + spi_message_add_tail(&(chan->fifo_tx[1]), &(ts->fifo_message)); + + chan->fifo_tx[1].tx_buf = xmit->buf + xmit->tail; + + if (!split_transfer) { + chan->fifo_tx[1].len = count; + chan->fifo_tx[1].cs_change = true; + + chan->fifo_tx[2].len = 0; + } else { + chan->fifo_tx[1].len = (UART_XMIT_SIZE - 1) - xmit->tail; + chan->fifo_tx[1].cs_change = false; + + chan->fifo_tx[2].tx_buf = xmit->buf; + chan->fifo_tx[2].cs_change = true; + chan->fifo_tx[2].len = count - chan->fifo_tx[1].len; + /* add second fifo transfer */ + spi_message_add_tail(&(chan->fifo_tx[2]), &(ts->fifo_message)); + } + + xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1); + return true; +} + +static void sc16is7x2_handle_modem(struct sc16is7x2_chip *ts, unsigned ch) +{ + struct sc16is7x2_channel *chan = &(ts->channel[ch]); + struct uart_port *uart = &chan->uart; + + if (chan->msr & UART_MSR_ANY_DELTA + && chan->ier & UART_IER_MSI + && uart->state != NULL) { + if (chan->msr & UART_MSR_TERI) + uart->icount.rng++; + if (chan->msr & UART_MSR_DDSR) + uart->icount.dsr++; + if (chan->msr & UART_MSR_DDCD) + uart_handle_dcd_change(uart, chan->msr & UART_MSR_DCD); + if (chan->msr & UART_MSR_DCTS) + uart_handle_cts_change(uart, chan->msr & UART_MSR_CTS); + + wake_up_interruptible(&uart->state->port.delta_msr_wait); + } +} + +static bool sc16is7x2_handle_channel(struct sc16is7x2_chip *ts, unsigned ch) +{ + struct sc16is7x2_channel *chan = &(ts->channel[ch]); + struct spi_message *m = &(ts->fifo_message); + bool rx, tx; + + dev_dbg(&ts->spi->dev, "%s (%i)\n", __func__, ch); + + chan->iir = sc16is7x2_read(ts->spi, UART_IIR, ch); + chan->msr = sc16is7x2_read(ts->spi, UART_MSR, ch); + chan->lsr = sc16is7x2_read(ts->spi, UART_LSR, ch); + + sc16is7x2_handle_modem(ts, ch); + + spi_message_init(m); + rx = sc16is7x2_msg_add_fifo_rx(ts, ch); + tx = sc16is7x2_msg_add_fifo_tx(ts, ch); + + if (rx || tx) + spi_sync(ts->spi, m); + + if (rx) + sc16is7x2_handle_fifo_rx(chan); + if (tx) + sc16is7x2_handle_fifo_tx(chan); + + dev_dbg(&ts->spi->dev, "%s finished (iir = 0x%02x)\n", + __func__, chan->iir); + + return (chan->iir & UART_IIR_NO_INT) == 0x00; +} + +static void sc16is7x2_work(struct work_struct *w) +{ + struct sc16is7x2_chip *ts = + container_of(w, struct sc16is7x2_chip, work); + unsigned pending = 0; + unsigned ch = 0; + + dev_dbg(&ts->spi->dev, "%s\n", __func__); + BUG_ON(!w); + BUG_ON(!ts); + + + if (ts->force_end_work) { + dev_dbg(&ts->spi->dev, "%s: force end!\n", __func__); + return; + } + + if (ts->channel[0].active) + pending |= BIT(0); + if (ts->channel[1].active) + pending |= BIT(1); + + do { + mutex_lock(&(ts->channel[ch].lock)); + if (pending & BIT(ch) && ts->channel[ch].active) { + if (!sc16is7x2_handle_channel(ts, ch)) + pending &= ~BIT(ch); + } + mutex_unlock(&(ts->channel[ch].lock)); + ch ^= 1; /* switch channel */ + } while (!ts->force_end_work && !freezing(current) && pending); + + dev_dbg(&ts->spi->dev, "%s finished\n", __func__); +} + +static irqreturn_t sc16is7x2_interrupt(int irq, void *dev_id) +{ + struct sc16is7x2_chip *ts = dev_id; + + dev_dbg(&ts->spi->dev, "%s\n", __func__); + + if (!ts->force_end_work && !work_pending(&ts->work) && + !freezing(current) && !ts->suspending) + queue_work(ts->workqueue, &ts->work); + + return IRQ_HANDLED; +} + +/* ******************************** INIT ********************************* */ + +static struct uart_driver sc16is7x2_uart_driver; + +static int sc16is7x2_register_gpio(struct sc16is7x2_chip *ts, + struct sc16is7x2_platform_data *pdata) +{ + struct sc16is7x2_spi_reg *cmds; + + ts->gpio.label = (pdata->label) ? pdata->label : DRIVER_NAME; + ts->gpio.request = sc16is7x2_gpio_request; + ts->gpio.free = sc16is7x2_gpio_free; + ts->gpio.get = sc16is7x2_get; + ts->gpio.set = sc16is7x2_set; + ts->gpio.direction_input = sc16is7x2_direction_input; + ts->gpio.direction_output = sc16is7x2_direction_output; + + ts->gpio.base = pdata->gpio_base; + ts->gpio.names = pdata->names; + ts->gpio.ngpio = SC16IS7X2_NR_GPIOS; + ts->gpio.can_sleep = 1; + ts->gpio.dev = &ts->spi->dev; + ts->gpio.owner = THIS_MODULE; + + /* disable all GPIOs, enable on request */ + ts->io_dir = 0x0f; + ts->io_state = 0; + ts->io_gpio = 0; + ts->io_control = IOC_GPIO30 | IOC_GPIO74; + + cmds = sc16is7x2_alloc_spi_cmds(4); + if (!cmds) + return -ENOMEM; + + sc16is7x2_add_write_cmd(&cmds[0], REG_IOI, 0, 0); + sc16is7x2_add_write_cmd(&cmds[1], REG_IOC, 0, ts->io_control); + sc16is7x2_add_write_cmd(&cmds[2], REG_IOS, 0, ts->io_state); + sc16is7x2_add_write_cmd(&cmds[3], REG_IOD, 0, ts->io_dir); + sc16is7x2_spi_async(ts->spi, cmds, 4); + + return gpiochip_add(&ts->gpio); +} + +static int sc16is7x2_register_uart_port(struct sc16is7x2_chip *ts, + struct sc16is7x2_platform_data *pdata, unsigned ch) +{ + struct sc16is7x2_channel *chan = &(ts->channel[ch]); + struct uart_port *uart = &chan->uart; + + mutex_init(&chan->lock); + chan->active = false; /* will be set in startup */ + chan->chip = ts; + + chan->read_fifo_cmd = READ_CMD(UART_RX, ch); + chan->fifo_rx.tx_buf = &(chan->read_fifo_cmd); + chan->fifo_rx.rx_buf = chan->rxbuf; + chan->fifo_rx.cs_change = true; + + chan->write_fifo_cmd = WRITE_CMD(UART_TX, ch); + chan->fifo_tx[0].tx_buf = &(chan->write_fifo_cmd); + chan->fifo_tx[0].rx_buf = NULL; + chan->fifo_tx[0].len = 1; + chan->fifo_tx[0].cs_change = false; + chan->fifo_tx[1].rx_buf = NULL; + chan->fifo_tx[2].rx_buf = NULL; + + uart->irq = ts->spi->irq; + uart->uartclk = pdata->uartclk; + uart->fifosize = FIFO_SIZE; + uart->ops = &sc16is7x2_uart_ops; + uart->flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF; + uart->line = pdata->uart_base + ch; + uart->type = PORT_SC16IS7X2; + uart->dev = &ts->spi->dev; + + return uart_add_one_port(&sc16is7x2_uart_driver, uart); +} + +static int __devinit sc16is7x2_probe(struct spi_device *spi) +{ + struct sc16is7x2_chip *ts; + struct sc16is7x2_platform_data *pdata; + int ret; + + pdata = spi->dev.platform_data; + if (!pdata || !pdata->gpio_base /* || pdata->uart_base */) { + dev_dbg(&spi->dev, "incorrect or missing platform data\n"); + return -EINVAL; + } + + printk(KERN_INFO DRIVER_NAME " at CS%d (irq %d), 2 UARTs, 8 GPIOs\n" + " eser%d, eser%d, gpiochip%d\n", + spi->chip_select, spi->irq, + pdata->uart_base, pdata->uart_base + 1, + pdata->gpio_base); + + ret = spi_setup(spi); + if (ret < 0) + return ret; + + ts = kzalloc(sizeof(struct sc16is7x2_chip), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + mutex_init(&ts->lock); + dev_set_drvdata(&spi->dev, ts); + ts->spi = spi; + ts->force_end_work = 1; + + /* Reset the chip TODO: and disable IRQ output */ + sc16is7x2_write(spi, REG_IOC, 0, IOC_SRESET); + + ret = request_irq(spi->irq, sc16is7x2_interrupt, + IRQF_TRIGGER_FALLING | IRQF_SHARED, "sc16is7x2", ts); + if (ret) { + dev_warn(&ts->spi->dev, "cannot register interrupt\n"); + goto exit_destroy; + } + + ret = sc16is7x2_register_uart_port(ts, pdata, 0); + if (ret) + goto exit_irq; + ret = sc16is7x2_register_uart_port(ts, pdata, 1); + if (ret) + goto exit_uart0; + + ret = sc16is7x2_register_gpio(ts, pdata); + if (ret) + goto exit_uart1; + + ts->workqueue = create_freezeable_workqueue(DRIVER_NAME); + if (!ts->workqueue) { + dev_warn(&ts->spi->dev, "cannot create workqueue\n"); + ret = -EBUSY; + goto exit_gpio; + } + INIT_WORK(&ts->work, sc16is7x2_work); + ts->force_end_work = 0; + + return ret; + +exit_gpio: + ret = gpiochip_remove(&ts->gpio); + +exit_uart1: + uart_remove_one_port(&sc16is7x2_uart_driver, &ts->channel[1].uart); + +exit_uart0: + uart_remove_one_port(&sc16is7x2_uart_driver, &ts->channel[0].uart); + +exit_irq: + free_irq(spi->irq, ts); + +exit_destroy: + dev_set_drvdata(&spi->dev, NULL); + mutex_destroy(&ts->lock); + kfree(ts); + return ret; +} + +static int __devexit sc16is7x2_remove(struct spi_device *spi) +{ + struct sc16is7x2_chip *ts; + int ret; + + ts = dev_get_drvdata(&spi->dev); + if (ts == NULL) + return -ENODEV; + + free_irq(spi->irq, ts); + ts->force_end_work = 1; + + if (ts->workqueue) { + flush_workqueue(ts->workqueue); + destroy_workqueue(ts->workqueue); + ts->workqueue = NULL; + } + + dev_set_drvdata(&spi->dev, NULL); + + ret = uart_remove_one_port(&sc16is7x2_uart_driver, + &ts->channel[0].uart); + if (ret) { + dev_err(&spi->dev, "Failed to remove the UART port A: %d\n", + ret); + goto exit_error; + } + ret = uart_remove_one_port(&sc16is7x2_uart_driver, + &ts->channel[1].uart); + if (ret) { + dev_err(&spi->dev, "Failed to remove the UART port B: %d\n", + ret); + goto exit_error; + } + ret = gpiochip_remove(&ts->gpio); + if (ret) { + dev_err(&spi->dev, "Failed to remove the GPIO controller: %d\n", + ret); + goto exit_error; + } + + mutex_destroy(&ts->lock); + kfree(ts); + +exit_error: + return ret; +} + +static struct uart_driver sc16is7x2_uart_driver = { + .owner = THIS_MODULE, + .driver_name = DRIVER_NAME, + .dev_name = "eser", + .major = SC16IS7X2_MAJOR, + .minor = SC16IS7X2_MINOR, + .nr = MAX_SC16IS7X2, +}; + +static struct spi_driver sc16is7x2_spi_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = sc16is7x2_probe, + .remove = __devexit_p(sc16is7x2_remove), +}; + +static int __init sc16is7x2_init(void) +{ + int ret; + ret = uart_register_driver(&sc16is7x2_uart_driver); + if (ret) { + printk(KERN_ERR "Couldn't register sc16is7x2 uart driver\n"); + return ret; + } + + return spi_register_driver(&sc16is7x2_spi_driver); +} +/* register after spi postcore initcall and before + * subsys initcalls that may rely on these GPIOs + */ +subsys_initcall(sc16is7x2_init); + +static void __exit sc16is7x2_exit(void) +{ + uart_unregister_driver(&sc16is7x2_uart_driver); + spi_unregister_driver(&sc16is7x2_spi_driver); +} +module_exit(sc16is7x2_exit); + +MODULE_AUTHOR("Manuel Stahl"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SC16IS7x2 SPI based UART chip"); +MODULE_ALIAS("spi:" DRIVER_NAME); diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 64458a9..1869dcf 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -46,6 +46,9 @@ #define PORT_AR7 18 /* Texas Instruments AR7 internal UART */ #define PORT_MAX_8250 18 /* max port ID */ +/* SC16IS7x2 SPI UART */ +#define PORT_SC16IS7X2 19 + /* * ARM specific type numbers. These are not currently guaranteed * to be implemented, and will change in the future. These are diff --git a/include/linux/spi/sc16is7x2.h b/include/linux/spi/sc16is7x2.h new file mode 100755 index 0000000..931fe50 --- /dev/null +++ b/include/linux/spi/sc16is7x2.h @@ -0,0 +1,17 @@ +#ifndef LINUX_SPI_SC16IS752_H +#define LINUX_SPI_SC16IS752_H + +#define SC16IS7X2_NR_GPIOS 8 + +struct sc16is7x2_platform_data { + unsigned int uartclk; + /* uart line number of the first channel */ + unsigned uart_base; + /* number assigned to the first GPIO */ + unsigned gpio_base; + char *label; + /* list of GPIO names (array length = SC16IS7X2_NR_GPIOS) */ + const char *const *names; +}; + +#endif ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] Add sc16is7x2 driver 2010-09-03 13:11 [PATCH] Add sc16is7x2 driver Manuel Stahl @ 2010-09-14 0:25 ` Andrew Morton 2010-09-14 13:07 ` Manuel Stahl 0 siblings, 1 reply; 8+ messages in thread From: Andrew Morton @ 2010-09-14 0:25 UTC (permalink / raw) To: Manuel Stahl; +Cc: linux-kernel, Greg KH On Fri, 03 Sep 2010 15:11:53 +0200 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> wrote: > This patch adds support for the sc16is7x2 chips. > The patch was pretty badly wordwrapped. > > ... > > +#define WRITE_CMD(reg, ch) (REG_WRITE | (reg & 0xf) << 3 | (ch & 0x1) << 1) > +#define READ_CMD(reg, ch) (REG_READ | (reg & 0xf) << 3 | (ch & 0x1) << 1) It would be better to implement these as lower-case-named inlined C functions. > + > +/* 16bit SPI command to read or write a register */ > +struct sc16is7x2_spi_reg { > + u8 cmd; > + u8 value; > +} __attribute__ ((packed)); We have a __packed helper macro for this. > +struct sc16is7x2_chip; > + > +/* > + * Some registers must be read back to modify. > + * To save time we cache them here in memory That implies that the caches have some locking (the mutex, perhaps?). Documenting that here would be appropriate. > > ... > > +/* ******************************** SPI > ********************************* */ > + > + > +/* > + * Reserve memory for command sequence > + * @param cnt number of commands This "@param" stuff is some form of markup which the kernel does not use. Please use standard kerneldoc markup throughout the driver, or just plain old English-language comments. > + */ > +static inline struct sc16is7x2_spi_reg * > +sc16is7x2_alloc_spi_cmds(unsigned cnt) > +{ > + return kzalloc(sizeof(struct sc16is7x2_spi_reg)*cnt, GFP_KERNEL); Use kcalloc() here. > +} > + > > ... > > +/* > + * sc16is7x2_write_async - Write a new register content (async) > + */ > +static inline int sc16is7x2_write_async(struct spi_device *spi, u8 reg, > u8 ch, > + u8 value) > +{ > + struct sc16is7x2_spi_reg *cmd = sc16is7x2_alloc_spi_cmds(1); > + if (!cmd) > + return -ENOMEM; > + sc16is7x2_add_write_cmd(cmd, reg, ch, value); > + return sc16is7x2_spi_async(spi, cmd, 1); > +} It's comventional to place a blank line between end-of-definitions and start-of-code. The driver has many instances of this. > +/* > + * sc16is7x2_write - Write a new register content (sync) > + */ > +static int sc16is7x2_write(struct spi_device *spi, u8 reg, u8 ch, u8 val) > +{ > + u16 word = REG_WRITE | (reg & 0xf) << 3 | (ch & 0x3) << 1 | val << 8; > + return spi_write(spi, (const u8 *)&word, sizeof(word)); > +} There's another. > +/** > + * sc16is7x2_read - Read back register content > + * @spi: The SPI device > + * @reg: Register offset > + * > + * Returns positive 8 bit value from device if successful or a > + * negative value on error > + */ ah-hah. That's a correct kerneldoc comment. > +static int sc16is7x2_read(struct spi_device *spi, unsigned reg, > unsigned ch) > +{ > + u8 cmd = REG_READ | (reg & 0xf) << 3 | (ch & 0x3) << 1; > + return spi_w8r8(spi, cmd); > +} > + > +/* ******************************** UART > ********************************* */ > + > +/* Uart divisor latch write */ > +static inline void sc16is7x2_add_dl_write_cmd(struct sc16is7x2_spi_reg > *cmd, > + u8 ch, int value) > +{ > + sc16is7x2_add_write_cmd(&cmd[0], UART_DLL, ch, value & 0xff); > + sc16is7x2_add_write_cmd(&cmd[1], UART_DLM, ch, value >> 8 & 0xff); > +} Probably the driver doesn't need any explicit "inline" usage at all - modern gcc's work all that out for themselves. And if gcc disagreed with your inline directive, it will just ignore it. > +static unsigned int sc16is7x2_tx_empty(struct uart_port *port) > +{ > + struct sc16is7x2_channel *chan = > + container_of(port, struct sc16is7x2_channel, uart); > + struct sc16is7x2_chip *ts = chan->chip; > + unsigned lsr; > + > + dev_dbg(&ts->spi->dev, "%s\n", __func__); > + > + mutex_lock(&chan->lock); > + lsr = chan->lsr; > + mutex_unlock(&chan->lock); It's strange to put locking around a single atomic read. What are we trying to do here? > + return lsr & UART_LSR_TEMT ? TIOCSER_TEMT : 0; > +} > + > > ... > > +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); hm. Above we use the mutex to protect char->lcr but here we're using a spinlock. > + sc16is7x2_write_async(ts->spi, UART_LCR, ch, chan->lcr); > +} > + > > ... > > +#define MIN(a, b) ((a < b) ? (a) : (b)) Nope. Use the min() from include/linux/kernel.h. > > ... > > +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; > +} The kernel has infrastructure for "threaded irqs" nowadays. What this driver is doing basically reimplements that concept. Did you consider using threaded IRQs directly? > > ... > ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] Add sc16is7x2 driver 2010-09-14 0:25 ` Andrew Morton @ 2010-09-14 13:07 ` Manuel Stahl 2010-09-14 13:27 ` Jonathan Corbet 2010-09-21 20:42 ` Greg KH 0 siblings, 2 replies; 8+ messages in thread From: Manuel Stahl @ 2010-09-14 13:07 UTC (permalink / raw) To: Andrew Morton; +Cc: linux-kernel, Greg KH, linux-serial [-- Attachment #1: Type: text/plain, Size: 2762 bytes --] Hi Andrew, thank you for reviewing this driver. A cleaned up patch is attached. Am 14.09.2010 02:25, schrieb Andrew Morton: > On Fri, 03 Sep 2010 15:11:53 +0200 > Manuel Stahl<manuel.stahl@iis.fraunhofer.de> wrote: > >> This patch adds support for the sc16is7x2 chips. >> > > The patch was pretty badly wordwrapped. Can you point out a few such places? >> +static unsigned int sc16is7x2_tx_empty(struct uart_port *port) >> +{ >> + struct sc16is7x2_channel *chan = >> + container_of(port, struct sc16is7x2_channel, uart); >> + struct sc16is7x2_chip *ts = chan->chip; >> + unsigned lsr; >> + >> + dev_dbg(&ts->spi->dev, "%s\n", __func__); >> + >> + mutex_lock(&chan->lock); >> + lsr = chan->lsr; >> + mutex_unlock(&chan->lock); > > It's strange to put locking around a single atomic read. What are we > trying to do here? > >> + return lsr& UART_LSR_TEMT ? TIOCSER_TEMT : 0; >> +} >> + >> >> ... >> >> +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); > > hm. Above we use the mutex to protect char->lcr but here we're using a > spinlock. I think in the break_ctl function it's not possible to use mutexes. What would be the right way to protect the register cache? >> >> ... >> >> +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; >> +} > > The kernel has infrastructure for "threaded irqs" nowadays. What this > driver is doing basically reimplements that concept. Did you consider > using threaded IRQs directly? Ah, I didn't know that. The irq stuff is based on the max3100 driver. Where can I find some example how to use the new threaded irqs? Regards, -- Dipl.-Inf. Manuel Stahl Fraunhofer-Institut für Integrierte Schaltungen IIS - Leistungsoptimierte Systeme - Nordostpark 93 Telefon +49 (0)911/58061-6419 90411 Nürnberg Fax +49 (0)911/58061-6398 http://www.iis.fraunhofer.de manuel.stahl@iis.fraunhofer.de [-- Attachment #2: add_sc16is7x2_driver.patch --] [-- Type: text/plain, Size: 39616 bytes --] This patch adds support for the sc16is7x2 chips. Signed-off-by: Manuel Stahl <manuel.stahl@iis.fraunhofer.de> --- 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 + 5 files changed, 1405 insertions(+), 0 deletions(-) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 12900f7..98fc0dd 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -269,6 +269,15 @@ config SERIAL_8250_RM9K comment "Non-8250 serial port support" +config SERIAL_SC16IS7X2 + tristate "SC16IS7x2 chips" + depends on SPI_MASTER && 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 --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 1ca4fd5..4c6a297 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_SERIAL_8250_BOCA) += 8250_boca.o obj-$(CONFIG_SERIAL_8250_EXAR_ST16C554) += 8250_exar_st16c554.o obj-$(CONFIG_SERIAL_8250_HUB6) += 8250_hub6.o obj-$(CONFIG_SERIAL_8250_MCA) += 8250_mca.o +obj-$(CONFIG_SERIAL_SC16IS7X2) += sc16is7x2.o obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o diff --git a/drivers/serial/sc16is7x2.c b/drivers/serial/sc16is7x2.c new file mode 100644 index 0000000..a9bcd03 --- /dev/null +++ b/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 +#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; + +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; +} + +/* + * Reserve memory for command sequence + * @cnt number of commands + */ +static struct sc16is7x2_spi_reg * +sc16is7x2_alloc_spi_cmds(unsigned cnt) +{ + 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); + 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); + 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 */) { + 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); + + 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"); + return ret; + } + + return spi_register_driver(&sc16is7x2_spi_driver); +} +/* register after spi postcore initcall and before + * subsys initcalls that may rely on these GPIOs + */ +subsys_initcall(sc16is7x2_init); + +static void __exit sc16is7x2_exit(void) +{ + uart_unregister_driver(&sc16is7x2_uart_driver); + spi_unregister_driver(&sc16is7x2_spi_driver); +} +module_exit(sc16is7x2_exit); + +MODULE_AUTHOR("Manuel Stahl"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SC16IS7x2 SPI based UART chip"); +MODULE_ALIAS("spi:" DRIVER_NAME); diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 563e234..7c1da31 100644 --- a/include/linux/serial_core.h +++ b/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 + /* * ARM specific type numbers. These are not currently guaranteed * to be implemented, and will change in the future. These are diff --git a/include/linux/spi/sc16is7x2.h b/include/linux/spi/sc16is7x2.h new file mode 100755 index 0000000..931fe50 --- /dev/null +++ b/include/linux/spi/sc16is7x2.h @@ -0,0 +1,17 @@ +#ifndef LINUX_SPI_SC16IS752_H +#define LINUX_SPI_SC16IS752_H + +#define SC16IS7X2_NR_GPIOS 8 + +struct sc16is7x2_platform_data { + unsigned int uartclk; + /* uart line number of the first channel */ + unsigned uart_base; + /* number assigned to the first GPIO */ + unsigned gpio_base; + char *label; + /* list of GPIO names (array length = SC16IS7X2_NR_GPIOS) */ + const char *const *names; +}; + +#endif ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] Add sc16is7x2 driver 2010-09-14 13:07 ` Manuel Stahl @ 2010-09-14 13:27 ` Jonathan Corbet 2010-09-15 13:42 ` Manuel Stahl 2010-09-21 20:42 ` Greg KH 1 sibling, 1 reply; 8+ messages in thread From: Jonathan Corbet @ 2010-09-14 13:27 UTC (permalink / raw) To: Manuel Stahl; +Cc: Andrew Morton, linux-kernel, Greg KH, linux-serial On Tue, 14 Sep 2010 15:07:13 +0200 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> wrote: > Where can I find some example how to use the new threaded irqs? Basic description here: http://lwn.net/Articles/302043/ If you grep request_threaded_irq() in drivers/, you'll certainly find some real-world examples. jon ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] Add sc16is7x2 driver 2010-09-14 13:27 ` Jonathan Corbet @ 2010-09-15 13:42 ` Manuel Stahl 2010-09-15 19:17 ` Andrew Morton 0 siblings, 1 reply; 8+ messages in thread From: Manuel Stahl @ 2010-09-15 13:42 UTC (permalink / raw) To: Jonathan Corbet; +Cc: Andrew Morton, linux-kernel, Greg KH, linux-serial [-- Attachment #1: Type: text/plain, Size: 525 bytes --] OK, here is an implementation with threaded irqs. Still not sure about the locking stuff. Maybe someone experienced with uart drivers can help? Am 14.09.2010 15:27, schrieb Jonathan Corbet: > On Tue, 14 Sep 2010 15:07:13 +0200 > Manuel Stahl<manuel.stahl@iis.fraunhofer.de> wrote: > >> Where can I find some example how to use the new threaded irqs? > > Basic description here: http://lwn.net/Articles/302043/ > > If you grep request_threaded_irq() in drivers/, you'll certainly find > some real-world examples. > > jon [-- Attachment #2: sc16is7x2-use-threaded-irqs.patch --] [-- Type: text/plain, Size: 9360 bytes --] sc16is7x2: use threaded irqs Signed-off-by: Manuel Stahl <manuel.stahl@iis.fraunhofer.de> --- drivers/serial/sc16is7x2.c | 115 +++++++++++++++---------------------------- 1 files changed, 40 insertions(+), 75 deletions(-) diff --git a/drivers/serial/sc16is7x2.c b/drivers/serial/sc16is7x2.c index a9bcd03..04cb1ad 100644 --- a/drivers/serial/sc16is7x2.c +++ b/drivers/serial/sc16is7x2.c @@ -92,28 +92,13 @@ struct sc16is7x2_channel { 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; - + /* set to true to make the work thread exit as soon as possible */ + bool force_end_work; 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; + struct mutex io_lock; /* lock for GPIO functions */ u8 io_dir; /* cache for IODir register */ u8 io_state; /* cache for IOState register */ u8 io_gpio; /* PIN is GPIO */ @@ -451,7 +436,7 @@ static void sc16is7x2_shutdown(struct uart_port *port) BUG_ON(!chan); BUG_ON(!ts); - if (ts->suspending) + if (ts->force_end_work) return; /* Disable interrupts from this port */ @@ -639,7 +624,7 @@ static void sc16is7x2_release_port(struct uart_port *port) struct sc16is7x2_chip *ts = chan->chip; dev_dbg(&ts->spi->dev, "%s\n", __func__); - ts->force_end_work = 1; + ts->force_end_work = true; } static int sc16is7x2_request_port(struct uart_port *port) @@ -709,7 +694,7 @@ static int sc16is7x2_gpio_request(struct gpio_chip *gpio, unsigned offset) BUG_ON(offset > 8); dev_dbg(&ts->spi->dev, "%s: offset = %d\n", __func__, offset); - mutex_lock(&ts->lock); + mutex_lock(&ts->io_lock); /* GPIO 0:3 and 4:7 can only be controlled as block */ ts->io_gpio |= BIT(offset); @@ -720,7 +705,7 @@ static int sc16is7x2_gpio_request(struct gpio_chip *gpio, unsigned offset) ret = sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control); } - mutex_unlock(&ts->lock); + mutex_unlock(&ts->io_lock); return ret; } @@ -734,7 +719,7 @@ static void sc16is7x2_gpio_free(struct gpio_chip *gpio, unsigned offset) BUG_ON(offset > 8); - mutex_lock(&ts->lock); + mutex_lock(&ts->io_lock); /* GPIO 0:3 and 4:7 can only be controlled as block */ ts->io_gpio &= ~BIT(offset); @@ -746,7 +731,7 @@ static void sc16is7x2_gpio_free(struct gpio_chip *gpio, unsigned offset) sc16is7x2_write(ts->spi, REG_IOC, 0, ts->io_control); } - mutex_unlock(&ts->lock); + mutex_unlock(&ts->io_lock); } static int sc16is7x2_direction_input(struct gpio_chip *gpio, unsigned offset) @@ -757,12 +742,12 @@ static int sc16is7x2_direction_input(struct gpio_chip *gpio, unsigned offset) BUG_ON(offset > 8); - mutex_lock(&ts->lock); + mutex_lock(&ts->io_lock); ts->io_dir &= ~BIT(offset); io_dir = ts->io_dir; - mutex_unlock(&ts->lock); + mutex_unlock(&ts->io_lock); return sc16is7x2_write_async(ts->spi, REG_IOD, 0, io_dir); } @@ -776,7 +761,7 @@ static int sc16is7x2_direction_output(struct gpio_chip *gpio, unsigned offset, BUG_ON(offset > 8); - mutex_lock(&ts->lock); + mutex_lock(&ts->io_lock); if (value) ts->io_state |= BIT(offset); @@ -791,7 +776,7 @@ static int sc16is7x2_direction_output(struct gpio_chip *gpio, unsigned offset, sc16is7x2_add_write_cmd(&cmds[1], REG_IOD, 0, ts->io_dir); } - mutex_unlock(&ts->lock); + mutex_unlock(&ts->io_lock); return sc16is7x2_spi_async(ts->spi, cmds, 2); } @@ -804,7 +789,7 @@ static int sc16is7x2_get(struct gpio_chip *gpio, unsigned offset) BUG_ON(offset > 8); - mutex_lock(&ts->lock); + mutex_lock(&ts->io_lock); if (ts->io_dir & BIT(offset)) { /* Output: return cached level */ @@ -818,7 +803,7 @@ static int sc16is7x2_get(struct gpio_chip *gpio, unsigned offset) } } - mutex_unlock(&ts->lock); + mutex_unlock(&ts->io_lock); return level; } @@ -831,7 +816,7 @@ static void sc16is7x2_set(struct gpio_chip *gpio, unsigned offset, int value) BUG_ON(offset > 8); - mutex_lock(&ts->lock); + mutex_lock(&ts->io_lock); if (value) ts->io_state |= BIT(offset); @@ -839,7 +824,7 @@ static void sc16is7x2_set(struct gpio_chip *gpio, unsigned offset, int value) ts->io_state &= ~BIT(offset); io_state = ts->io_state; - mutex_unlock(&ts->lock); + mutex_unlock(&ts->io_lock); sc16is7x2_write_async(ts->spi, REG_IOS, 0, io_state); } @@ -971,7 +956,7 @@ static bool sc16is7x2_msg_add_fifo_tx(struct sc16is7x2_chip *ts, unsigned ch) } txlvl = sc16is7x2_read(ts->spi, REG_TXLVL, ch); - if (txlvl <= 0) { + if (txlvl == 0) { dev_dbg(&ts->spi->dev, " fifo full\n"); return false; } @@ -1066,21 +1051,18 @@ static bool sc16is7x2_handle_channel(struct sc16is7x2_chip *ts, unsigned ch) return (chan->iir & UART_IIR_NO_INT) == 0x00; } -static void sc16is7x2_work(struct work_struct *w) +static irqreturn_t sc16is7x2_work(int irq, void *data) { - struct sc16is7x2_chip *ts = - container_of(w, struct sc16is7x2_chip, work); + struct sc16is7x2_chip *ts = (struct sc16is7x2_chip *)data; 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; + return IRQ_NONE; } if (ts->channel[0].active) @@ -1089,29 +1071,28 @@ static void sc16is7x2_work(struct work_struct *w) 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); + } while (!ts->force_end_work && pending); dev_dbg(&ts->spi->dev, "%s finished\n", __func__); + + return IRQ_HANDLED; } -static irqreturn_t sc16is7x2_interrupt(int irq, void *dev_id) +static irqreturn_t sc16is7x2_irq(int irq, void *data) { - struct sc16is7x2_chip *ts = dev_id; - - dev_dbg(&ts->spi->dev, "%s\n", __func__); + struct sc16is7x2_chip *ts = (struct sc16is7x2_channel *)data; - if (!ts->force_end_work && !work_pending(&ts->work) && - !freezing(current) && !ts->suspending) - queue_work(ts->workqueue, &ts->work); - - return IRQ_HANDLED; + /* It takes too long to read the regs over SPI, + * so just wake up the thread */ + if (ts->channel[0].active || ts->channel[1].active) + return IRQ_WAKE_THREAD; + else + return IRQ_NONE; } /* ******************************** INIT ********************************* */ @@ -1229,16 +1210,16 @@ static int __devinit sc16is7x2_probe(struct spi_device *spi) if (!ts) return -ENOMEM; - mutex_init(&ts->lock); + mutex_init(&ts->io_lock); spi_set_drvdata(spi, ts); ts->spi = spi; - ts->force_end_work = 1; + ts->force_end_work = true; /* 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); + ret = request_threaded_irq(spi->irq, sc16is7x2_irq, sc16is7x2_work, + IRQF_TRIGGER_FALLING | IRQF_SHARED, DRIVER_NAME, ts); if (ret) { dev_warn(&ts->spi->dev, "cannot register interrupt\n"); goto exit_destroy; @@ -1255,14 +1236,7 @@ static int __devinit sc16is7x2_probe(struct spi_device *spi) 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; + ts->force_end_work = false; printk(KERN_INFO DRIVER_NAME " at CS%d (irq %d), 2 UARTs, 8 GPIOs\n" " eser%d, eser%d, gpiochip%d\n", @@ -1272,9 +1246,6 @@ static int __devinit sc16is7x2_probe(struct spi_device *spi) return ret; -exit_gpio: - ret = gpiochip_remove(&ts->gpio); - exit_uart1: sc16is7x2_unregister_uart_port(ts, 1); @@ -1286,7 +1257,7 @@ exit_irq: exit_destroy: dev_set_drvdata(&spi->dev, NULL); - mutex_destroy(&ts->lock); + mutex_destroy(&ts->io_lock); kfree(ts); return ret; } @@ -1299,14 +1270,8 @@ static int __devexit sc16is7x2_remove(struct spi_device *spi) if (ts == NULL) return -ENODEV; + ts->force_end_work = true; 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) @@ -1321,7 +1286,7 @@ static int __devexit sc16is7x2_remove(struct spi_device *spi) goto exit_error; } - mutex_destroy(&ts->lock); + mutex_destroy(&ts->io_lock); kfree(ts); exit_error: -- 1.7.2.3 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] Add sc16is7x2 driver 2010-09-15 13:42 ` Manuel Stahl @ 2010-09-15 19:17 ` Andrew Morton 0 siblings, 0 replies; 8+ messages in thread From: Andrew Morton @ 2010-09-15 19:17 UTC (permalink / raw) To: Manuel Stahl; +Cc: Jonathan Corbet, linux-kernel, Greg KH, linux-serial On Wed, 15 Sep 2010 15:42:14 +0200 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> wrote: > OK, here is an implementation with threaded irqs. That simplified things nicely. Does it still work? > Still not sure about > the locking stuff. Maybe someone experienced with uart drivers can help? The question is too general for anyone to answer, really. Please describe the specific issues in resonable detail? ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] Add sc16is7x2 driver 2010-09-14 13:07 ` Manuel Stahl 2010-09-14 13:27 ` Jonathan Corbet @ 2010-09-21 20:42 ` Greg KH 2010-09-22 8:01 ` Manuel Stahl 1 sibling, 1 reply; 8+ messages in thread From: Greg KH @ 2010-09-21 20:42 UTC (permalink / raw) To: Manuel Stahl; +Cc: Andrew Morton, linux-kernel, linux-serial On Tue, Sep 14, 2010 at 03:07:13PM +0200, Manuel Stahl wrote: > --- a/include/linux/serial_core.h > +++ b/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 Hm, didn't you just duplicate the number here? Shouldn't that be "20" and you bump the max up? In the end, we need to just drop those, I don't think we need defines anymore, but I need to look at it some more to find out. thanks, greg k-h ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] Add sc16is7x2 driver 2010-09-21 20:42 ` Greg KH @ 2010-09-22 8:01 ` Manuel Stahl 0 siblings, 0 replies; 8+ messages in thread From: Manuel Stahl @ 2010-09-22 8:01 UTC (permalink / raw) To: Greg KH; +Cc: Andrew Morton, linux-kernel, linux-serial Am 21.09.2010 22:42, schrieb Greg KH: > On Tue, Sep 14, 2010 at 03:07:13PM +0200, Manuel Stahl wrote: >> --- a/include/linux/serial_core.h >> +++ b/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 > > Hm, didn't you just duplicate the number here? Shouldn't that be "20" > and you bump the max up? Oh, that might be true. > In the end, we need to just drop those, I don't think we need defines > anymore, but I need to look at it some more to find out. Thank you for doing that, Greg. Regards, -- Dipl.-Inf. Manuel Stahl Fraunhofer-Institut für Integrierte Schaltungen IIS - Leistungsoptimierte Systeme - Nordostpark 93 Telefon +49 (0)911/58061-6419 90411 Nürnberg Fax +49 (0)911/58061-6398 http://www.iis.fraunhofer.de manuel.stahl@iis.fraunhofer.de ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2010-09-22 8:01 UTC | newest] Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2010-09-03 13:11 [PATCH] Add sc16is7x2 driver Manuel Stahl 2010-09-14 0:25 ` Andrew Morton 2010-09-14 13:07 ` Manuel Stahl 2010-09-14 13:27 ` Jonathan Corbet 2010-09-15 13:42 ` Manuel Stahl 2010-09-15 19:17 ` Andrew Morton 2010-09-21 20:42 ` Greg KH 2010-09-22 8:01 ` Manuel Stahl
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
Powered by JetHome