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. Thank you, Jon Signed-off-by: Jon Ringle <jringle@xxxxxxxxxxxxx> --- 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@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-serial" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html