[PATCH v2] RFC: WIP: sc16is7xx.c

From: jon
Date: Fri Mar 07 2014 - 07:25:48 EST


From: Jon Ringle <jringle@xxxxxxxxxxxxx>

On Thu, 6 Mar 2014, Joe Perches wrote:
> On Thu, 2014-03-06 at 21:52 -0800, Greg KH wrote:
> > On Thu, Mar 06, 2014 at 09:35:46PM -0500, jon@xxxxxxxxxx wrote:
> > > From: Jon Ringle <jringle@xxxxxxxxxxxxx>
> > >
> > > I am requesting comments on this serial driver.
> > > I am currently having some latency issues with it where I experience
> > > packet loss at speed of 19200.
> > >
> > > I welcome any and all comments.
> >
> > The basic coding style problems make it hard to read to be able to help
> > review it, sorry.
> >
> > Yes, brains have patterns, you want to follow the same patterns as
> > others, it matters.
>
> Here's a patternizing patch on top of this...
>
> It's an extended version of what I sent Jon privately.
>
> Mostly whitespace but some other neatening too.
> Brace removals, tabifying, 80 column comments,
> spelling typos, pr_<level>, c90 comments, etc.
>
> I don't care that's it does a lot of things in
> a single patch because this hasn't ever been
> submitted before and I hope Jon rolls it into
> his next submission.

Thanks Joe!

Here is the patch with Joe's cleanup.

Jon

Signed-off-by: Jon Ringle <jringle@xxxxxxxxxxxxx>
---
drivers/tty/serial/Kconfig | 7 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/sc16is7xx.c | 871 +++++++++++++++++++++++++++++++++++++++
include/uapi/linux/serial_core.h | 3 +
4 files changed, 882 insertions(+)
create mode 100644 drivers/tty/serial/sc16is7xx.c

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index febd45c..1dfaeec 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1179,6 +1179,13 @@ config SERIAL_SCCNXP_CONSOLE
help
Support for console on SCCNXP serial ports.

+config SERIAL_SC16IS7XX
+ tristate "SC16IS7xx RS485 serial support"
+ select SERIAL_CORE
+ default n
+ help
+ This selects support for SC16IS7xx for use as a RS485 serial port
+
config SERIAL_BFIN_SPORT
tristate "Blackfin SPORT emulate UART"
depends on BLACKFIN
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 3068c77..c3bac45 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_SERIAL_SB1250_DUART) += sb1250-duart.o
obj-$(CONFIG_ETRAX_SERIAL) += crisv10.o
obj-$(CONFIG_SERIAL_SC26XX) += sc26xx.o
obj-$(CONFIG_SERIAL_SCCNXP) += sccnxp.o
+obj-$(CONFIG_SERIAL_SC16IS7XX) += sc16is7xx.o
obj-$(CONFIG_SERIAL_JSM) += jsm/
obj-$(CONFIG_SERIAL_TXX9) += serial_txx9.o
obj-$(CONFIG_SERIAL_VR41XX) += vr41xx_siu.o
diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c
new file mode 100644
index 0000000..045241e
--- /dev/null
+++ b/drivers/tty/serial/sc16is7xx.c
@@ -0,0 +1,871 @@
+/*
+ * SC16IS740/750/760 tty serial driver - Copyright (C) 2014 GridPoint
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The SC16IS740/750/760 is a slave I2C-bus/SPI interface to a single-channel
+ * high performance UART. The SC16IS740/750/760âs internal register set is
+ * backward-compatible with the widely used and widely popular 16C450.
+ *
+ * The SC16IS740/750/760 also provides additional advanced features such as
+ * auto hardware and software flow control, automatic RS-485 support, and
+ * software reset.
+ *
+ * Notes:
+ *
+ * The sc16is740 driver is used for the GPEC RS485 Half duplex communication.
+ *
+ * In the EC1K board the sc16is740 RTS line is connected to a SN65HVD1780DR
+ * chip which is used to signal the RS485 direction.
+ * When RTS is low, the RS485 direction is set to output from the CPU.
+ *
+ * To set the RS485 direction we use the sc16is740 internal RS485 feature
+ * where the chip drives the RTS line when the data is written to the TX FIFO
+ * (see the spec note for the EFCR[4] bit configuration).
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/platform_data/gpio-davinci.h>
+#include <linux/i2c.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/sched/rt.h>
+#include <linux/serial.h>
+#include <linux/serial_core.h>
+#include <linux/serial_reg.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/wait.h>
+
+#define DRV_VERSION "0.3"
+#define DRV_NAME "sc16is7xx"
+#define DEV_NAME "ttySC"
+
+#define SC16IS7XX_MAJOR 204 /* Take place of the /dev/ttySC0
+ * SCI serial port 0
+ */
+#define SC16IS7XX_MINOR 8
+
+/*
+ * Software Definitions
+ */
+/* Commands sent from the uart callbacks to the work handler */
+#define SC16IS7XX_CMND_STOP_RX (0) /* Disable the RX interrupt */
+#define SC16IS7XX_CMND_START_TX (1) /* Enable the TX holding register
+ * empty interrupt
+ */
+#define SC16IS7XX_CMND_STOP_TX (2) /* Disable the TX holding register
+ * empty interrupt
+ */
+#define SC16IS7XX_CMND_NEW_TERMIOS (3) /* Apply termios configuration */
+#define SC16IS7XX_CMND_BREAK_CTRL (4)
+#define SC16IS7XX_CMND_TX_RX (5)
+
+#define SC16IS7XX_CLEAR_FIFO_ON_TX /* If defined controller will clear
+ * tx fifo before it transmits chars
+ */
+
+/*
+ * SC16IS7XX Hardware Definitions
+ */
+#define DA850_RS485_INT_PIN GPIO_TO_PIN(0, 4)
+
+#define SC16IS7XX_CLK 7372800 /* crystal clock connected
+ * to the SC16IS7XX chip
+ */
+#define SC16IS7XX_DEFAULT_BAUDRATE 19200
+
+/* General registers set */
+#define SC16IS7XX_TCR 0x06
+#define SC16IS7XX_TLR 0x07
+#define SC16IS7XX_TXLVL 0x08
+#define SC16IS7XX_RXLVL 0x09
+#define SC16IS7XX_EFCR 0x0F
+
+/* LCR / MCR configurations */
+#define UART_LCR_8N1 UART_LCR_WLEN8
+
+#define SC16IS7XX_LCRVAL UART_LCR_8N1 /* 8 data, 1 stop, no parity */
+#define SC16IS7XX_MCRVAL (UART_MCR_DTR | UART_MCR_RTS)
+ /* clock divisor = 1,
+ * UART mode,
+ * loopback disabled,
+ * RTS/DTR are set,
+ * TCR/TLR disabled
+ */
+
+/* SC16IS7XX Internal register address translation */
+#define REG_TO_I2C_ADDR(reg) (((reg) & 0x0f) << 3)
+
+#define SC16IS7XX_FIFO_SIZE 64 /* Rx fifo size */
+#define SC16IS7XX_LOAD_SIZE 64 /* Tx fifo size */
+
+struct uart_sc16is7xx_port {
+ struct uart_port port;
+
+ /* private to the driver */
+ struct i2c_client *client; /* I2C client handle */
+
+ int tx_empty; /* last TX empty bit */
+
+ unsigned long command; /* Command to the work executed
+ * from the workqueue
+ */
+
+ struct ktermios *termios_new;
+ struct ktermios *termios_old;
+
+ int break_state;
+
+ char ier; /* cache Interrupt Enable Register to
+ * manage the interrupt from the work
+ */
+ char lcr;
+ char fcr; /* cache the FIFO control register to
+ * hold write only values of that register
+ */
+
+ spinlock_t lock;
+
+ /* set to 1 to make the workhandler exit as soon as possible */
+ int force_end_work;
+};
+
+static inline unsigned char sc16is7xx_read_reg(struct uart_sc16is7xx_port *up,
+ unsigned char reg)
+{
+ int rc;
+ u8 val = 0;
+ u8 sc_reg = REG_TO_I2C_ADDR(reg);
+
+ rc = i2c_master_send(up->client, &sc_reg, 1);
+ if (rc < 0) {
+ dev_err(&up->client->dev,
+ "%s I2C error writing the i2c client rc = %d\n",
+ __func__, rc);
+ goto out;
+ }
+
+ rc = i2c_master_recv(up->client, &val, 1);
+ if (rc < 0)
+ dev_err(&up->client->dev,
+ "%s I2C error reading from the i2c client rc = %d\n",
+ __func__, rc);
+
+out:
+ return val;
+}
+
+static inline void sc16is7xx_write_reg(struct uart_sc16is7xx_port *up,
+ unsigned char reg, unsigned char value)
+{
+ int rc;
+ u8 msg[2];
+
+ msg[0] = REG_TO_I2C_ADDR(reg);
+ msg[1] = value;
+
+ rc = i2c_master_send(up->client, msg, 2);
+ if (rc < 0)
+ dev_err(&up->client->dev,
+ "%s I2C error writing the i2c client rc = %d\n",
+ __func__, rc);
+}
+
+static void sc16is7xx_enable_irq(struct uart_sc16is7xx_port *up, int mask)
+{
+ up->ier |= mask;
+ sc16is7xx_write_reg(up, UART_IER, up->ier);
+}
+
+static void sc16is7xx_disable_irq(struct uart_sc16is7xx_port *up, int mask)
+{
+ up->ier &= ~mask;
+ sc16is7xx_write_reg(up, UART_IER, up->ier);
+}
+
+static void sc16is7xx_set_baudrate(struct uart_sc16is7xx_port *up, int baudrate)
+{
+ u8 lcr, ier;
+ u32 divisor;
+
+ ier = sc16is7xx_read_reg(up, UART_IER);
+ lcr = sc16is7xx_read_reg(up, UART_LCR);
+
+ /* Disable interrupts */
+ sc16is7xx_write_reg(up, UART_IER, 0x00);
+
+ /* Disable TX/RX */
+ sc16is7xx_write_reg(up, SC16IS7XX_EFCR, 0x06);
+
+ /* Open the LCR divisors for configuration */
+ sc16is7xx_write_reg(up, UART_LCR, UART_LCR_CONF_MODE_B);
+
+ /* Enable enhanced features and internal clock divider */
+ sc16is7xx_write_reg(up, UART_EFR, 0x10);
+
+ /* Set the input clock divisor to 1 */
+ sc16is7xx_write_reg(up, UART_MCR, UART_MCR_CLKSEL|4);
+
+ /* Get the baudrate divisor from the upper port layer */
+ divisor = uart_get_divisor(&up->port, baudrate);
+
+ /* Write the new divisor */
+ sc16is7xx_write_reg(up, UART_DLL, divisor & 0xff);
+ sc16is7xx_write_reg(up, UART_DLM, (divisor >> 8) & 0xff);
+
+ /* Put LCR back to the normal mode */
+ sc16is7xx_write_reg(up, UART_LCR, lcr);
+
+ sc16is7xx_write_reg(up, SC16IS7XX_TLR, 0x0f);
+
+ /* Enable the FIFOs */
+ up->fcr = UART_FCR_ENABLE_FIFO;
+ sc16is7xx_write_reg(up, UART_FCR, up->fcr);
+
+ /* Enable the Rx and Tx and control the RTS (RS485_DIR) line */
+ sc16is7xx_write_reg(up, SC16IS7XX_EFCR, 0x10);
+
+ /* Restore the interrupts configuration */
+ sc16is7xx_write_reg(up, UART_IER, ier);
+}
+
+/* deliver the Tx characters to the HW */
+static void sc16is7xx_transmit_chars(struct uart_sc16is7xx_port *up)
+{
+ struct circ_buf *xmit = &up->port.state->xmit;
+ int count;
+ int chars = 0;
+
+ dev_dbg(&up->client->dev, "%s\n", __func__);
+
+ if (up->port.x_char) {
+ sc16is7xx_write_reg(up, UART_TX, up->port.x_char);
+ up->port.icount.tx++;
+ up->port.x_char = 0;
+ return;
+ }
+
+ if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) {
+ up->ier &= ~UART_IER_THRI;
+ return;
+ }
+
+ count = up->port.fifosize;
+ do {
+ sc16is7xx_write_reg(up, UART_TX, xmit->buf[xmit->tail]);
+ xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+ chars++;
+ up->port.icount.tx++;
+ if (uart_circ_empty(xmit))
+ break;
+ } while ((--count > 0) && !up->force_end_work);
+
+ dev_dbg(&up->client->dev, "%s: TX %d\n", __func__, chars);
+
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(&up->port);
+
+ if (uart_circ_empty(xmit))
+ up->ier &= ~UART_IER_THRI;
+}
+
+/* receives characters from the HW and transfer themto the TTY layer */
+static inline void sc16is7xx_receive_chars(struct uart_sc16is7xx_port *up,
+ int *status)
+{
+ unsigned int ch, flag;
+ int chars = 0;
+ int max_count = 256;
+
+ dev_dbg(&up->client->dev, "%s\n", __func__);
+
+ do {
+ if (likely(*status & UART_LSR_DR))
+ ch = sc16is7xx_read_reg(up, UART_RX);
+ else
+ ch = 0xffff;
+
+ if (*status & up->port.ignore_status_mask)
+ goto ignore_char;
+
+ flag = TTY_NORMAL;
+ up->port.icount.rx++;
+
+ if (unlikely(*status & (UART_LSR_BRK_ERROR_BITS))) {
+ /*
+ * For statistics only
+ */
+ if (*status & UART_LSR_BI) {
+ *status &= ~(UART_LSR_FE | UART_LSR_PE);
+ up->port.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(&up->port))
+ goto ignore_char;
+ } else if (*status & UART_LSR_PE) {
+ up->port.icount.parity++;
+ } else if (*status & UART_LSR_FE) {
+ up->port.icount.frame++;
+ }
+ if (*status & UART_LSR_OE)
+ up->port.icount.overrun++;
+
+ /* Mask off conditions which should be ignored */
+ *status &= up->port.read_status_mask;
+
+ if (*status & UART_LSR_BI)
+ flag = TTY_BREAK;
+ else if (*status & UART_LSR_PE)
+ flag = TTY_PARITY;
+ else if (*status & UART_LSR_FE)
+ flag = TTY_FRAME;
+ }
+
+ if (unlikely(0xffff == ch))
+ goto ignore_char;
+
+ if (uart_handle_sysrq_char(&up->port, ch))
+ goto ignore_char;
+
+ uart_insert_char(&up->port, *status, UART_LSR_OE, ch, flag);
+ chars++;
+
+ignore_char:
+ *status = sc16is7xx_read_reg(up, UART_LSR);
+ } while ((*status & (UART_LSR_DR | UART_LSR_BI)) &&
+ (max_count-- > 0) && !up->force_end_work);
+
+ dev_dbg(&up->client->dev, "%s RX:%d chars oe:%d\n",
+ __func__, chars, up->port.icount.overrun);
+
+ if (chars > 0)
+ tty_flip_buffer_push(&(up->port.state->port));
+}
+
+static void sc16is7xx_set_termios_work(struct uart_sc16is7xx_port *up)
+{
+ struct ktermios *termios = up->termios_new;
+ struct ktermios *old = up->termios_old;
+ unsigned long flags;
+ unsigned char cval;
+ unsigned int baud;
+
+ spin_lock_irqsave(&up->lock, flags);
+
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ cval = UART_LCR_WLEN5;
+ break;
+ case CS6:
+ cval = UART_LCR_WLEN6;
+ break;
+ case CS7:
+ cval = UART_LCR_WLEN7;
+ break;
+ case CS8:
+ default:
+ cval = UART_LCR_WLEN8;
+ break;
+ }
+
+ if (termios->c_cflag & CSTOPB)
+ cval |= UART_LCR_STOP;
+ if (termios->c_cflag & PARENB)
+ cval |= UART_LCR_PARITY;
+ if (!(termios->c_cflag & PARODD))
+ cval |= UART_LCR_EPAR;
+
+ sc16is7xx_write_reg(up, UART_LCR, cval);
+
+ /* Ask the core to calculate the divisor for us */
+ baud = uart_get_baud_rate(&up->port, termios, old, 9600, 115200);
+ sc16is7xx_set_baudrate(up, baud);
+
+ /* Low latency since we Tx from the work queue */
+ up->port.state->port.low_latency = 1;
+
+ /* Update the per-port timeout */
+ uart_update_timeout(&up->port, termios->c_cflag, baud);
+
+ /* ignore all characters if CREAD is not set */
+ if ((termios->c_cflag & CREAD) == 0)
+ up->port.ignore_status_mask |= UART_LSR_DR;
+
+ if (tty_termios_baud_rate(termios))
+ tty_termios_encode_baud_rate(termios, baud, baud);
+
+ spin_unlock_irqrestore(&up->lock, flags);
+}
+
+static void sc16is7xx_work(struct uart_sc16is7xx_port *up)
+{
+ unsigned int lsr;
+
+ up->ier = sc16is7xx_read_reg(up, UART_IER);
+
+ /*
+ * We cannot handle the interrupts while in the work queue so we
+ * disable the RX interrupt. It is ok because of during the
+ * reception of the characters we check the status of the
+ * interrupt register and process all incoming packets
+ */
+ sc16is7xx_write_reg(up, UART_IER, 0x00);
+
+ dev_dbg(&up->client->dev, "%s: start work - command:%04lx ier:0x%02x\n",
+ __func__, up->command, (int)up->ier);
+
+ if (test_and_clear_bit(SC16IS7XX_CMND_NEW_TERMIOS, &up->command)) {
+ dev_dbg(&up->client->dev, "CMND_NEW_TERMIOS\n");
+ /* User requested the changes in the Terminal Configurations */
+ sc16is7xx_set_termios_work(up);
+ }
+
+ if (test_and_clear_bit(SC16IS7XX_CMND_BREAK_CTRL, &up->command)) {
+ if (up->break_state == -1)
+ up->lcr |= UART_LCR_SBC;
+ else
+ up->lcr &= ~UART_LCR_SBC;
+ sc16is7xx_write_reg(up, UART_LCR, up->lcr);
+ }
+
+ if (test_and_clear_bit(SC16IS7XX_CMND_START_TX, &up->command)) {
+ dev_dbg(&up->client->dev, "CMND_START_TX\n");
+ /* Enable the Tx holding register empty interrupt */
+ up->ier |= UART_IER_THRI;
+
+#ifdef SC16IS7XX_CLEAR_FIFO_ON_TX
+ /* Clear Tx Fifo to remove the junk characters if any */
+ if (up->fcr & UART_FCR_ENABLE_FIFO) {
+ /* Fifo is enabled */
+ sc16is7xx_write_reg(up, UART_FCR,
+ up->fcr & UART_FCR_CLEAR_XMIT);
+ dev_dbg(&up->client->dev, "Clear FIFO\n");
+ }
+#endif
+ }
+
+ if (test_and_clear_bit(SC16IS7XX_CMND_STOP_TX, &up->command)) {
+ dev_dbg(&up->client->dev, "CMND_STOP_TX\n");
+ /* Disable the Tx holding register interrupt */
+ up->ier &= ~UART_IER_THRI;
+ }
+
+ if (test_and_clear_bit(SC16IS7XX_CMND_STOP_RX, &up->command)) {
+ dev_dbg(&up->client->dev, "CMND_STOP_RX\n");
+ /*
+ * User requested to stop the RX interrupt so we clear the
+ * interrupt enable register
+ */
+ up->ier &= ~UART_IER_RDI;
+ }
+
+ clear_bit(SC16IS7XX_CMND_TX_RX, &up->command);
+ lsr = sc16is7xx_read_reg(up, UART_LSR);
+ if ((lsr & (UART_LSR_DR | UART_LSR_BI)) && (up->ier & UART_IER_RDI)) {
+ sc16is7xx_receive_chars(up, &lsr);
+ /* check if there is more to receive */
+ if (lsr & (UART_LSR_DR | UART_LSR_BI))
+ set_bit(SC16IS7XX_CMND_TX_RX, &up->command);
+ }
+
+ if ((lsr & UART_LSR_THRE) && (up->ier & UART_IER_THRI))
+ sc16is7xx_transmit_chars(up);
+
+ if (sc16is7xx_read_reg(up, UART_LSR) & UART_LSR_THRE) {
+ dev_dbg(&up->client->dev, "%s: TX_EMPTY\n", __func__);
+ up->tx_empty = TIOCSER_TEMT;
+ } else {
+ /* port is not ready to accept the new TX characters */
+ dev_dbg(&up->client->dev, "%s: TX_NOT_EMPTY\n", __func__);
+ up->tx_empty = 0;
+ }
+
+ dev_dbg(&up->client->dev, "%s: end work - ier 0x%02X\n",
+ __func__, (int)up->ier);
+
+ /* Restore the interrupts */
+ sc16is7xx_write_reg(up, UART_IER, up->ier);
+}
+
+static irqreturn_t sc16is7xx_ist(int irq, void *dev_id)
+{
+ struct uart_sc16is7xx_port *up = (struct uart_sc16is7xx_port *)dev_id;
+ unsigned long flags;
+
+ spin_lock_irqsave(&up->lock, flags);
+ sc16is7xx_work(up);
+ spin_unlock_irqrestore(&up->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static unsigned int sc16is7xx_tx_empty(struct uart_port *port)
+{
+ struct uart_sc16is7xx_port *up = (struct uart_sc16is7xx_port *)port;
+
+ dev_dbg(&up->client->dev, "%s:(%d)\n", __func__, up->tx_empty);
+
+ return up->tx_empty;
+}
+
+static void sc16is7xx_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+ /*
+ * Just a placeholder
+ * We do not have modem control lines in our RS485 port
+ */
+}
+
+static unsigned int sc16is7xx_get_mctrl(struct uart_port *port)
+{
+ /*
+ * Just a placeholder
+ * We do not have modem control lines in our RS485 port
+ */
+ return TIOCM_CAR | TIOCM_RNG | TIOCM_DSR | TIOCM_CTS;
+}
+
+static void sc16is7xx_start_tx(struct uart_port *port)
+{
+ struct uart_sc16is7xx_port *up = (struct uart_sc16is7xx_port *)port;
+ unsigned long flags;
+
+ spin_lock_irqsave(&up->lock, flags);
+
+ set_bit(SC16IS7XX_CMND_START_TX, &up->command);
+ dev_dbg(&up->client->dev, "%s\n", __func__);
+ sc16is7xx_work(up);
+
+ spin_unlock_irqrestore(&up->lock, flags);
+}
+
+static void sc16is7xx_stop_rx(struct uart_port *port)
+{
+ struct uart_sc16is7xx_port *up = (struct uart_sc16is7xx_port *)port;
+ unsigned long flags;
+
+ spin_lock_irqsave(&up->lock, flags);
+
+ set_bit(SC16IS7XX_CMND_STOP_RX, &up->command);
+ dev_dbg(&up->client->dev, "%s\n", __func__);
+ sc16is7xx_work(up);
+
+ spin_unlock_irqrestore(&up->lock, flags);
+}
+
+static void sc16is7xx_stop_tx(struct uart_port *port)
+{
+ struct uart_sc16is7xx_port *up = (struct uart_sc16is7xx_port *)port;
+ unsigned long flags;
+
+ spin_lock_irqsave(&up->lock, flags);
+
+ set_bit(SC16IS7XX_CMND_STOP_TX, &up->command);
+ dev_dbg(&up->client->dev, "%s\n", __func__);
+ sc16is7xx_work(up);
+
+ spin_unlock_irqrestore(&up->lock, flags);
+}
+
+static void sc16is7xxs_enable_ms(struct uart_port *port)
+{
+ /* Do nothing */
+}
+
+static void sc16is7xx_break_ctl(struct uart_port *port, int break_state)
+{
+ struct uart_sc16is7xx_port *up = (struct uart_sc16is7xx_port *)port;
+ unsigned long flags;
+
+ spin_lock_irqsave(&up->lock, flags);
+
+ dev_dbg(&up->client->dev, "%s:(%d)\n", __func__, break_state);
+ up->break_state = break_state;
+ set_bit(SC16IS7XX_CMND_BREAK_CTRL, &up->command);
+ sc16is7xx_work(up);
+
+ spin_unlock_irqrestore(&up->lock, flags);
+}
+
+static void sc16is7xx_shutdown(struct uart_port *port)
+{
+ struct uart_sc16is7xx_port *up = (struct uart_sc16is7xx_port *)port;
+ unsigned long flags;
+
+ spin_lock_irqsave(&up->lock, flags);
+
+ dev_dbg(&up->client->dev, "%s\n", __func__);
+
+ /* Disable interrupts from this port */
+ sc16is7xx_disable_irq(up, UART_IER_THRI | UART_IER_RDI);
+
+ /* Disable break condition and FIFOs */
+ sc16is7xx_write_reg(up, UART_LCR,
+ sc16is7xx_read_reg(up, UART_LCR) & ~UART_LCR_SBC);
+ sc16is7xx_write_reg(up, UART_FCR,
+ (UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR |
+ UART_FCR_CLEAR_XMIT));
+ sc16is7xx_write_reg(up, UART_FCR, 0);
+
+ spin_unlock_irqrestore(&up->lock, flags);
+
+ dev_dbg(&up->client->dev, "%s done\n", __func__);
+}
+
+static int sc16is7xx_startup(struct uart_port *port)
+{
+ struct uart_sc16is7xx_port *up = (struct uart_sc16is7xx_port *)port;
+ unsigned long flags;
+
+ dev_dbg(&up->client->dev, "%s\n", __func__);
+
+ spin_lock_irqsave(&up->lock, flags);
+
+ up->force_end_work = 0;
+ up->command = 0;
+ up->break_state = -1;
+ up->tx_empty = TIOCSER_TEMT;
+
+ /* Disable IRQs to configure */
+ sc16is7xx_write_reg(up, UART_IER, 0);
+
+ /* Now, initialize the UART */
+ sc16is7xx_write_reg(up, UART_LCR, UART_LCR_8N1);
+
+ /*
+ * Clear the FIFO buffers and disable them
+ * (they will be reenabled in set_termios())
+ */
+ while (sc16is7xx_read_reg(up, UART_LSR) & (UART_LSR_DR | UART_LSR_BI)) {
+ /*
+ * Empty the RX holding register to prevent printing
+ * stale characters on the screen
+ */
+ sc16is7xx_read_reg(up, UART_RX);
+ }
+
+ /* Finally, enable interrupts */
+ sc16is7xx_enable_irq(up, UART_IER_RDI);
+
+ spin_unlock_irqrestore(&up->lock, flags);
+
+ return 0;
+}
+
+static void sc16is7xx_set_termios(struct uart_port *port,
+ struct ktermios *termios,
+ struct ktermios *old)
+{
+ struct uart_sc16is7xx_port *up = (struct uart_sc16is7xx_port *)port;
+ unsigned long flags;
+
+ spin_lock_irqsave(&up->lock, flags);
+
+ up->termios_new = termios;
+ up->termios_old = old;
+
+ set_bit(SC16IS7XX_CMND_NEW_TERMIOS, &up->command);
+
+ sc16is7xx_work(up);
+
+ spin_unlock_irqrestore(&up->lock, flags);
+}
+
+static const char *sc16is7xx_type(struct uart_port *port)
+{
+ struct uart_sc16is7xx_port *up = (struct uart_sc16is7xx_port *)port;
+ return up->port.type == PORT_SC16IS7XX ? DEV_NAME : NULL;
+}
+
+static void sc16is7xx_release_port(struct uart_port *port)
+{
+}
+
+static int sc16is7xx_request_port(struct uart_port *port)
+{
+ return 0;
+}
+
+static void sc16is7xx_config_port(struct uart_port *port, int flags)
+{
+ struct uart_sc16is7xx_port *up = (struct uart_sc16is7xx_port *)port;
+
+ if (flags & UART_CONFIG_TYPE)
+ up->port.type = PORT_SC16IS7XX;
+}
+
+static int sc16is7xx_verify_port(struct uart_port *port,
+ struct serial_struct *ser)
+{
+ if (ser->type == PORT_UNKNOWN || ser->type == PORT_SC16IS7XX)
+ return 0;
+
+ return -EINVAL;
+}
+
+static struct uart_ops sc16is7xx_ops = {
+ .tx_empty = sc16is7xx_tx_empty,
+ .set_mctrl = sc16is7xx_set_mctrl,
+ .get_mctrl = sc16is7xx_get_mctrl,
+ .stop_tx = sc16is7xx_stop_tx,
+ .start_tx = sc16is7xx_start_tx,
+ .stop_rx = sc16is7xx_stop_rx,
+ .enable_ms = sc16is7xxs_enable_ms,
+ .break_ctl = sc16is7xx_break_ctl,
+ .startup = sc16is7xx_startup,
+ .shutdown = sc16is7xx_shutdown,
+ .set_termios = sc16is7xx_set_termios,
+ .type = sc16is7xx_type,
+ .release_port = sc16is7xx_release_port,
+ .request_port = sc16is7xx_request_port,
+ .config_port = sc16is7xx_config_port,
+ .verify_port = sc16is7xx_verify_port,
+};
+
+static struct uart_driver sc16is7xx_uart_driver = {
+ .owner = THIS_MODULE,
+ .driver_name = DRV_NAME,
+ .dev_name = DEV_NAME,
+ .major = SC16IS7XX_MAJOR,
+ .minor = SC16IS7XX_MINOR,
+ .nr = 1,
+};
+
+static int uart_driver_registered;
+
+static int sc16is7xx_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int retval;
+ struct uart_sc16is7xx_port *up = NULL; /* user port */
+
+ if (!uart_driver_registered) {
+ retval = uart_register_driver(&sc16is7xx_uart_driver);
+ if (retval) {
+ pr_err("Couldn't register sc16is7xx uart driver\n");
+ return retval;
+ }
+ uart_driver_registered = 1;
+ }
+
+ up = kzalloc(sizeof(*up), GFP_KERNEL);
+ if (!up)
+ return -ENOMEM;
+
+ /* First check if adaptor is OK and it supports our I2C functionality */
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "Can't find the sc16is7xx chip\n");
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");
+
+ /* Configure the GPIO IRQ line */
+ retval = gpio_request(DA850_RS485_INT_PIN, "SC16IS7xx INT");
+ if (retval) {
+ dev_err(&client->dev, "Can't request gpio interrupt pin\n");
+ retval = -EIO;
+ goto exit;
+ }
+
+ /* Set GPIO IRQ pin to be input */
+ gpio_direction_input(DA850_RS485_INT_PIN);
+
+ up->client = client;
+
+ dev_dbg(&client->dev, "%s: adding port\n", __func__);
+ up->port.irq = gpio_to_irq(DA850_RS485_INT_PIN);
+ up->port.uartclk = SC16IS7XX_CLK;
+ up->port.fifosize = SC16IS7XX_FIFO_SIZE;
+ up->port.ops = &sc16is7xx_ops;
+ up->port.iotype = UPIO_PORT;
+ up->port.flags = UPF_FIXED_TYPE | UPF_FIXED_PORT;
+ up->port.line = 0;
+ up->port.type = PORT_SC16IS7XX;
+ up->port.dev = &client->dev;
+
+ retval = uart_add_one_port(&sc16is7xx_uart_driver, &up->port);
+ if (retval < 0)
+ dev_warn(&client->dev,
+ "uart_add_one_port failed with error %d\n",
+ retval);
+
+ i2c_set_clientdata(client, up);
+
+ /* Disable interrupts */
+ up->ier = 0;
+ sc16is7xx_write_reg(up, UART_IER, 0);
+
+ if (devm_request_threaded_irq(&up->client->dev, up->port.irq,
+ NULL, sc16is7xx_ist,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ DRV_NAME, up) < 0) {
+ dev_err(&up->client->dev, "cannot allocate irq %d\n",
+ up->port.irq);
+ return -EBUSY;
+ }
+
+ return 0;
+
+exit:
+ kfree(up);
+ return retval;
+}
+
+static int sc16is7xx_remove(struct i2c_client *client)
+{
+ struct uart_sc16is7xx_port *up = i2c_get_clientdata(client);
+
+ if (!uart_driver_registered)
+ return 0;
+
+ devm_free_irq(&client->dev, up->port.irq, up);
+
+ /* find out the index for the chip we are removing */
+ dev_dbg(&client->dev, "%s: removing port\n", __func__);
+ uart_remove_one_port(&sc16is7xx_uart_driver, &up->port);
+ kfree(up);
+
+ pr_debug("removing sc16is7xx driver\n");
+ uart_unregister_driver(&sc16is7xx_uart_driver);
+
+ uart_driver_registered = 0;
+
+ return 0;
+}
+
+static const struct i2c_device_id sc16is7xx_i2c_id[] = {
+ { "sc16is7xx", 0},
+ { }
+};
+
+static struct i2c_driver sc16is7xx_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = sc16is7xx_probe,
+ .remove = sc16is7xx_remove,
+ .id_table = sc16is7xx_i2c_id,
+};
+
+module_i2c_driver(sc16is7xx_driver);
+
+MODULE_DESCRIPTION("SC16IS7xx RS485 Port Driver");
+MODULE_AUTHOR("Jon Ringle <jringle@xxxxxxxxxxxxx>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+MODULE_ALIAS("i2c:sc16is7xx");
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index b47dba2..a37c79a 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -238,4 +238,7 @@
/* Tilera TILE-Gx UART */
#define PORT_TILEGX 106

+/* SC16IS74xx */
+#define PORT_SC16IS7XX 107
+
#endif /* _UAPILINUX_SERIAL_CORE_H */
--
1.8.5.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/