* [PATCH] RFC: WIP: sc16is7xx [v0.4]
@ 2014-03-10 6:26 jon
2014-03-10 7:02 ` Alexander Shiyan
2014-03-10 14:17 ` One Thousand Gnomes
0 siblings, 2 replies; 9+ messages in thread
From: jon @ 2014-03-10 6:26 UTC (permalink / raw)
To: gregkh, jslaby; +Cc: linux-kernel, linux-serial, Jon Ringle
From: Jon Ringle <jringle@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 <jringle@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_work@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 <jringle@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-serial" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH] RFC: WIP: sc16is7xx [v0.4]
2014-03-10 6:26 [PATCH] RFC: WIP: sc16is7xx [v0.4] jon
@ 2014-03-10 7:02 ` Alexander Shiyan
2014-03-10 11:50 ` Jon Ringle
2014-03-10 14:17 ` One Thousand Gnomes
1 sibling, 1 reply; 9+ messages in thread
From: Alexander Shiyan @ 2014-03-10 7:02 UTC (permalink / raw)
To: jon; +Cc: gregkh, jslaby, linux-kernel, linux-serial, Jon Ringle
Понедельник, 10 марта 2014, 2:26 -04:00 от jon@ringle.org:
> From: Jon Ringle <jringle@gridpoint.com>
...
> +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
Documentation says:
The SC16IS740/750/760 is a slave I²C-bus/SPI interface to a single-channel
high performance UART.
...
The SC16IS740/750/760 also provides additional advanced features such
as auto hardware and software flow control, automatic RS-485 support...
So why do you position this chip as RS485 only? Automatic direction for
RS485 is just a feature as an addition for standart UART.
---
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] RFC: WIP: sc16is7xx [v0.4]
2014-03-10 7:02 ` Alexander Shiyan
@ 2014-03-10 11:50 ` Jon Ringle
2014-03-10 14:20 ` One Thousand Gnomes
2014-03-10 16:14 ` Alexander Shiyan
0 siblings, 2 replies; 9+ messages in thread
From: Jon Ringle @ 2014-03-10 11:50 UTC (permalink / raw)
To: Alexander Shiyan
Cc: jon, gregkh, jslaby, linux-kernel, linux-serial, Jon Ringle
[-- Attachment #1: Type: TEXT/PLAIN, Size: 1031 bytes --]
On Mon, 10 Mar 2014, Alexander Shiyan wrote:
> Понедельник, 10 марта 2014, 2:26 -04:00 от jon@ringle.org:
> > From: Jon Ringle <jringle@gridpoint.com>
> ...
> > +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
>
> Documentation says:
> The SC16IS740/750/760 is a slave I²C-bus/SPI interface to a single-channel
> high performance UART.
> ...
> The SC16IS740/750/760 also provides additional advanced features such
> as auto hardware and software flow control, automatic RS-485 support...
>
> So why do you position this chip as RS485 only? Automatic direction for
> RS485 is just a feature as an addition for standart UART.
You're right. This is just how we are using the chip in our board. Would a
configuration flag in platform_data make sense to use the auto rs485
direction?
Any thoughts as to the latency issue I report?
Thanks.
Regards,
Jon
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] RFC: WIP: sc16is7xx [v0.4]
2014-03-10 11:50 ` Jon Ringle
@ 2014-03-10 14:20 ` One Thousand Gnomes
2014-03-10 16:14 ` Alexander Shiyan
1 sibling, 0 replies; 9+ messages in thread
From: One Thousand Gnomes @ 2014-03-10 14:20 UTC (permalink / raw)
To: Jon Ringle
Cc: Alexander Shiyan, gregkh, jslaby, linux-kernel, linux-serial,
Jon Ringle
On Mon, 10 Mar 2014 07:50:12 -0400 (EDT)
Jon Ringle <jon@ringle.org> wrote:
>
> On Mon, 10 Mar 2014, Alexander Shiyan wrote:
>
> > Понедельник, 10 марта 2014, 2:26 -04:00 от jon@ringle.org:
> > > From: Jon Ringle <jringle@gridpoint.com>
> > ...
> > > +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
> >
> > Documentation says:
> > The SC16IS740/750/760 is a slave I²C-bus/SPI interface to a single-channel
> > high performance UART.
> > ...
> > The SC16IS740/750/760 also provides additional advanced features such
> > as auto hardware and software flow control, automatic RS-485 support...
> >
> > So why do you position this chip as RS485 only? Automatic direction for
> > RS485 is just a feature as an addition for standart UART.
>
> You're right. This is just how we are using the chip in our board. Would a
> configuration flag in platform_data make sense to use the auto rs485
> direction?
We have interfaces for RS485 runtime configuration so it could be
extended to set a lot of parameters that way, but I think for now it
might not be a bad idea to support rs485 auto direction in/out/off in the
platform data.
Alan
--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] RFC: WIP: sc16is7xx [v0.4]
2014-03-10 11:50 ` Jon Ringle
2014-03-10 14:20 ` One Thousand Gnomes
@ 2014-03-10 16:14 ` Alexander Shiyan
2014-03-11 18:20 ` Jon Ringle
1 sibling, 1 reply; 9+ messages in thread
From: Alexander Shiyan @ 2014-03-10 16:14 UTC (permalink / raw)
To: Jon Ringle; +Cc: gregkh, jslaby, linux-kernel, linux-serial, Jon Ringle
Понедельник, 10 марта 2014, 7:50 -04:00 от Jon Ringle <jon@ringle.org>:
>
> On Mon, 10 Mar 2014, Alexander Shiyan wrote:
>
> > Понедельник, 10 марта 2014, 2:26 -04:00 от jon@ringle.org:
> > > From: Jon Ringle <jringle@gridpoint.com>
> > ...
> > > +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
> >
> > Documentation says:
> > The SC16IS740/750/760 is a slave I²C-bus/SPI interface to a single-channel
> > high performance UART.
> > ...
> > The SC16IS740/750/760 also provides additional advanced features such
> > as auto hardware and software flow control, automatic RS-485 support...
> >
> > So why do you position this chip as RS485 only? Automatic direction for
> > RS485 is just a feature as an addition for standart UART.
>
> You're right. This is just how we are using the chip in our board. Would a
> configuration flag in platform_data make sense to use the auto rs485
> direction?
You don't need a flag, just use IOCTLs.
https://git.kernel.org/cgit/linux/kernel/git/next/linux-next.git/commit/drivers/tty/serial/max310x.c?id=55367c620aed6bc27a82bb1763366931d7f2ee66
> Any thoughts as to the latency issue I report?
I do not understand why you chose the sccnxp driver as a template.
Use as a template max310x driver from linux-next branch.
I'm sure it will be better.
About latency.
I'm not exactly understand the problem, but I can say the following:
Since you're using the I2C bus, maximum UART daudrate can be
predicted from the I2C bus bandwidth.
Count how many I2C clocks spent on reception and transmission
of one byte through UART.
---
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] RFC: WIP: sc16is7xx [v0.4]
2014-03-10 16:14 ` Alexander Shiyan
@ 2014-03-11 18:20 ` Jon Ringle
2014-03-11 18:44 ` Jon Ringle
0 siblings, 1 reply; 9+ messages in thread
From: Jon Ringle @ 2014-03-11 18:20 UTC (permalink / raw)
To: Alexander Shiyan
Cc: Jon Ringle, gregkh, jslaby, linux-kernel, linux-serial,
Jon Ringle
[-- Attachment #1: Type: TEXT/PLAIN, Size: 5376 bytes --]
On Mon, 10 Mar 2014, Alexander Shiyan wrote:
> Понедельник, 10 марта 2014, 7:50 -04:00 от Jon Ringle <jon@ringle.org>:
> >
> > On Mon, 10 Mar 2014, Alexander Shiyan wrote:
> >
> > > Понедельник, 10 марта 2014, 2:26 -04:00 от jon@ringle.org:
> > > > From: Jon Ringle <jringle@gridpoint.com>
> > > ...
> > > > +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
> > >
> > > Documentation says:
> > > The SC16IS740/750/760 is a slave I²C-bus/SPI interface to a single-channel
> > > high performance UART.
> > > ...
> > > The SC16IS740/750/760 also provides additional advanced features such
> > > as auto hardware and software flow control, automatic RS-485 support...
> > >
> > > So why do you position this chip as RS485 only? Automatic direction for
> > > RS485 is just a feature as an addition for standart UART.
> >
> > You're right. This is just how we are using the chip in our board. Would a
> > configuration flag in platform_data make sense to use the auto rs485
> > direction?
>
> You don't need a flag, just use IOCTLs.
> https://git.kernel.org/cgit/linux/kernel/git/next/linux-next.git/commit/drivers/tty/serial/max310x.c?id=55367c620aed6bc27a82bb1763366931d7f2ee66
>
> > Any thoughts as to the latency issue I report?
>
> I do not understand why you chose the sccnxp driver as a template.
> Use as a template max310x driver from linux-next branch.
> I'm sure it will be better.
I followed your advice on using max310x as a template. One thing that I'm
struggling to get right is the regmap. The datasheet[1] specifies the
register bits as follows:
Table 33. Register address byte (I2C)
Bit Name Function
7 - not used
6:3 A[3:0] UART's internal register select
2:1 CH1,0 channel select: CH1 = 0, CH0 = 0
Other values are reserved and should not be used.
0 - not used
So, I need to shift the register address by 3. Here is how I have defined
the regmap stuff:
------>8 [relevant code] >8-------
/* SC16IS7XX register definitions */
#define SC16IS7XX_RHR_REG (0x00) /* RX FIFO */
#define SC16IS7XX_THR_REG (0x00) /* TX FIFO */
#define SC16IS7XX_IER_REG (0x01) /* Interrupt enable */
#define SC16IS7XX_IIR_REG (0x02) /* Interrupt Ident */
#define SC16IS7XX_FCR_REG (0x02) /* FIFO control */
#define SC16IS7XX_LCR_REG (0x03) /* Line Control */
#define SC16IS7XX_MCR_REG (0x04) /* Modem Control */
#define SC16IS7XX_LSR_REG (0x05) /* Line Status */
#define SC16IS7XX_MSR_REG (0x06) /* Modem Status */
#define SC16IS7XX_SPR_REG (0x07) /* Scratch Pad */
#define SC16IS7XX_TXLVL_REG (0x08) /* TX FIFO level */
#define SC16IS7XX_RXLVL_REG (0x09) /* RX FIFO level */
#define SC16IS7XX_IODIR_REG (0x0a) /* I/O Dir (750/760) */
#define SC16IS7XX_IOSTATE_REG (0x0b) /* I/O State (750/760) */
#define SC16IS7XX_IOINTENA_REG (0x0c) /* I/O Int Enable (750/760) */
#define SC16IS7XX_IOCONTROL_REG (0x0e) /* I/O Control (750/760) */
#define SC16IS7XX_EFCR_REG (0x0f) /* Extra Feature Control */
/* TX/Trigger Registers: Only if ((MCR[2] == 0) && (EFR[4] == 1)) */
#define SC16IS7XX_TCR_REG (0x06) /* Transmit control */
#define SC16IS7XX_TLR_REG (0x07) /* Trigger level */
/* Special Register set: Only if ((LCR[7] == 1) && (LCR != 0xBF)) */
#define SC16IS7XX_DLL_REG (0x00) /* Divisor Latch Low */
#define SC16IS7XX_DLH_REG (0x01) /* Divisor Latch High */
/* Enhanced Register set: Only if (LCR == 0xBF) */
#define SC16IS7XX_EFR_REG (0x02) /* Enhanced Features */
#define SC16IS7XX_XON1_REG (0x04) /* Xon1 word */
#define SC16IS7XX_XON2_REG (0x05) /* Xon2 word */
#define SC16IS7XX_XOFF1_REG (0x06) /* Xoff1 word */
#define SC16IS7XX_XOFF2_REG (0x07) /* Xoff2 word */
#define SC16IS7XX_REG_SHIFT 3
static bool sc16is7xx_reg_writeable(struct device *dev, unsigned int reg)
{
switch ((reg >> SC16IS7XX_REG_SHIFT) & 0xf) {
case SC16IS7XX_LSR_REG:
case SC16IS7XX_MSR_REG:
case SC16IS7XX_TXLVL_REG:
case SC16IS7XX_RXLVL_REG:
return false;
default:
break;
}
return true;
}
static bool sc16is7xx_reg_volatile(struct device *dev, unsigned int reg)
{
switch ((reg >> SC16IS7XX_REG_SHIFT) & 0xf) {
case SC16IS7XX_RHR_REG:
case SC16IS7XX_IIR_REG:
case SC16IS7XX_LSR_REG:
case SC16IS7XX_MSR_REG:
case SC16IS7XX_TXLVL_REG:
case SC16IS7XX_RXLVL_REG:
case SC16IS7XX_IOSTATE_REG:
return true;
default:
break;
}
return false;
}
static bool sc16is7xx_reg_precious(struct device *dev, unsigned int reg)
{
switch ((reg >> SC16IS7XX_REG_SHIFT) & 0xf) {
case SC16IS7XX_RHR_REG:
case SC16IS7XX_IIR_REG:
case SC16IS7XX_MSR_REG:
return true;
default:
break;
}
return false;
}
static struct regmap_config regcfg = {
.reg_bits = 8,
.reg_stride = (1 << SC16IS7XX_REG_SHIFT),
.val_bits = 8,
.max_register = (0xf << SC16IS7XX_REG_SHIFT),
.cache_type = REGCACHE_RBTREE,
.writeable_reg = sc16is7xx_reg_writeable,
.volatile_reg = sc16is7xx_reg_volatile,
.precious_reg = sc16is7xx_reg_precious,
};
------------->8-----------------
I'm not sure that I have this defined correctly. I also saw in
regmap_config pad_bits, which I've tried setting to SC16IS7XX_REG_SHIFT,
but when I do that, I get -EINVAL failure.
Thanks,
Jon
[1] http://www.nxp.com/documents/data_sheet/SC16IS740_750_760.pdf
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH] RFC: WIP: sc16is7xx [v0.4]
2014-03-11 18:20 ` Jon Ringle
@ 2014-03-11 18:44 ` Jon Ringle
2014-03-11 19:20 ` Alexander Shiyan
0 siblings, 1 reply; 9+ messages in thread
From: Jon Ringle @ 2014-03-11 18:44 UTC (permalink / raw)
To: Jon Ringle
Cc: Alexander Shiyan, gregkh, jslaby, linux-kernel, linux-serial,
Jon Ringle
[-- Attachment #1: Type: TEXT/PLAIN, Size: 4879 bytes --]
I left out some relevant functions (sc16is7xx_port_read(),
sc16is7xx_port_write(), sc16is7xx_port_update()) in my previous email...
On Mon, 10 Mar 2014, Alexander Shiyan wrote:
> Понедельник, 10 марта 2014, 7:50 -04:00 от Jon Ringle <jon@ringle.org>:
> >
> > On Mon, 10 Mar 2014, Alexander Shiyan wrote:
> I do not understand why you chose the sccnxp driver as a template.
> Use as a template max310x driver from linux-next branch.
> I'm sure it will be better.
I followed your advice on using max310x as a template. One thing that I'm
struggling to get right is the regmap. The datasheet[1] specifies the
register bits as follows:
Table 33. Register address byte (I2C)
Bit Name Function
7 - not used
6:3 A[3:0] UART's internal register select
2:1 CH1,0 channel select: CH1 = 0, CH0 = 0
Other values are reserved and should not be used.
0 - not used
So, I need to shift the register address by 3. Here is how I have defined
the regmap stuff:
------>8 [relevant code] >8-------
/* SC16IS7XX register definitions */
#define SC16IS7XX_RHR_REG (0x00) /* RX FIFO */
#define SC16IS7XX_THR_REG (0x00) /* TX FIFO */
#define SC16IS7XX_IER_REG (0x01) /* Interrupt enable */
#define SC16IS7XX_IIR_REG (0x02) /* Interrupt Ident */
#define SC16IS7XX_FCR_REG (0x02) /* FIFO control */
#define SC16IS7XX_LCR_REG (0x03) /* Line Control */
#define SC16IS7XX_MCR_REG (0x04) /* Modem Control */
#define SC16IS7XX_LSR_REG (0x05) /* Line Status */
#define SC16IS7XX_MSR_REG (0x06) /* Modem Status */
#define SC16IS7XX_SPR_REG (0x07) /* Scratch Pad */
#define SC16IS7XX_TXLVL_REG (0x08) /* TX FIFO level */
#define SC16IS7XX_RXLVL_REG (0x09) /* RX FIFO level */
#define SC16IS7XX_IODIR_REG (0x0a) /* I/O Dir (750/760) */
#define SC16IS7XX_IOSTATE_REG (0x0b) /* I/O State (750/760) */
#define SC16IS7XX_IOINTENA_REG (0x0c) /* I/O Int Enable (750/760) */
#define SC16IS7XX_IOCONTROL_REG (0x0e) /* I/O Control (750/760) */
#define SC16IS7XX_EFCR_REG (0x0f) /* Extra Feature Control */
/* TX/Trigger Registers: Only if ((MCR[2] == 0) && (EFR[4] == 1)) */
#define SC16IS7XX_TCR_REG (0x06) /* Transmit control */
#define SC16IS7XX_TLR_REG (0x07) /* Trigger level */
/* Special Register set: Only if ((LCR[7] == 1) && (LCR != 0xBF)) */
#define SC16IS7XX_DLL_REG (0x00) /* Divisor Latch Low */
#define SC16IS7XX_DLH_REG (0x01) /* Divisor Latch High */
/* Enhanced Register set: Only if (LCR == 0xBF) */
#define SC16IS7XX_EFR_REG (0x02) /* Enhanced Features */
#define SC16IS7XX_XON1_REG (0x04) /* Xon1 word */
#define SC16IS7XX_XON2_REG (0x05) /* Xon2 word */
#define SC16IS7XX_XOFF1_REG (0x06) /* Xoff1 word */
#define SC16IS7XX_XOFF2_REG (0x07) /* Xoff2 word */
#define SC16IS7XX_REG_SHIFT 3
static u8 sc16is7xx_port_read(struct uart_port *port, u8 reg)
{
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
unsigned int val = 0;
regmap_read(s->regmap, reg << SC16IS7XX_REG_SHIFT, &val);
return val;
}
static void sc16is7xx_port_write(struct uart_port *port, u8 reg, u8 val)
{
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
regmap_write(s->regmap, reg << SC16IS7XX_REG_SHIFT, val);
}
static void sc16is7xx_port_update(struct uart_port *port, u8 reg, u8 mask, u8 val)
{
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
regmap_update_bits(s->regmap, reg << SC16IS7XX_REG_SHIFT, mask, val);
}
static bool sc16is7xx_reg_writeable(struct device *dev, unsigned int reg)
{
switch ((reg >> SC16IS7XX_REG_SHIFT) & 0xf) {
case SC16IS7XX_LSR_REG:
case SC16IS7XX_MSR_REG:
case SC16IS7XX_TXLVL_REG:
case SC16IS7XX_RXLVL_REG:
return false;
default:
break;
}
return true;
}
static bool sc16is7xx_reg_volatile(struct device *dev, unsigned int reg)
{
switch ((reg >> SC16IS7XX_REG_SHIFT) & 0xf) {
case SC16IS7XX_RHR_REG:
case SC16IS7XX_IIR_REG:
case SC16IS7XX_LSR_REG:
case SC16IS7XX_MSR_REG:
case SC16IS7XX_TXLVL_REG:
case SC16IS7XX_RXLVL_REG:
case SC16IS7XX_IOSTATE_REG:
return true;
default:
break;
}
return false;
}
static bool sc16is7xx_reg_precious(struct device *dev, unsigned int reg)
{
switch ((reg >> SC16IS7XX_REG_SHIFT) & 0xf) {
case SC16IS7XX_RHR_REG:
case SC16IS7XX_IIR_REG:
case SC16IS7XX_MSR_REG:
return true;
default:
break;
}
return false;
}
static struct regmap_config regcfg = {
.reg_bits = 8,
.reg_stride = (1 << SC16IS7XX_REG_SHIFT),
.val_bits = 8,
.max_register = (0xf << SC16IS7XX_REG_SHIFT),
.cache_type = REGCACHE_RBTREE,
.writeable_reg = sc16is7xx_reg_writeable,
.volatile_reg = sc16is7xx_reg_volatile,
.precious_reg = sc16is7xx_reg_precious,
};
------------->8-----------------
I'm not sure that I have this defined correctly. I also saw in
regmap_config pad_bits, which I've tried setting to SC16IS7XX_REG_SHIFT,
but when I do that, I get -EINVAL failure.
Thanks,
Jon
[1] http://www.nxp.com/documents/data_sheet/SC16IS740_750_760.pdf
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH] RFC: WIP: sc16is7xx [v0.4]
2014-03-11 18:44 ` Jon Ringle
@ 2014-03-11 19:20 ` Alexander Shiyan
0 siblings, 0 replies; 9+ messages in thread
From: Alexander Shiyan @ 2014-03-11 19:20 UTC (permalink / raw)
To: Jon Ringle; +Cc: gregkh, jslaby, linux-serial, Jon Ringle
Вторник, 11 марта 2014, 14:44 -04:00 от Jon Ringle <jon@ringle.org>:
>
> I left out some relevant functions (sc16is7xx_port_read(),
> sc16is7xx_port_write(), sc16is7xx_port_update()) in my previous email...
>
> On Mon, 10 Mar 2014, Alexander Shiyan wrote:
>
> > Понедельник, 10 марта 2014, 7:50 -04:00 от Jon Ringle <jon@ringle.org>:
> > >
> > > On Mon, 10 Mar 2014, Alexander Shiyan wrote:
> > I do not understand why you chose the sccnxp driver as a template.
> > Use as a template max310x driver from linux-next branch.
> > I'm sure it will be better.
Here are the approximate changes:
> #define SC16IS7XX_REG_SHIFT 3
#define SC16IS7XX_REG_SHIFT 2
> static u8 sc16is7xx_port_read(struct uart_port *port, u8 reg)
> {
> struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
> unsigned int val = 0;
>
> regmap_read(s->regmap, reg << SC16IS7XX_REG_SHIFT, &val);
regmap_read(s->regmap, (reg << SC16IS7XX_REG_SHIFT) | port->index, &val);
Same for "write" and "update".
> static struct regmap_config regcfg = {
> .reg_bits = 8,
.reg_bits = 7,
> .reg_stride = (1 << SC16IS7XX_REG_SHIFT),
Kill this line (.reg_stride), but add:
.pad_bits = 1,
---
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] RFC: WIP: sc16is7xx [v0.4]
2014-03-10 6:26 [PATCH] RFC: WIP: sc16is7xx [v0.4] jon
2014-03-10 7:02 ` Alexander Shiyan
@ 2014-03-10 14:17 ` One Thousand Gnomes
1 sibling, 0 replies; 9+ messages in thread
From: One Thousand Gnomes @ 2014-03-10 14:17 UTC (permalink / raw)
To: jon; +Cc: gregkh, jslaby, linux-kernel, linux-serial, Jon Ringle
> +#define DRV_NAME "sc16is7xx"
> +#define DRV_VERSION "0.4"
> +#define SC16IS7XX_MAJOR 204
> +#define SC16IS7XX_MINOR 8
Use dynamic minors for any new device
> +static inline u8 sc16is7xx_read(struct uart_port *port, u8 reg)
I wouldn't inline this - the compiler will figure it out for you and
you've got a fair bit code there.
> +static void sc16is7xx_handle_rx(struct uart_port *port)
This gets called from handle_events which gets called from the ist
handler with IRQs disabled. I'm surprised it works at all but you
definitely don't want to be doing all this work with interrupts blocked.
That may be part of your problem.
> +static void sc16is7xx_handle_tx(struct uart_port *port)
Ditto
> +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;
(for the unknown case you also need to do
termios->c_cflag &= ~CSIZE;
termios->c_cflag |= CS8;
so the caller gets informed they didn't get their requested feature
Likewise btw for unsupported features. In this case CMSPAR appears not to
be supported ?)
Reading drivers/tty/serial/max310x.c may be helpful with regards to the
locking. It is SPI based so also has to defer some of the processing from
the IRQ handler. The threaded IRQ support we now have may also help you.
Alan
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2014-03-11 19:21 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-03-10 6:26 [PATCH] RFC: WIP: sc16is7xx [v0.4] jon
2014-03-10 7:02 ` Alexander Shiyan
2014-03-10 11:50 ` Jon Ringle
2014-03-10 14:20 ` One Thousand Gnomes
2014-03-10 16:14 ` Alexander Shiyan
2014-03-11 18:20 ` Jon Ringle
2014-03-11 18:44 ` Jon Ringle
2014-03-11 19:20 ` Alexander Shiyan
2014-03-10 14:17 ` One Thousand Gnomes
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).