[PATCH v2] USB: serial: add nt124 usb to serial driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This driver is for the NovaTech 124 4x serial expansion board for the
NovaTech OrionLXm.

Firmware source code can be found here:
https://github.com/novatechweb/nt124

Signed-off-by: George McCollister <george.mccollister@xxxxxxxxx>
---
Changes to v1:
  - Added description after nt124.c on line 2.
  - Removed DRIVER_AUTHOR and DRIVER_DESC, use MODULE macros directly.
  - Removed some unnecessary new lines and comments.
  - Removed __packed from struct nt124_line_coding.
  - Added locking around ctrlin and ctrlout.
  - Switch ctrlin and ctrlout from unsigned int to u16.
  - Removed serial_transmit and added tx_empty. Use a hybrid
    notification/polling method to accurately determine when transmission is
    finished while minimizing bus traffic (see comments in the code for
    details).
  - Removed flowctrl from struct nt124_line_coding.
  - Use u16 for request and value, size_t for len arguments of nt124_ctrl_msg()
  - Use USB_CTRL_SET_TIMEOUT instead of 5000.
  - Use %04x for 16-bit variables and %zu for size_t variables in dev_dbg() and
    dev_err().
  - Removed use of ?: constructs.
  - Removed nt124_set_control, nt124_set_line, nt124_send_break and
  - nt124_set_flowctrl macros in favor of calling nt124_ctrl_msg() directly.
  - Renamed nt124_process_notify() to nt124_process_status().
  - Call usb_serial_handle_dcd_change() unconditionally when DCD has changed.
  - Removed in argument list assignments.
  - Use usb_translate_errors() in nt124_port_tiocmset().
  - Use C_CSTOPB, C_CSIZE, C_PARENB, C_CMSPAR, C_PARODD, C_CRTSCTS macros.
  - Raise/lower RTS on !B0/B0.
  - Added NT124_BREAK_ON and NT124_BREAK_OFF #defines.
  - Change nt124_open() to just call nt124_set_termios() followed by
    usb_serial_generic_open().
  - Don't set bulk_in_size and bulk_out_size.
  - Performed thorough testing.

 drivers/usb/serial/Kconfig  |   9 +
 drivers/usb/serial/Makefile |   1 +
 drivers/usb/serial/nt124.c  | 501 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 511 insertions(+)
 create mode 100644 drivers/usb/serial/nt124.c

diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig
index b7cf198..677a26a 100644
--- a/drivers/usb/serial/Kconfig
+++ b/drivers/usb/serial/Kconfig
@@ -510,6 +510,15 @@ config USB_SERIAL_NAVMAN
 	  To compile this driver as a module, choose M here: the
 	  module will be called navman.
 
+config USB_SERIAL_NT124
+	tristate "USB NovaTech 124 Serial Driver"
+	help
+	  Say Y here if you want to use the NovaTech 124 4x USB to serial
+	  board.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called nt124.
+
 config USB_SERIAL_PL2303
 	tristate "USB Prolific 2303 Single Port Serial Driver"
 	help
diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile
index 349d9df..f88eaab 100644
--- a/drivers/usb/serial/Makefile
+++ b/drivers/usb/serial/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_USB_SERIAL_MOS7720)		+= mos7720.o
 obj-$(CONFIG_USB_SERIAL_MOS7840)		+= mos7840.o
 obj-$(CONFIG_USB_SERIAL_MXUPORT)		+= mxuport.o
 obj-$(CONFIG_USB_SERIAL_NAVMAN)			+= navman.o
+obj-$(CONFIG_USB_SERIAL_NT124)			+= nt124.o
 obj-$(CONFIG_USB_SERIAL_OMNINET)		+= omninet.o
 obj-$(CONFIG_USB_SERIAL_OPTICON)		+= opticon.o
 obj-$(CONFIG_USB_SERIAL_OPTION)			+= option.o
diff --git a/drivers/usb/serial/nt124.c b/drivers/usb/serial/nt124.c
new file mode 100644
index 0000000..d837593
--- /dev/null
+++ b/drivers/usb/serial/nt124.c
@@ -0,0 +1,501 @@
+/*
+ * nt124.c - Driver for nt124 4x serial board based on STM32F103
+ *
+ * Copyright (c) 2014 - 2015 NovaTech LLC
+ *
+ * Portions derived from the cdc-acm driver
+ *
+ * The original intention was to implement a cdc-acm compliant
+ * 4x USB to serial converter in the STM32F103 however several problems arose.
+ *   The STM32F103 didn't have enough end points to implement 4 ports.
+ *   CTS control was required by the application.
+ *   Accurate notification of transmission completion was required.
+ *   RTSCTS flow control support was required.
+ *
+ * The interrupt endpoint was eliminated and the control line information
+ * was moved to the first two bytes of the bulk in endpoint message. CTS
+ * control and mechanisms to enable RTSCTS flow control and deliver TXEMPTY
+ * information were added.
+ *
+ * Firmware source code can be found here:
+ * https://github.com/novatechweb/nt124
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <asm/unaligned.h>
+
+#define NT124_VID		0x2aeb
+#define NT124_USB_PID		124
+
+static const struct usb_device_id id_table[] = {
+	{ USB_DEVICE(NT124_VID, NT124_USB_PID) },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+/*
+ * Output control lines.
+ */
+#define NT124_CTRL_DTR		0x01
+#define NT124_CTRL_RTS		0x02
+
+/*
+ * Input control lines and line errors.
+ */
+#define NT124_CTRL_DCD		0x01
+#define NT124_CTRL_DSR		0x02
+#define NT124_CTRL_BRK		0x04
+#define NT124_CTRL_RI		0x08
+#define NT124_CTRL_FRAMING	0x10
+#define NT124_CTRL_PARITY	0x20
+#define NT124_CTRL_OVERRUN	0x40
+#define NT124_CTRL_TXEMPTY	0x80
+#define NT124_CTRL_CTS		0x100
+
+#define USB_NT124_REQ_SET_LINE_CODING		0x20
+#define USB_NT124_REQ_SET_CONTROL_LINE_STATE	0x22
+#define USB_NT124_REQ_SEND_BREAK		0x23
+#define USB_NT124_REQ_SET_FLOW_CONTROL		0x90
+#define USB_NT124_REQ_GET_TXEMPTY		0x91
+
+#define NT124_BREAK_OFF		0x0
+#define NT124_BREAK_ON		0xffff
+
+#define NT124_STOP_BITS_1	0
+#define NT124_STOP_BITS_2	2
+
+#define NT124_PARITY_NONE	0
+#define NT124_PARITY_ODD	1
+#define NT124_PARITY_EVEN	2
+
+#define NT124_RTSCTS_FLOW_CONTROL_OFF	0
+#define NT124_RTSCTS_FLOW_CONTROL_ON	1
+
+struct nt124_line_coding {
+	__le32	dwDTERate;
+	u8	bCharFormat;
+	u8	bParityType;
+	u8	bDataBits;
+};
+
+struct nt124_private {
+	u16 bInterfaceNumber;
+	/* input control lines (DCD, DSR, RI, break, overruns) */
+	u16 ctrlin;
+	/* output control lines (DTR, RTS) */
+	u16 ctrlout;
+	spinlock_t ctrl_lock;
+	struct nt124_line_coding line;
+	bool tx_empty;
+};
+
+static int nt124_ctrl_msg(struct usb_serial_port *port, u16 request, u16 value,
+			  void *buf, size_t len)
+{
+	struct nt124_private *priv = usb_get_serial_port_data(port);
+	int retval;
+
+	retval = usb_control_msg(port->serial->dev,
+			usb_sndctrlpipe(port->serial->dev, 0),
+			request, USB_DIR_OUT | USB_TYPE_VENDOR, value,
+			priv->bInterfaceNumber,
+			buf, len, USB_CTRL_SET_TIMEOUT);
+
+	dev_dbg(&port->dev,
+		"%s - rq 0x%04x, val 0x%04x, len %zu, result %d\n",
+		__func__, request, value, len, retval);
+
+	if (retval < 0) {
+		dev_err(&port->dev,
+			"%s - usb_control_msg failed (%d)\n",
+			__func__, retval);
+		return retval;
+	}
+
+	if (retval != len) {
+		dev_err(&port->dev,
+			"%s - short write (%d / %zu)\n",
+			__func__, retval, len);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static void nt124_process_status(struct usb_serial_port *port,
+				 struct nt124_private *priv,
+				 unsigned char *data)
+{
+	u16 ctrlin;
+	u16 newctrl;
+	unsigned long flags;
+	struct tty_struct *tty;
+
+	newctrl = get_unaligned_le16(data);
+
+	spin_lock_irqsave(&priv->ctrl_lock, flags);
+	ctrlin = priv->ctrlin;
+	priv->ctrlin = newctrl;
+	if (newctrl & NT124_CTRL_TXEMPTY)
+		priv->tx_empty = true;
+	spin_unlock_irqrestore(&priv->ctrl_lock, flags);
+
+	if (ctrlin & ~newctrl & NT124_CTRL_DCD) {
+		tty = tty_port_tty_get(&port->port);
+		if (tty)
+			usb_serial_handle_dcd_change(port, tty,
+					newctrl & NT124_CTRL_DCD);
+	}
+}
+
+static void nt124_process_read_urb(struct urb *urb)
+{
+	struct usb_serial_port *port = urb->context;
+	struct nt124_private *priv = usb_get_serial_port_data(port);
+	unsigned char *data = (unsigned char *)urb->transfer_buffer;
+	size_t datalen;
+
+	/* The packet should always be at least 2 bytes because status is
+	 * included. If it's too short, discard it. */
+	if (urb->actual_length < 2)
+		return;
+
+	nt124_process_status(port, priv, data);
+
+	datalen = urb->actual_length - 2;
+
+	if (!datalen)
+		return;
+
+	tty_insert_flip_string(&port->port, &data[2], datalen);
+	tty_flip_buffer_push(&port->port);
+}
+
+static int nt124_tiocmget(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct nt124_private *priv = usb_get_serial_port_data(port);
+	int result = 0;
+	unsigned long flags;
+	u16 ctrlout;
+	u16 ctrlin;
+
+	spin_lock_irqsave(&priv->ctrl_lock, flags);
+	ctrlout = priv->ctrlout;
+	ctrlin = priv->ctrlin;
+	spin_unlock_irqrestore(&priv->ctrl_lock, flags);
+
+	if (ctrlout & NT124_CTRL_DTR)
+		result |= TIOCM_DTR;
+
+	if (ctrlout & NT124_CTRL_RTS)
+		result |= TIOCM_RTS;
+
+	if (ctrlin & NT124_CTRL_DSR)
+		result |= TIOCM_DSR;
+
+	if (ctrlin & NT124_CTRL_RI)
+		result |= TIOCM_RI;
+
+	if (ctrlin & NT124_CTRL_DCD)
+		result |= TIOCM_CD;
+
+	if (ctrlin & NT124_CTRL_CTS)
+		result |= TIOCM_CTS;
+
+	return result;
+}
+
+static int nt124_port_tiocmset(struct usb_serial_port *port,
+			       unsigned int set, unsigned int clear)
+{
+	struct nt124_private *priv = usb_get_serial_port_data(port);
+	unsigned int newctrl;
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&priv->ctrl_lock, flags);
+	newctrl = priv->ctrlout;
+
+	if (set & TIOCM_DTR)
+		newctrl |= NT124_CTRL_DTR;
+
+	if (set & TIOCM_RTS)
+		newctrl |= NT124_CTRL_RTS;
+
+	if (clear & TIOCM_DTR)
+		newctrl &= ~NT124_CTRL_DTR;
+
+	if (clear & TIOCM_RTS)
+		newctrl &= ~NT124_CTRL_RTS;
+
+	if (priv->ctrlout == newctrl) {
+		spin_unlock_irqrestore(&priv->ctrl_lock, flags);
+		return 0;
+	}
+
+	priv->ctrlout = newctrl;
+	spin_unlock_irqrestore(&priv->ctrl_lock, flags);
+
+	ret = nt124_ctrl_msg(port, USB_NT124_REQ_SET_CONTROL_LINE_STATE,
+			newctrl, NULL, 0);
+
+	return usb_translate_errors(ret);
+}
+
+static int nt124_tiocmset(struct tty_struct *tty,
+			  unsigned int set, unsigned int clear)
+{
+	struct usb_serial_port *port = tty->driver_data;
+
+	return nt124_port_tiocmset(port, set, clear);
+}
+
+static void nt124_dtr_rts(struct usb_serial_port *port, int on)
+{
+	if (on)
+		nt124_port_tiocmset(port, TIOCM_DTR | TIOCM_RTS, 0);
+	else
+		nt124_port_tiocmset(port, 0, TIOCM_DTR | TIOCM_RTS);
+}
+
+static void nt124_set_termios(struct tty_struct *tty,
+			      struct usb_serial_port *port,
+			      struct ktermios *termios_old)
+{
+	struct nt124_private *priv = usb_get_serial_port_data(port);
+	struct ktermios *termios = &tty->termios;
+	struct nt124_line_coding newline;
+	int newctrl;
+	u16 flowcontrol;
+	unsigned long flags;
+
+	newline.dwDTERate = cpu_to_le32(tty_get_baud_rate(tty));
+	if (C_CSTOPB(tty))
+		newline.bCharFormat = NT124_STOP_BITS_2;
+	else
+		newline.bCharFormat = NT124_STOP_BITS_1;
+
+	/* Mark and space parity aren't supported.
+	 * Use the old setting or no parity if termios_old isn't available. */
+	if (C_PARENB(tty) && C_CMSPAR(tty)) {
+		termios->c_cflag &= ~(PARENB | PARODD | CMSPAR);
+		if (termios_old)
+			termios->c_cflag |= termios_old->c_cflag &
+				(PARENB | PARODD | CMSPAR);
+	}
+
+	if (C_PARENB(tty)) {
+		if (C_PARODD(tty))
+			newline.bParityType = NT124_PARITY_ODD;
+		else
+			newline.bParityType = NT124_PARITY_EVEN;
+	} else {
+		newline.bParityType = NT124_PARITY_NONE;
+	}
+
+	/* 5 and 6 databits aren't supported.
+	 * Use the old setting or 8 databits if termios_old isn't available. */
+	if (C_CSIZE(tty) == CS5 || C_CSIZE(tty) == CS6) {
+		termios->c_cflag &= ~CSIZE;
+		if (termios_old)
+			termios->c_cflag |= termios_old->c_cflag & CSIZE;
+		else
+			termios->c_cflag |= CS8;
+	}
+
+	switch (C_CSIZE(tty)) {
+	case CS7:
+		newline.bDataBits = 7;
+		break;
+	case CS8:
+	default:
+		newline.bDataBits = 8;
+		break;
+	}
+
+	spin_lock_irqsave(&priv->ctrl_lock, flags);
+	newctrl = priv->ctrlout;
+	if (C_BAUD(tty) == B0) {
+		newline.dwDTERate = priv->line.dwDTERate;
+		newctrl &= ~(NT124_CTRL_DTR | NT124_CTRL_RTS);
+	} else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) {
+		newctrl |= NT124_CTRL_DTR | NT124_CTRL_RTS;
+	}
+
+	if (newctrl != priv->ctrlout) {
+		priv->ctrlout = newctrl;
+		spin_unlock_irqrestore(&priv->ctrl_lock, flags);
+		nt124_ctrl_msg(port, USB_NT124_REQ_SET_CONTROL_LINE_STATE,
+			newctrl, NULL, 0);
+	} else
+		spin_unlock_irqrestore(&priv->ctrl_lock, flags);
+
+	if (memcmp(&priv->line, &newline, sizeof(newline))) {
+		memcpy(&priv->line, &newline, sizeof(newline));
+		dev_dbg(&port->dev, "%s - set line: %u %u %u %u\n",
+			__func__,
+			le32_to_cpu(newline.dwDTERate),
+			newline.bCharFormat, newline.bParityType,
+			newline.bDataBits);
+		nt124_ctrl_msg(port, USB_NT124_REQ_SET_LINE_CODING, 0,
+			       &priv->line, sizeof(priv->line));
+	}
+
+	if (!termios_old ||
+		C_CRTSCTS(tty) != (termios_old->c_cflag & CRTSCTS)) {
+		if (C_CRTSCTS(tty))
+			flowcontrol = NT124_RTSCTS_FLOW_CONTROL_ON;
+		else
+			flowcontrol = NT124_RTSCTS_FLOW_CONTROL_OFF;
+		nt124_ctrl_msg(port, USB_NT124_REQ_SET_FLOW_CONTROL,
+				flowcontrol, NULL, 0);
+	}
+}
+
+static bool nt124_tx_empty(struct usb_serial_port *port)
+{
+	struct nt124_private *priv = usb_get_serial_port_data(port);
+	u8 tx_empty;
+	int retval;
+	unsigned long flags;
+	bool result;
+
+	/* If tx_empty has already been marked false there is no need to poll
+	 * for it's value since we can rely on a bulk in empty notification.
+	 * In the common situation of tcdrain() being called after transmitting
+	 * we prevent a considerable amount of bus traffic. */
+	spin_lock_irqsave(&priv->ctrl_lock, flags);
+	if (!priv->tx_empty) {
+		spin_unlock_irqrestore(&priv->ctrl_lock, flags);
+		return false;
+	}
+
+	/* Set tx_empty to false, since it can only set it to true below
+	 * there is no possibility to lose a tx_empty notification from the
+	 * bulk in packet while the lock isn't held */
+	priv->tx_empty = false;
+	spin_unlock_irqrestore(&priv->ctrl_lock, flags);
+
+	retval = usb_control_msg(port->serial->dev,
+			usb_rcvctrlpipe(port->serial->dev, 0),
+			USB_NT124_REQ_GET_TXEMPTY,
+			USB_DIR_IN | USB_TYPE_VENDOR,
+			0,
+			priv->bInterfaceNumber,
+			&tx_empty,
+			sizeof(tx_empty),
+			USB_CTRL_SET_TIMEOUT);
+	if (retval < 0) {
+		dev_err(&port->dev,
+			"%s - usb_control_msg failed (%d)\n",
+			__func__, retval);
+		return true;
+	}
+
+	if (retval != sizeof(tx_empty)) {
+		dev_err(&port->dev,
+			"%s - short read (%d / %zu)\n",
+			__func__, retval, sizeof(tx_empty));
+		return true;
+	}
+
+	spin_lock_irqsave(&priv->ctrl_lock, flags);
+	if (tx_empty)
+		priv->tx_empty = true;
+	result = priv->tx_empty;
+	spin_unlock_irqrestore(&priv->ctrl_lock, flags);
+
+	return result;
+}
+
+static void nt124_break_ctl(struct tty_struct *tty, int state)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	int retval;
+	u16 val;
+
+	if (state)
+		val = NT124_BREAK_ON;
+	else
+		val = NT124_BREAK_OFF;
+
+	retval = nt124_ctrl_msg(port, USB_NT124_REQ_SEND_BREAK, val, NULL, 0);
+	if (retval < 0)
+		dev_err(&port->dev, "%s - send break failed\n", __func__);
+}
+
+static int nt124_open(struct tty_struct *tty,
+		      struct usb_serial_port *port)
+{
+	nt124_set_termios(tty, port, NULL);
+
+	return usb_serial_generic_open(tty, port);
+}
+
+static int nt124_port_probe(struct usb_serial_port *port)
+{
+	struct usb_interface *interface = port->serial->interface;
+	struct usb_host_interface *cur_altsetting = interface->cur_altsetting;
+	struct nt124_private *priv;
+
+	priv = devm_kzalloc(&port->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	spin_lock_init(&priv->ctrl_lock);
+	priv->bInterfaceNumber = cur_altsetting->desc.bInterfaceNumber;
+	priv->tx_empty = true;
+
+	usb_set_serial_port_data(port, priv);
+
+	return 0;
+}
+
+static struct usb_serial_driver nt124_device = {
+	.driver = {
+		.owner =	THIS_MODULE,
+		.name =		"nt124",
+	},
+	.id_table =		id_table,
+	.num_ports =		1,
+	.open =			nt124_open,
+	.process_read_urb =	nt124_process_read_urb,
+	.tx_empty =		nt124_tx_empty,
+	.throttle =		usb_serial_generic_throttle,
+	.unthrottle =		usb_serial_generic_unthrottle,
+	.set_termios =		nt124_set_termios,
+	.tiocmget =		nt124_tiocmget,
+	.tiocmset =		nt124_tiocmset,
+	.dtr_rts =		nt124_dtr_rts,
+	.break_ctl =		nt124_break_ctl,
+	.port_probe =		nt124_port_probe,
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+	&nt124_device, NULL
+};
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_AUTHOR("George McCollister <george.mccollister@xxxxxxxxx>");
+MODULE_DESCRIPTION("nt124 USB serial driver");
+MODULE_LICENSE("GPL");
-- 
2.2.2

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




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux