From: Jon Ringle <jrin...@gridpoint.com>

I started over and rewrote this driver patternized on sccnxp.c

However, I am still experiencing major latency problems with this driver at
19200 speeds.

The method that I'm testing is simply transferring a small file just over
4k in size.

On the target platform I do:
$ socat /dev/ttySC0,raw,echo=0,b19200 - > rx-file

On my development machine, I do:
$ socat /dev/ttyUSB1,echo=0,raw,time=1,min=255,b19200 FILE:./tx-file

When the socat running on the development machine returns to the prompt,
it has transmitted all the bytes in tx-file. I then kill the socat running
on the target platform. Success is defined as rx-file being identical to
tx-file. However, I find that even at only 19200, this driver fails to
receive all bytes sent.

I welcome any and all comments.

Thank you,
Jon

Signed-off-by: Jon Ringle <jrin...@gridpoint.com>
---
 drivers/tty/serial/Kconfig       |   7 +
 drivers/tty/serial/Makefile      |   1 +
 drivers/tty/serial/sc16is7xx.c   | 730 +++++++++++++++++++++++++++++++++++++++
 include/uapi/linux/serial_core.h |   3 +
 4 files changed, 741 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..26268fa7
--- /dev/null
+++ b/drivers/tty/serial/sc16is7xx.c
@@ -0,0 +1,730 @@
+/*
+ * SC16IS740/750/760 tty serial driver - Copyright (C) 2014 GridPoint
+ *
+ *  Based on sccnxp.c, by Alexander Shiyan <shc_w...@mail.ru>
+ *
+ * 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/err.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/console.h>
+#include <linux/gpio.h>
+#include <linux/serial_core.h>
+#include <linux/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/io.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/serial-sc16is7xx.h>
+#include <linux/i2c.h>
+
+#define DRV_NAME       "sc16is7xx"
+#define DRV_VERSION    "0.4"
+#define SC16IS7XX_MAJOR        204
+#define SC16IS7XX_MINOR        8
+
+#define SC16IS7XX_HAVE_IO              0x00000001
+
+/* General registers set */
+#define SC16IS7XX_TCR          0x06
+#define SC16IS7XX_TLR          0x07
+#define SC16IS7XX_TXLVL                0x08
+#define SC16IS7XX_RXLVL                0x09
+#define SC16IS7XX_EFCR         0x0F
+
+struct sc16is7xx_chip {
+       const char              *name;
+       unsigned int            nr;
+       unsigned int            flags;
+       unsigned int            fifosize;
+};
+
+struct sc16is7xx_port {
+       struct uart_driver      uart;
+       struct uart_port        port[SC16IS7XX_MAX_UARTS];
+       bool                    opened[SC16IS7XX_MAX_UARTS];
+
+       struct i2c_client       *client;
+
+       int                     irq;
+       u8                      ier;
+
+       struct sc16is7xx_chip   *chip;
+
+       spinlock_t              lock;
+
+       struct sc16is7xx_pdata  pdata;
+};
+
+static const struct sc16is7xx_chip sc16is740 = {
+       .name           = "SC16IS740",
+       .nr             = 1,
+       .flags          = 0,
+       .fifosize       = 64,
+};
+
+static const struct sc16is7xx_chip sc16is750 = {
+       .name           = "SC16IS750",
+       .nr             = 1,
+       .flags          = SC16IS7XX_HAVE_IO,
+       .fifosize       = 64,
+};
+
+static const struct sc16is7xx_chip sc16is760 = {
+       .name           = "SC16IS760",
+       .nr             = 1,
+       .flags          = SC16IS7XX_HAVE_IO,
+       .fifosize       = 64,
+};
+
+static inline u8 sc16is7xx_read(struct uart_port *port, u8 reg)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       int rc;
+       u8 val = 0;
+       u8 sc_reg = ((reg & 0x0f) << port->regshift);
+
+       rc = i2c_master_send(s->client, &sc_reg, 1);
+       if (rc < 0) {
+               dev_err(&s->client->dev,
+                       "%s I2C error writing the i2c client rc = %d\n",
+                       __func__, rc);
+               goto out;
+       }
+
+       rc = i2c_master_recv(s->client, &val, 1);
+       if (rc < 0)
+               dev_err(&s->client->dev,
+                       "%s I2C error reading from the i2c client rc = %d\n",
+                       __func__, rc);
+
+out:
+       return val;
+}
+
+static inline void sc16is7xx_write(struct uart_port *port, u8 reg, u8 v)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       int rc;
+       u8 msg[2];
+
+       msg[0] = ((reg & 0x0f) << port->regshift);
+       msg[1] = v;
+
+       rc = i2c_master_send(s->client, msg, 2);
+       if (rc < 0)
+               dev_err(&s->client->dev,
+                       "%s I2C error writing the i2c client rc = %d\n",
+                       __func__, rc);
+}
+
+static void sc16is7xx_set_baud(struct uart_port *port, int baud)
+{
+       u8 lcr;
+       u32 divisor;
+
+       lcr = sc16is7xx_read(port, UART_LCR);
+
+       /* Disable TX/RX */
+       sc16is7xx_write(port, SC16IS7XX_EFCR, 0x06);
+
+       /* Open the LCR divisors for configuration */
+       sc16is7xx_write(port, UART_LCR, UART_LCR_CONF_MODE_B);
+
+       /* Enable enhanced features and internal clock divider */
+       sc16is7xx_write(port, UART_EFR, 0x10);
+
+       /* Set the input clock divisor to 1 */
+       sc16is7xx_write(port, UART_MCR, UART_MCR_CLKSEL|4);
+
+       /* Get the baudrate divisor from the upper port layer */
+       divisor = uart_get_divisor(port, baud);
+
+       /* Write the new divisor */
+       sc16is7xx_write(port, UART_DLL, divisor & 0xff);
+       sc16is7xx_write(port, UART_DLM, (divisor >> 8) & 0xff);
+
+       /* Put LCR back to the normal mode */
+       sc16is7xx_write(port, UART_LCR, lcr);
+
+       sc16is7xx_write(port, SC16IS7XX_TLR, 0x0f);
+
+       /* Enable the FIFOs */
+       sc16is7xx_write(port, UART_FCR, UART_FCR_ENABLE_FIFO);
+
+       /* Enable the Rx and Tx and  control the RTS (RS485_DIR) line */
+       sc16is7xx_write(port, SC16IS7XX_EFCR, 0x10);
+}
+
+static void sc16is7xx_enable_irq(struct uart_port *port, int mask)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+
+       s->ier |= mask;
+       sc16is7xx_write(port, UART_IER, s->ier);
+}
+
+static void sc16is7xx_disable_irq(struct uart_port *port, int mask)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+
+       s->ier &= ~mask;
+       sc16is7xx_write(port, UART_IER, s->ier);
+}
+
+static void sc16is7xx_handle_rx(struct uart_port *port)
+{
+       u8 lsr;
+       unsigned int ch, flag;
+
+       for (;;) {
+               lsr = sc16is7xx_read(port, UART_LSR);
+               if (!(lsr & (UART_LSR_DR | UART_LSR_BI)))
+                       break;
+               lsr &= UART_LSR_BRK_ERROR_BITS;
+
+               ch = sc16is7xx_read(port, UART_RX);
+
+               port->icount.rx++;
+               flag = TTY_NORMAL;
+
+               if (unlikely(lsr)) {
+                       if (lsr & UART_LSR_BI) {
+                               port->icount.brk++;
+                               lsr &= ~(UART_LSR_FE | UART_LSR_PE);
+
+                               if (uart_handle_break(port))
+                                       continue;
+                       } else if (lsr & UART_LSR_PE)
+                               port->icount.parity++;
+                       else if (lsr & UART_LSR_FE)
+                               port->icount.frame++;
+                       else if (lsr & UART_LSR_OE) {
+                               port->icount.overrun++;
+                       }
+
+                       lsr &= port->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;
+                       else if (lsr & UART_LSR_OE)
+                               flag = TTY_OVERRUN;
+               }
+
+               if (uart_handle_sysrq_char(port, ch))
+                       continue;
+
+               if (lsr & port->ignore_status_mask)
+                       continue;
+
+               uart_insert_char(port, lsr, UART_LSR_OE, ch, flag);
+       }
+
+       tty_flip_buffer_push(&port->state->port);
+}
+
+static void sc16is7xx_handle_tx(struct uart_port *port)
+{
+       u8 lsr;
+       struct circ_buf *xmit = &port->state->xmit;
+
+       if (unlikely(port->x_char)) {
+               sc16is7xx_write(port, UART_TX, port->x_char);
+               port->icount.tx++;
+               port->x_char = 0;
+               return;
+       }
+
+       if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
+               /* Disable TX if FIFO is empty */
+               if (sc16is7xx_read(port, UART_LSR) & UART_LSR_THRE)
+                       sc16is7xx_disable_irq(port, UART_IER_THRI);
+               return;
+       }
+
+       while (!uart_circ_empty(xmit)) {
+               lsr = sc16is7xx_read(port, UART_LSR);
+               if (!(lsr & UART_LSR_THRE))
+                       break;
+
+               sc16is7xx_write(port, UART_TX, xmit->buf[xmit->tail]);
+               xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+               port->icount.tx++;
+       }
+
+       if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+               uart_write_wakeup(port);
+}
+
+static void sc16is7xx_handle_events(struct sc16is7xx_port *s)
+{
+       int i;
+       u8 iir;
+
+       do {
+               iir = sc16is7xx_read(&s->port[0], UART_IIR);
+               if (!(((iir & UART_IIR_THRI) && (s->ier & UART_IER_THRI))
+                   ||((iir & UART_IIR_RDI) && (s->ier & UART_IER_RDI))))
+                       break;
+
+               for (i = 0; i < s->uart.nr; i++) {
+                       if (s->opened[i] && (iir & UART_IIR_RDI))
+                               sc16is7xx_handle_rx(&s->port[i]);
+                       if (s->opened[i] && (iir & UART_IIR_THRI))
+                               sc16is7xx_handle_tx(&s->port[i]);
+               }
+       } while (1);
+}
+
+static irqreturn_t sc16is7xx_ist(int irq, void *dev_id)
+{
+       struct sc16is7xx_port *s = (struct sc16is7xx_port *)dev_id;
+       unsigned long flags;
+
+       spin_lock_irqsave(&s->lock, flags);
+       sc16is7xx_handle_events(s);
+       spin_unlock_irqrestore(&s->lock, flags);
+
+       return IRQ_HANDLED;
+}
+
+static void sc16is7xx_start_tx(struct uart_port *port)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       unsigned long flags;
+
+       spin_lock_irqsave(&s->lock, flags);
+       sc16is7xx_enable_irq(port, UART_IER_THRI);
+       spin_unlock_irqrestore(&s->lock, flags);
+}
+
+static void sc16is7xx_stop_tx(struct uart_port *port)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       unsigned long flags;
+
+       spin_lock_irqsave(&s->lock, flags);
+       sc16is7xx_disable_irq(port, UART_IER_THRI);
+       spin_unlock_irqrestore(&s->lock, flags);
+}
+
+static void sc16is7xx_stop_rx(struct uart_port *port)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       unsigned long flags;
+
+       spin_lock_irqsave(&s->lock, flags);
+       sc16is7xx_disable_irq(port, UART_IER_RDI);
+       spin_unlock_irqrestore(&s->lock, flags);
+}
+
+static unsigned int sc16is7xx_tx_empty(struct uart_port *port)
+{
+       u8 val;
+       unsigned long flags;
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+
+       spin_lock_irqsave(&s->lock, flags);
+       val = sc16is7xx_read(port, UART_LSR);
+       spin_unlock_irqrestore(&s->lock, flags);
+
+       return (val & UART_LSR_THRE) ? TIOCSER_TEMT : 0;
+}
+
+static void sc16is7xx_enable_ms(struct uart_port *port)
+{
+       /* Do nothing */
+}
+
+static void sc16is7xx_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+       /* Do nothing */
+}
+
+static unsigned int sc16is7xx_get_mctrl(struct uart_port *port)
+{
+       /*
+        * We do not have modem control lines in our RS485 port
+        */
+       return TIOCM_DSR | TIOCM_CTS | TIOCM_CAR | TIOCM_RNG;
+}
+
+static void sc16is7xx_break_ctl(struct uart_port *port, int break_state)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       unsigned long flags;
+       u8 lcr;
+
+       spin_lock_irqsave(&s->lock, flags);
+       lcr = sc16is7xx_read(port, UART_LCR);
+       lcr = (break_state ? (lcr | UART_LCR_SBC) : (lcr & ~UART_LCR_SBC));
+       sc16is7xx_write(port, UART_LCR, lcr);
+       spin_unlock_irqrestore(&s->lock, flags);
+}
+
+static void sc16is7xx_set_termios(struct uart_port *port,
+                                 struct ktermios *termios, struct ktermios 
*old)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       unsigned long flags;
+       u8 cval;
+       u8 fcr;
+       int baud;
+
+       spin_lock_irqsave(&s->lock, flags);
+
+       /* Mask termios capabilities we don't support */
+       termios->c_cflag &= ~CMSPAR;
+
+       /* Disable RX & TX, reset break condition, status and FIFOs */
+       fcr = sc16is7xx_read(port, UART_FCR);
+       fcr |= UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT;
+       fcr &= ~UART_FCR_ENABLE_FIFO;
+       sc16is7xx_write(port, UART_FCR, fcr);
+
+       /* Word size */
+       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;
+       }
+
+       /* Parity */
+       if (termios->c_cflag & PARENB)
+               cval |= UART_LCR_PARITY;
+       if (!(termios->c_cflag & PARODD))
+               cval |= UART_LCR_EPAR;
+
+       /* Stop bits */
+       if (termios->c_cflag & CSTOPB)
+               cval |= UART_LCR_STOP;
+
+       /* Update desired format */
+       sc16is7xx_write(port, UART_LCR, cval);
+
+       /* Set read status mask */
+       port->read_status_mask = UART_LSR_OE;
+       if (termios->c_iflag & INPCK)
+               port->read_status_mask |= UART_LSR_PE | UART_LSR_FE;
+       if (termios->c_iflag & (BRKINT | PARMRK))
+               port->read_status_mask |= UART_LSR_BI;
+
+       /* Set status ignore mask */
+       port->ignore_status_mask = 0;
+       if (termios->c_iflag & IGNBRK)
+               port->ignore_status_mask |= UART_LSR_BI;
+       if (!(termios->c_cflag & CREAD))
+               port->ignore_status_mask |= UART_LSR_BRK_ERROR_BITS;
+
+       /* Setup baudrate */
+       baud = uart_get_baud_rate(port, termios, old, 50, 115200);
+       sc16is7xx_set_baud(port, baud);
+
+       /* Low latency since we Tx from the work queue */
+       port->state->port.low_latency = 1;
+
+       /* Update timeout according to new baud rate */
+       uart_update_timeout(port, termios->c_cflag, baud);
+
+       /* Report actual baudrate back to core */
+       if (tty_termios_baud_rate(termios))
+               tty_termios_encode_baud_rate(termios, baud, baud);
+
+       /* Enable RX & TX */
+       sc16is7xx_write(port, UART_FCR, fcr | UART_FCR_ENABLE_FIFO);
+
+       spin_unlock_irqrestore(&s->lock, flags);
+}
+
+static int sc16is7xx_startup(struct uart_port *port)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       unsigned long flags;
+
+       spin_lock_irqsave(&s->lock, flags);
+
+       /* Disable IRQs to configure */
+       sc16is7xx_write(port, UART_IER, 0);
+
+       /* Now, initialize the UART */
+       sc16is7xx_write(port, UART_LCR, UART_LCR_WLEN8);
+
+       /*
+        * Clear the FIFO buffers and disable them
+        * (they will be reenabled in set_termios())
+        */
+       while (sc16is7xx_read(port, UART_LSR) & (UART_LSR_DR | UART_LSR_BI)) {
+               /*
+                * Empty the RX holding register to prevent printing
+                * stale characters on the screen
+                */
+               sc16is7xx_read(port, UART_RX);
+       }
+
+       /* Finally, enable interrupts */
+       sc16is7xx_enable_irq(port, UART_IER_RDI);
+
+       s->opened[port->line] = 1;
+
+       spin_unlock_irqrestore(&s->lock, flags);
+
+       return 0;
+}
+
+static void sc16is7xx_shutdown(struct uart_port *port)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       unsigned long flags;
+
+       spin_lock_irqsave(&s->lock, flags);
+
+       s->opened[port->line] = 0;
+
+       /* Disable interrupts */
+       sc16is7xx_disable_irq(port, UART_IER_THRI | UART_IER_RDI);
+
+       /* Disable break condition and FIFOs */
+       sc16is7xx_write(port, UART_LCR,
+                           sc16is7xx_read(port, UART_LCR) & ~UART_LCR_SBC);
+       sc16is7xx_write(port, UART_FCR,
+                           (UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR |
+                            UART_FCR_CLEAR_XMIT));
+       sc16is7xx_write(port, UART_FCR, 0);
+
+       spin_unlock_irqrestore(&s->lock, flags);
+}
+
+static const char *sc16is7xx_type(struct uart_port *port)
+{
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+
+       return (port->type == PORT_SC16IS7XX) ? s->chip->name : NULL;
+}
+
+static void sc16is7xx_release_port(struct uart_port *port)
+{
+       /* Do nothing */
+}
+
+static int sc16is7xx_request_port(struct uart_port *port)
+{
+       /* Do nothing */
+       return 0;
+}
+
+static void sc16is7xx_config_port(struct uart_port *port, int flags)
+{
+       if (flags & UART_CONFIG_TYPE)
+               port->type = PORT_SC16IS7XX;
+}
+
+static int sc16is7xx_verify_port(struct uart_port *port, struct serial_struct 
*s)
+{
+       if ((s->type == PORT_UNKNOWN) || (s->type == PORT_SC16IS7XX))
+               return 0;
+       if (s->irq == port->irq)
+               return 0;
+
+       return -EINVAL;
+}
+
+static const 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      = sc16is7xx_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 const struct i2c_device_id sc16is7xx_id_table[] = {
+       { .name = "sc16is740",  .driver_data = (kernel_ulong_t)&sc16is740, },
+       { .name = "sc16is750",  .driver_data = (kernel_ulong_t)&sc16is750, },
+       { .name = "sc16is760",  .driver_data = (kernel_ulong_t)&sc16is760, },
+       { }
+};
+
+static int sc16is7xx_probe(struct i2c_client *client,
+                          const struct i2c_device_id *id)
+{
+       struct sc16is7xx_pdata *pdata = dev_get_platdata(&client->dev);
+       int i, ret;
+       struct sc16is7xx_port *s;
+
+       if (!pdata)
+               return -ENODEV;
+
+       s = devm_kzalloc(&client->dev, sizeof(struct sc16is7xx_port), 
GFP_KERNEL);
+       if (!s) {
+               dev_err(&client->dev, "Error allocating port structure\n");
+               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");
+               ret = -ENODEV;
+               goto err_out;
+       }
+
+       dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");
+
+       spin_lock_init(&s->lock);
+
+       s->chip = (struct sc16is7xx_chip *)id->driver_data;
+
+       memcpy(&s->pdata, pdata, sizeof(struct sc16is7xx_pdata));
+
+       /* Configure the GPIO IRQ line */
+       ret = gpio_request(pdata->irq_pin, "SC16IS7xx INT");
+       if (ret) {
+               dev_err(&client->dev, "Can't request gpio interrupt pin\n");
+               ret = -EIO;
+               goto err_out;
+       }
+
+       /* Set GPIO IRQ pin to be input */
+       gpio_direction_input(pdata->irq_pin);
+
+       s->irq = gpio_to_irq(pdata->irq_pin);
+       if (s->irq < 0) {
+               dev_err(&client->dev, "Missing irq_pin data\n");
+               ret = -ENXIO;
+               goto err_out;
+       }
+
+       s->uart.owner           = THIS_MODULE;
+       s->uart.dev_name        = "ttySC";
+       s->uart.major           = SC16IS7XX_MAJOR;
+       s->uart.minor           = SC16IS7XX_MINOR;
+       s->uart.nr              = s->chip->nr;
+
+       ret = uart_register_driver(&s->uart);
+       if (ret) {
+               dev_err(&client->dev, "Registering UART driver failed\n");
+               goto err_out;
+       }
+
+       for (i = 0; i < s->uart.nr; i++) {
+               s->port[i].line         = i;
+               s->port[i].dev          = &client->dev;
+               s->port[i].irq          = s->irq;
+               s->port[i].type         = PORT_SC16IS7XX;
+               s->port[i].fifosize     = s->chip->fifosize;
+               s->port[i].flags        = UPF_SKIP_TEST | UPF_FIXED_TYPE;
+               s->port[i].regshift     = s->pdata.reg_shift;
+               s->port[i].uartclk      = s->pdata.uartclk;
+               s->port[i].ops          = &sc16is7xx_ops;
+               s->port[i].iotype       = UPIO_PORT;
+
+               uart_add_one_port(&s->uart, &s->port[i]);
+       }
+
+       s->client = client;
+       i2c_set_clientdata(client, s);
+
+       /* Disable interrupts */
+       s->ier = 0;
+       sc16is7xx_write(&s->port[0], UART_IER, 0);
+
+       ret = devm_request_threaded_irq(&client->dev, s->irq, NULL,
+                                       sc16is7xx_ist,
+                                       IRQF_TRIGGER_FALLING |
+                                       IRQF_ONESHOT,
+                                       dev_name(&client->dev), s);
+       if (!ret)
+               return 0;
+
+       dev_err(&client->dev, "Unable to request IRQ %i\n", s->irq);
+
+err_out:
+       kfree(s);
+       return ret;
+}
+
+static int sc16is7xx_remove(struct i2c_client *client)
+{
+       int i;
+       struct sc16is7xx_port *s = i2c_get_clientdata(client);
+
+       devm_free_irq(&client->dev, s->irq, s);
+
+       for (i = 0; i < s->uart.nr; i++)
+               uart_remove_one_port(&s->uart, &s->port[i]);
+
+       kfree(s);
+       uart_unregister_driver(&s->uart);
+
+       return 0;
+}
+
+static struct i2c_driver sc16is7xx_driver = {
+       .driver = {
+               .name   = DRV_NAME,
+               .owner  = THIS_MODULE,
+       },
+       .probe          = sc16is7xx_probe,
+       .remove         = sc16is7xx_remove,
+       .id_table       = sc16is7xx_id_table,
+};
+
+module_i2c_driver(sc16is7xx_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Jon Ringle <jrin...@gridpoint.com>");
+MODULE_DESCRIPTION("SC16IS7xx RS485 Port Driver");
+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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to