This driver is for NAL Reserach Corporation Iridium modems with USB.
There are different variants of the bus protocol, but the driver should
detect this automatically.

Signed-off-by: Bertold Van den Bergh <vandenbe...@bertold.org>
---
 Documentation/usb/usb-serial.rst |  15 ++
 drivers/usb/serial/Kconfig       |   9 +
 drivers/usb/serial/Makefile      |   1 +
 drivers/usb/serial/nal.c         | 357 +++++++++++++++++++++++++++++++
 4 files changed, 382 insertions(+)
 create mode 100644 drivers/usb/serial/nal.c

diff --git a/Documentation/usb/usb-serial.rst b/Documentation/usb/usb-serial.rst
index 8fa7dbd3d..fdc8c0c76 100644
--- a/Documentation/usb/usb-serial.rst
+++ b/Documentation/usb/usb-serial.rst
@@ -494,6 +494,21 @@ Moschip MCS7720, MCS7715 driver
       with this driver with a simple addition to the usb_device_id table.  I
       don't have one of these devices, so I can't say for sure.
 
+NAL Research Corporation driver
+-------------------------------
+
+  This driver is for NAL Reserach Corporation Iridium modems with USB.
+  There are different variants of the bus protocol, but the driver should
+  detect this automatically.
+
+  The manufacturer provided Windows driver attaches to all PIDs in the given
+  VID, so you may want to try this driver even if the PID doesn't match.
+
+  The manufacturer's website: https://www.nalresearch.com/
+
+  For any questions or problems with this driver, please contact:
+  Bertold Van den Bergh <vandenbe...@bertold.org>
+
 Generic Serial driver
 ---------------------
 
diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig
index 4007fa25a..f97c44068 100644
--- a/drivers/usb/serial/Kconfig
+++ b/drivers/usb/serial/Kconfig
@@ -436,6 +436,15 @@ config USB_SERIAL_MXUPORT
          To compile this driver as a module, choose M here: the
          module will be called mxuport.
 
+config USB_SERIAL_NAL
+       tristate "USB NAL Research Corporation Serial Bridge"
+       help
+         Say Y here if you want to use NAL Research Corporation
+         USB devices.
+
+         To compile this driver as a module, choose M here: the
+         module will be called nal.
+
 config USB_SERIAL_NAVMAN
        tristate "USB Navman GPS device"
        help
diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile
index 2d491e434..f3cbe972f 100644
--- a/drivers/usb/serial/Makefile
+++ b/drivers/usb/serial/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_USB_SERIAL_METRO)                        += 
metro-usb.o
 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_NAL)                   += nal.o
 obj-$(CONFIG_USB_SERIAL_NAVMAN)                        += navman.o
 obj-$(CONFIG_USB_SERIAL_OMNINET)               += omninet.o
 obj-$(CONFIG_USB_SERIAL_OPTICON)               += opticon.o
diff --git a/drivers/usb/serial/nal.c b/drivers/usb/serial/nal.c
new file mode 100644
index 000000000..e3272cd5e
--- /dev/null
+++ b/drivers/usb/serial/nal.c
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for NAL Research Corporation USB serial converter.
+ * Tested using A3LA-XG.
+ *
+ * Copyright (C) 2020 Bertold Van den Bergh (vandenbe...@bertold.org)
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+static const struct usb_device_id id_table[] = {
+       { USB_DEVICE(0x2017, 0x0001) },
+       { }
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+struct nal_serial_private {
+       struct usb_device       *dev;
+       struct mutex             cmd_mutex; //Mutex for sharing cmd_buf
+       unsigned char            cmd_buf[64];
+
+       spinlock_t               lock;      //Lock for sharing next three 
entries
+       unsigned char            control_get;
+       unsigned char            control_put;
+       unsigned char            header_type;
+
+       wait_queue_head_t        control_event;
+
+       struct workqueue_struct *work_queue;
+       struct work_struct       control_work;
+       struct work_struct       data_work;
+};
+
+static int nal_prepare_write_buffer(struct usb_serial_port *port,
+                                   void *buf, size_t count);
+static void nal_process_read_urb(struct urb *urb);
+static int nal_request(struct nal_serial_private *priv, int type);
+static void nal_data_work(struct work_struct *work);
+static int nal_tiocmget(struct tty_struct *tty);
+static int nal_send_control(struct nal_serial_private *priv,
+                           unsigned int set, unsigned int clear);
+static int nal_tiocmset(struct tty_struct *tty,
+                       unsigned int set, unsigned int clear);
+static void nal_dtr_rts(struct usb_serial_port *port, int on);
+static void nal_control_work(struct work_struct *work);
+static int nal_port_probe(struct usb_serial_port *serial);
+static int nal_port_remove(struct usb_serial_port *serial);
+
+static struct usb_serial_driver nal_device = {
+       .driver = {
+               .owner =        THIS_MODULE,
+               .name =         "nal",
+       },
+       .id_table               = id_table,
+       .num_ports              = 1,
+       .tiocmget               = nal_tiocmget,
+       .tiocmset               = nal_tiocmset,
+       .port_probe             = nal_port_probe,
+       .port_remove            = nal_port_remove,
+       .dtr_rts                = nal_dtr_rts,
+       .process_read_urb       = nal_process_read_urb,
+       .prepare_write_buffer   = nal_prepare_write_buffer
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+       &nal_device, NULL
+};
+
+#define CONTROL_DSR (1)
+#define CONTROL_CD  (2)
+#define CONTROL_RI  (4)
+#define CONTROL_CTS (8)
+#define CONTROL_DTR (16)
+#define CONTROL_RTS (32)
+
+static int nal_prepare_write_buffer(struct usb_serial_port *port,
+                                   void *buf, size_t count)
+{
+       struct nal_serial_private *priv = usb_get_serial_port_data(port);
+       unsigned char *header = (unsigned char *)buf;
+       unsigned char header_type, cout;
+
+       spin_lock(&priv->lock);
+       header_type = priv->header_type;
+       spin_unlock(&priv->lock);
+
+       count = min_t(size_t, count, 64 - header_type);
+
+       cout = kfifo_out_locked(&port->write_fifo, buf + header_type,
+                               count, &port->lock) + header_type;
+
+       header[0] = 5;
+
+       if (!header_type) {
+               return 0;
+       } else if (header_type == 2) {
+               header[1] = count;
+               return 64;
+       }
+
+       return cout;
+}
+
+static void nal_process_read_urb(struct urb *urb)
+{
+       struct usb_serial_port *port = urb->context;
+       struct nal_serial_private *priv = usb_get_serial_port_data(port);
+       const unsigned char *buf = (const unsigned char *)urb->transfer_buffer;
+       unsigned char length;
+
+       if (urb->actual_length < 1)
+               return;
+
+       if (!priv->header_type && urb->actual_length < 64) {
+               spin_lock(&priv->lock);
+               priv->header_type = 1;
+               spin_unlock(&priv->lock);
+       }
+
+       if (priv->header_type != 1)
+               schedule_work(&priv->data_work);
+
+       if (buf[0] == 5 && urb->actual_length >= 2) {
+               if (!priv->header_type) {
+                       return;
+               } else if (priv->header_type == 1) {
+                       tty_insert_flip_string(&port->port, buf + 1,
+                                              urb->actual_length - 1);
+               } else {
+                       length = buf[1];
+                       if (length > urb->actual_length - 2)
+                               length = urb->actual_length - 2;
+
+                       tty_insert_flip_string(&port->port, buf + 2, length);
+               }
+
+               tty_flip_buffer_push(&port->port);
+       } else if (buf[0] == 0 && urb->actual_length >= 2) {
+               spin_lock(&priv->lock);
+               priv->control_get = 0x80 | buf[1];
+               if (!priv->header_type && urb->actual_length == 64)
+                       priv->header_type = 2;
+               spin_unlock(&priv->lock);
+
+               wake_up(&priv->control_event);
+       } else if (buf[0] == 1) {
+               schedule_work(&priv->control_work);
+       } else {
+               dev_info(&priv->dev->dev, "Unsupported input (length=%u): 
%02x\n",
+                        urb->actual_length, buf[0]);
+       }
+}
+
+static int nal_request(struct nal_serial_private *priv, int type)
+{
+       int ret_val;
+
+       mutex_lock(&priv->cmd_mutex);
+
+       /* type==0: Request control lines
+        * type==1: Request application data
+        */
+       priv->cmd_buf[0] = type ? 0x04 : 0x01;
+       priv->cmd_buf[1] = 0xFF;
+
+       ret_val = usb_bulk_msg(priv->dev, usb_sndbulkpipe(priv->dev, 1),
+                              priv->cmd_buf, 64, NULL, HZ);
+
+       mutex_unlock(&priv->cmd_mutex);
+
+       return ret_val;
+}
+
+static void nal_data_work(struct work_struct *work)
+{
+       struct nal_serial_private *priv =
+               container_of(work, struct nal_serial_private, data_work);
+
+       nal_request(priv, 1);
+}
+
+static int nal_tiocmget(struct tty_struct *tty)
+{
+       struct usb_serial_port *port = tty->driver_data;
+       struct nal_serial_private *priv = usb_get_serial_port_data(port);
+       int ret_val, control = 0;
+
+       spin_lock(&priv->lock);
+       priv->control_get = 0;
+       spin_unlock(&priv->lock);
+
+       ret_val = nal_request(priv, 0);
+       if (ret_val)
+               return ret_val;
+
+       while (!control) {
+               ret_val = wait_event_interruptible_timeout(priv->control_event,
+                                                          priv->control_get > 
0, HZ);
+               if (ret_val == 0)
+                       ret_val = -ETIMEDOUT;
+               if (ret_val < 0)
+                       return ret_val;
+
+               spin_lock(&priv->lock);
+               control = priv->control_get;
+               spin_unlock(&priv->lock);
+       }
+
+       ret_val = ((control & CONTROL_DSR) ? TIOCM_DSR : 0) |
+                ((control & CONTROL_CD)  ? TIOCM_CD  : 0) |
+                ((control & CONTROL_RI)  ? TIOCM_RI  : 0) |
+                ((control & CONTROL_CTS) ? TIOCM_CTS : 0);
+
+       spin_lock(&priv->lock);
+       control = priv->control_put;
+       spin_unlock(&priv->lock);
+
+       ret_val |= ((control & CONTROL_DTR) ? TIOCM_DTR : 0) |
+                 ((control & CONTROL_RTS) ? TIOCM_RTS : 0);
+
+       return ret_val;
+}
+
+static int nal_send_control(struct nal_serial_private *priv,
+                           unsigned int set, unsigned int clear)
+{
+       int ret_val, control;
+
+       mutex_lock(&priv->cmd_mutex);
+
+       spin_lock(&priv->lock);
+       if (set & TIOCM_RTS)
+               priv->control_put |= CONTROL_RTS;
+       if (set & TIOCM_DTR)
+               priv->control_put |= CONTROL_DTR;
+       if (clear & TIOCM_RTS)
+               priv->control_put &= ~CONTROL_RTS;
+       if (clear & TIOCM_DTR)
+               priv->control_put &= ~CONTROL_DTR;
+
+       control = priv->control_put;
+       spin_unlock(&priv->lock);
+
+       priv->cmd_buf[0] = 0x00;
+       priv->cmd_buf[1] = 0x0d | control;
+
+       ret_val = usb_bulk_msg(priv->dev, usb_sndbulkpipe(priv->dev, 1),
+                              priv->cmd_buf, 64, NULL, HZ);
+
+       mutex_unlock(&priv->cmd_mutex);
+
+       return ret_val;
+}
+
+static int nal_tiocmset(struct tty_struct *tty,
+                       unsigned int set, unsigned int clear)
+{
+       struct usb_serial_port *port = tty->driver_data;
+       struct nal_serial_private *priv = usb_get_serial_port_data(port);
+
+       return nal_send_control(priv, set, clear);
+}
+
+static void nal_dtr_rts(struct usb_serial_port *port, int enable)
+{
+       struct nal_serial_private *priv = usb_get_serial_port_data(port);
+
+       if (enable)
+               nal_send_control(priv, TIOCM_RTS | TIOCM_DTR, 0);
+       else
+               nal_send_control(priv, 0, TIOCM_RTS | TIOCM_DTR);
+}
+
+static void nal_control_work(struct work_struct *work)
+{
+       struct nal_serial_private *priv =
+               container_of(work, struct nal_serial_private, control_work);
+
+       nal_send_control(priv, 0, 0);
+}
+
+static int nal_port_probe(struct usb_serial_port *serial)
+{
+       struct nal_serial_private *priv;
+       int ret_val;
+
+       priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->dev = serial->serial->dev;
+       spin_lock_init(&priv->lock);
+       init_waitqueue_head(&priv->control_event);
+       mutex_init(&priv->cmd_mutex);
+
+       priv->work_queue = alloc_workqueue("nal_wq", 0, 0);
+       if (!priv->work_queue) {
+               ret_val = -ENOMEM;
+               goto fail_queue;
+       }
+
+       usb_set_serial_port_data(serial, priv);
+
+       INIT_WORK(&priv->control_work, nal_control_work);
+       INIT_WORK(&priv->data_work, nal_data_work);
+
+       /* Used for header autodetect */
+       ret_val = nal_request(priv, 0);
+       if (ret_val < 0)
+               goto fail_probe;
+
+       ret_val = nal_send_control(priv, TIOCM_RTS | TIOCM_DTR, 0);
+       if (ret_val < 0)
+               goto fail_probe;
+
+       return 0;
+
+fail_probe:
+       cancel_work_sync(&priv->data_work);
+       cancel_work_sync(&priv->control_work);
+       destroy_workqueue(priv->work_queue);
+fail_queue:
+       kfree(priv);
+       return ret_val;
+}
+
+static int nal_port_remove(struct usb_serial_port *serial)
+{
+       struct nal_serial_private *priv = usb_get_serial_port_data(serial);
+
+       cancel_work_sync(&priv->data_work);
+       cancel_work_sync(&priv->control_work);
+       destroy_workqueue(priv->work_queue);
+       kfree(priv);
+
+       return 0;
+}
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+#define AUTHOR "Bertold Van den Bergh <vandenbe...@bertold.org>"
+#define DESC   "Driver for NAL Research Corporation USB serial interface"
+MODULE_AUTHOR(AUTHOR);
+MODULE_DESCRIPTION(DESC);
+MODULE_LICENSE("GPL v2");
-- 
2.20.1

Reply via email to