lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:	Thu,  6 Mar 2014 21:35:46 -0500
From:	jon@...gle.org
To:	gregkh@...uxfoundation.org, jslaby@...e.cz
Cc:	linux-kernel@...r.kernel.org, linux-serial@...r.kernel.org,
	Jon Ringle <jringle@...dpoint.com>
Subject: [PATCH] RFC: WIP: sc16is7xx.c

From: Jon Ringle <jringle@...dpoint.com>

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.

Thank you,
Jon

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

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index febd45c..c841272 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..5daed84
--- /dev/null
+++ b/drivers/tty/serial/sc16is7xx.c
@@ -0,0 +1,850 @@
+/*
+ * 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.
+ *
+ * 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.
+ *
+ * Notes:
+ * The sc16is740 driver is used for the GPEC RS485 Half duplex communication.
+ * In the EC1K board the sc16is740 RTS line is connected to the SN65HVD1780DR chip and which
+ * is used to signal the RS485 diretion. When the RTS is low the RS485 direction is set to
+ * output from the CPU.
+ * To set the RS485 diretion 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).
+ *
+ */
+
+#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 new 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,lpback 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",__FUNCTION__, 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",__FUNCTION__, 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",__FUNCTION__, 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);
+	sc16is7xx_write_reg(up, UART_IER, 0x00); // Disable interrupts
+
+	sc16is7xx_write_reg(up, SC16IS7XX_EFCR, 0x06 ); // Disable TX/RX
+
+	sc16is7xx_write_reg(up, UART_LCR, UART_LCR_CONF_MODE_B);   // Open the LCR divisors for configuration
+
+	sc16is7xx_write_reg(up, UART_EFR, 0x10);   // Enable enhanced features and internal clock divider
+
+	sc16is7xx_write_reg(up, UART_MCR, UART_MCR_CLKSEL|4);  // Set the input clock divisor to 1
+
+	/* Get the baudrate divisor from the upper port layer */
+	divisor = uart_get_divisor(&up->port, baudrate);
+
+	sc16is7xx_write_reg(up, UART_DLL, divisor & 0xff );      // Write the new divisor
+	sc16is7xx_write_reg(up, UART_DLM, (divisor >> 8) & 0xff);
+
+	sc16is7xx_write_reg(up, UART_LCR, lcr);   // Put LCR back to the normal mode
+
+	sc16is7xx_write_reg(up, SC16IS7XX_TLR, 0x0f);
+	up->fcr = UART_FCR_ENABLE_FIFO;
+	sc16is7xx_write_reg(up, UART_FCR, up->fcr);   // Enable the FIFOs
+
+	sc16is7xx_write_reg(up, SC16IS7XX_EFCR, 0x10); // Enable the Rx and Tx and  control the RTS (RS485_DIR) line
+
+	sc16is7xx_write_reg(up, UART_IER, ier); // Restore the interrupts configuration
+}
+
+/*
+ * The function actually delivers 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;
+	}
+}
+
+/*
+ * The function actually receives the characters from the HW and transfers them up to 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;
+	default:
+	case CS8:
+		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);
+
+	up->port.state->port.low_latency = 1; // Low latency since we Tx from the work queue
+
+	/*
+	 * 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);
+		if (lsr & (UART_LSR_DR | UART_LSR_BI)) {
+			/* There is more to receive */
+			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", __FUNCTION__);
+		up->tx_empty = TIOCSER_TEMT;
+	}
+	else
+	{
+		dev_dbg(&up->client->dev, "%s: TX_NOT_EMPTY\n", __FUNCTION__);
+		up->tx_empty = 0; /* port is not ready to accept  the new TX characters */
+	}
+
+	dev_dbg(&up->client->dev, "%s: end work ier 0x%02X\n", __func__, (int)up->ier);
+
+	sc16is7xx_write_reg(up, UART_IER, up->ier); /* Restore the interrupts */
+}
+
+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", __FUNCTION__, 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", __FUNCTION__, 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)
+{
+	int ret = -EINVAL;
+
+	if (ser->type == PORT_UNKNOWN || ser->type == PORT_SC16IS7XX)
+		ret = 0;
+	return ret;
+}
+
+
+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 = 0;
+
+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) {
+			printk(KERN_ERR "Couldn't register sc16is7xx uart driver\n");
+			return retval;
+		}
+		uart_driver_registered = 1;
+	}
+
+	up = kzalloc(sizeof(struct uart_sc16is7xx_port), GFP_KERNEL);
+	if (!up) {
+		dev_warn(&client->dev,
+			 "kmalloc for sc16is7xx structure failed!\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");
+		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;
+	}
+
+	gpio_direction_input(DA850_RS485_INT_PIN); // Set GPIO IRQ pin to be input
+
+	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);
+	up = NULL;
+
+	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@...dpoint.com>");
+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@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ