linux-usb.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* Driver for MaxLinear/Exar USB (UART) Serial adapters.
@ 2018-04-04  7:06 Patong Yang
  0 siblings, 0 replies; 15+ messages in thread
From: Patong Yang @ 2018-04-04  7:06 UTC (permalink / raw)
  To: johan; +Cc: gregkh, pyang, linux-usb

The driver is based on the CDC-ACM driver. In addition to supporting 
the features of the MaxLinear/Exar USB UART devices, the driver also 
has support for 2 other functions per customer requirements:

- Specific entries are checked in the BIOS to detect if the board is a
  "Caracalla" board before enabling specific modes in the MaxLinear/Exar
  USB UARTs.  The smbios code is based on the example at:
  https://sourceforge.net/projects/smbios/

- When specific IOCTLs are called by a user-space application, a 
  port_config file is created for the /dev/ttyXRUSB device at a
  specific USB tree location, and some configuration data is stored. 
  The driver checks for the port_config file when the driver is loaded
  for each port and loads the configuration settings if there is a
  port_config file for the USB tree location.

Signed-off-by: Patong Yang <patong.mxl@gmail.com>
---
 drivers/usb/serial/xrusb_serial.c | 3285 +++++++++++++++++++++++++++++++++++++
 drivers/usb/serial/xrusb_serial.h |  448 +++++
 2 files changed, 3733 insertions(+)
 create mode 100644 drivers/usb/serial/xrusb_serial.c
 create mode 100644 drivers/usb/serial/xrusb_serial.h

diff --git a/drivers/usb/serial/xrusb_serial.c b/drivers/usb/serial/xrusb_serial.c
new file mode 100644
index 000000000000..16a5bcff9103
--- /dev/null
+++ b/drivers/usb/serial/xrusb_serial.c
@@ -0,0 +1,3285 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * xrusb_serial.c
+ *
+ * Copyright (c) 2018 Patong Yang <patong.mxl@gmail.com>
+ *
+ * USB Serial Driver based on the cdc-acm.c driver for the
+ * MaxLinear/Exar USB UARTs/Serial adapters
+ */
+
+
+#undef DEBUG
+#undef VERBOSE_DEBUG
+
+#include <linux/kernel.h>
+#include <linux/sched/signal.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/log2.h>
+#include <linux/serial.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/dmi.h>
+#include <asm/byteorder.h>
+#include <asm/unaligned.h>
+#include <linux/idr.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+
+#include "linux/version.h"
+#include "xrusb_serial.h"
+
+#define DRIVER_AUTHOR "Patong Yang <patong.mxl@gmail.com>"
+#define DRIVER_DESC "MaxLinear/Exar USB UART (serial port) driver"
+
+static struct usb_driver xrusb_driver;
+static struct tty_driver *xrusb_tty_driver;
+static struct xrusb *xrusb_table[XRUSB_TTY_MINORS];
+static struct file *port_param_file[32];
+static struct reg_addr_map xr2280x_reg_map;
+static struct reg_addr_map xr21b1411_reg_map;
+static struct reg_addr_map xr21v141x_reg_map;
+static struct reg_addr_map xr21b142x_reg_map;
+
+static DEFINE_MUTEX(xrusb_table_lock);
+
+/*
+ * SMBIOS code snippets below borrowed from
+ * https://sourceforge.net/projects/smbios/
+ *
+ * Checking SMBIOS values to determine if this is Caracalla board
+ * where modes are programmed in the BIOS
+ *
+ */
+
+unsigned char smbios_check_entry_point (void *addr)
+{
+	unsigned char *i;
+	unsigned char checksum = 0;
+	unsigned char length;
+
+	length = ((struct smbios_entry_point_struct *) addr)->
+		entry_point_length;
+	/* calculate checksum for entry point structure (should be 0) */
+	for (i = (unsigned char *) addr;
+		i < (unsigned char *) addr + length; i++)
+		checksum += *i;
+	return checksum;
+}
+
+struct smbios_entry_point_struct *smbios_find_entry_point (void *base)
+{
+	struct smbios_entry_point_struct *entry_point = 0;
+	unsigned int *temp;
+
+	/* search for the magic dword on paragraph boundaries */
+	for (temp = base;
+		!entry_point && temp < (unsigned int *) base + BIOS_MAP_LENGTH;
+		temp += 4) {
+
+		if (*temp == SMBIOS_MAGIC_DWORD) {
+			/* check if entry point valid (build checksum) */
+			if (!(smbios_check_entry_point (temp))) {
+				entry_point = (struct
+					smbios_entry_point_struct *) temp;
+
+				// fix display of Bios version string
+				// SMBios version is known as 2.1, 2.2,
+				// 2.3 and 2.3.1, never as 2.01 (JB)
+				SM_BIOS_DEBUG("SM-BIOS V%d.%d entry point ",
+					entry_point->major_version,
+					entry_point->minor_version);
+				SM_BIOS_DEBUG("found at 0x%x\n",
+					(unsigned int) temp);
+			}
+		}
+	}
+	return entry_point;
+}
+
+int smbios_check_caracalla_config(unsigned char *config0,
+	unsigned char *config1)
+{
+
+	int i;
+	int result = -1;
+	unsigned char *p;
+
+	smbios_base = ioremap(BIOS_START_ADDRESS, BIOS_MAP_LENGTH);
+
+	if (!smbios_base) {
+		SM_BIOS_DEBUG("ioremap() for entry point failed\n");
+		result = -ENXIO;
+		return result;
+	}
+
+	smbios_entry_point = smbios_find_entry_point (smbios_base);
+	if (!smbios_entry_point) {
+		SM_BIOS_DEBUG("SM-BIOS entry point not found\n");
+		iounmap(smbios_base);
+		result = -ENXIO;
+		return result;
+	}
+	/*
+	 *	for SM-BIOS:
+	 *	check if Pointer to DMI structures exist.
+	 *	intermediate_string (_DMI_) is not '\0' terminated,
+	 *	so strncmp() with sizeof(DMI_STRING) - 1 is needed.
+	 */
+	if (smbios_entry_point) {
+		if (strncmp((char *) &(smbios_entry_point->intermediate_string),
+			DMI_STRING, sizeof(DMI_STRING) - 1))
+			SM_BIOS_DEBUG("Pointer to DMI struct not found!\n");
+	}
+
+	/*
+	 *	map the SM-BIOS structures physical address range.
+	 *	the 'real' smbios_structures_base contains the starting
+	 *	address, where the instances of dmi structures are located.
+	 */
+	if (smbios_entry_point)	{
+		smbios_structures_base =
+			ioremap(smbios_entry_point->struct_table_address,
+			(unsigned long)smbios_entry_point->struct_table_length);
+		if (!(smbios_structures_base)) {
+			SM_BIOS_DEBUG("ioremap() for struct table failed\n");
+			iounmap(smbios_base);
+			result = -ENXIO;
+		return result;
+		}
+	}
+	SM_BIOS_DEBUG("smbios_structures_base to 0x%p length %d ",
+			smbios_structures_base,
+			smbios_entry_point->struct_table_length);
+	SM_BIOS_DEBUG(" no_of_structures:%d\n",
+			smbios_entry_point->no_of_structures);
+
+	p = (unsigned char *)smbios_structures_base;
+	for (i = 0; i < smbios_entry_point->struct_table_length; i++) {
+		if ((p[i] == 0xc0) && (p[i+1] == 0x06)) {
+			SM_BIOS_DEBUG("Found 0xc0 at offset:%d 0x%02x 0x%02x ",
+				i, p[i], p[i+1]);
+			SM_BIOS_DEBUG("0x%02x 0x%02x 0x%02x 0x%02x\n\t",
+				p[i+2], p[i+3], p[i+4], p[i+5]);
+		*config0 = p[i+4];
+		*config1 = p[i+5];
+		result = 0;
+		break;
+		}
+	}
+	iounmap(smbios_structures_base);
+	iounmap(smbios_base);
+	return result;
+}
+
+/*
+ * Look up an XRUSB structure by index. If found and not disconnected,
+ * increment its refcount and return it with its mutex held.
+ */
+
+static struct xrusb *xrusb_get_by_index(unsigned int index)
+{
+	struct xrusb *xrusb;
+
+	mutex_lock(&xrusb_table_lock);
+	xrusb = xrusb_table[index];
+	if (xrusb) {
+		mutex_lock(&xrusb->mutex);
+		if (xrusb->disconnected) {
+			mutex_unlock(&xrusb->mutex);
+			xrusb = NULL;
+		} else {
+			tty_port_get(&xrusb->port);
+			mutex_unlock(&xrusb->mutex);
+		}
+	}
+	mutex_unlock(&xrusb_table_lock);
+	return xrusb;
+}
+
+/*
+ * Try to find an available minor number and if found, associate it with
+ * 'xrusb'.
+ */
+static int xrusb_alloc_minor(struct xrusb *xrusb)
+{
+	int minor;
+
+	mutex_lock(&xrusb_table_lock);
+	for (minor = 0; minor < XRUSB_TTY_MINORS; minor++) {
+		if (!xrusb_table[minor]) {
+			xrusb_table[minor] = xrusb;
+			break;
+		}
+	}
+	mutex_unlock(&xrusb_table_lock);
+
+	return minor;
+}
+
+/* Release the minor number associated with 'xrusb'.  */
+static void xrusb_release_minor(struct xrusb *xrusb)
+{
+	mutex_lock(&xrusb_table_lock);
+	xrusb_table[xrusb->minor] = NULL;
+	mutex_unlock(&xrusb_table_lock);
+}
+
+/*
+ * Functions for XRUSB control messages.
+ */
+
+static int xrusb_ctrl_msg(struct xrusb *xrusb,
+		int request, int value,	void *buf, int len)
+{
+	int rv = usb_control_msg(xrusb->dev,
+		usb_sndctrlpipe(xrusb->dev, 0),
+		request,
+		USB_RT_XRUSB,
+		value,
+		xrusb->control->altsetting[0].desc.bInterfaceNumber,
+		buf,
+		len,
+		5000);
+	dev_dbg(&xrusb->control->dev,
+		"%s - rq 0x%02x, val %#x, len %#x, result %d\n",
+		__func__, request, value, len, rv);
+	return rv < 0 ? rv : 0;
+}
+
+int xrusb_set_reg(struct xrusb *xrusb, int regnum, int value)
+{
+	int result;
+	int channel = 0;
+	int XR2280xaddr = XR2280x_FUNC_MGR_OFFSET + regnum;
+
+	if ((xrusb->DeviceProduct & 0xfff0) == 0x1400) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_sndctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_SET_XR2280X,			// request
+			USB_DIR_OUT | USB_TYPE_VENDOR,		// request_type
+			value,					// request value
+			XR2280xaddr,				// index
+			NULL,					// data
+			0,					// size
+			5000);					// timeout
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1420) {
+		channel = (xrusb->channel - 4)*2;
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_sndctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_SET_XR21B142X,			// request
+			USB_DIR_OUT | USB_TYPE_VENDOR | 1,	// request_type
+			value,					// request value
+			regnum | (channel << 8),		// index
+			NULL,					// data
+			0,					// size
+			5000);					// timeout
+	} else if (xrusb->DeviceProduct == 0x1411) {
+		result = usb_control_msg(xrusb->dev,	// usb device
+			usb_sndctrlpipe(xrusb->dev, 0),	// endpoint pipe
+			XRUSB_SET_XR21B1411,			// request
+			USB_DIR_OUT | USB_TYPE_VENDOR,		// request_type
+			value,					// request value
+			regnum,					// index
+			NULL,					// data
+			0,					// size
+			5000);					// timeout
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1410) {
+		if (xrusb->channel)
+			channel = xrusb->channel - 1;
+		result = usb_control_msg(xrusb->dev,	// usb device
+			usb_sndctrlpipe(xrusb->dev, 0),	// endpoint pipe
+			XRUSB_SET_XR21V141X,			// request
+			USB_DIR_OUT | USB_TYPE_VENDOR,		// request_type
+			value,					// request value
+			regnum | (channel << 8),		// index
+			NULL,					// data
+			0,					// size
+			5000);					// timeout
+	} else
+		result = -1;
+
+	if (result < 0)
+		dev_err(&xrusb->control->dev,
+			"%s Error:%d\n", __func__, result);
+
+	return result;
+}
+
+/* Write to UART Block registers in XR21V141x */
+int xrusb_set_block_reg(struct xrusb *xrusb, int block, int regnum, int value)
+{
+	int result;
+
+	result = usb_control_msg(xrusb->dev,		// usb device
+		usb_sndctrlpipe(xrusb->dev, 0),		// endpoint pipe
+		XRUSB_SET_XR21V141X,			// request
+		USB_DIR_OUT | USB_TYPE_VENDOR,		// request_type
+		value,					// request value
+		regnum | (block << 8),			// index
+		NULL,					// data
+		0,					// size
+		5000);					// timeout
+
+	if (result < 0)
+		dev_err(&xrusb->control->dev, "%s Error:%d\n",
+			__func__, result);
+
+	return result;
+}
+
+int xrusb_get_reg(struct xrusb *xrusb, int regnum, short *value)
+{
+	int result;
+	int channel = 0;
+	int XR2280xaddr = XR2280x_FUNC_MGR_OFFSET + regnum;
+	void *dmadata = kmalloc(2, GFP_KERNEL);
+
+	if (!dmadata)
+		return -ENOMEM;
+
+	if ((xrusb->DeviceProduct & 0xfff0) == 0x1400) {
+		result = usb_control_msg(xrusb->dev,	// usb device
+			usb_rcvctrlpipe(xrusb->dev, 0),	// endpoint pipe
+			XRUSB_GET_XR2280X,			// request
+			USB_DIR_IN | USB_TYPE_VENDOR,		// request_type
+			0,					// request value
+			XR2280xaddr,				// index
+			dmadata,				// data
+			2,					// size
+			5000);					// timeout
+		memcpy(value, dmadata, 2);
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1420) {
+		channel = (xrusb->channel - 4)*2;
+		result = usb_control_msg(xrusb->dev,	// usb device
+			usb_rcvctrlpipe(xrusb->dev, 0),	// endpoint pipe
+			XRUSB_GET_XR21B142X,			// request
+			USB_DIR_IN | USB_TYPE_VENDOR | 1,	// request_type
+			0,					// request value
+			regnum | (channel << 8),		// index
+			dmadata,				// data
+			2,					// size
+			5000);					// timeout
+			memcpy(value, dmadata, 2);
+	} else if (xrusb->DeviceProduct == 0x1411) {
+		result = usb_control_msg(xrusb->dev,	// usb device
+			usb_rcvctrlpipe(xrusb->dev, 0),	// endpoint pipe
+			XRUSB_GET_XR21B1411,			// request
+			USB_DIR_IN | USB_TYPE_VENDOR,		// request_type
+			0,					// request value
+			regnum,					// index
+			dmadata,				// data
+			2,					// size
+			5000);					// timeout
+			memcpy(value, dmadata, 2);
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1410) {
+		if (xrusb->channel)
+			channel = xrusb->channel - 1;
+		result = usb_control_msg(xrusb->dev,	// usb device
+			usb_rcvctrlpipe(xrusb->dev, 0),	// endpoint pipe
+			XRUSB_GET_XR21V141X,			// request
+			USB_DIR_IN | USB_TYPE_VENDOR,		// request_type
+			0,					// request value
+			regnum | (channel << 8),		// index
+			dmadata,				// data
+			1,					// size
+			5000);					// timeout
+			memcpy(value, dmadata, 1);
+	} else
+		result = -1;
+
+	if (result < 0) {
+		dev_err(&xrusb->control->dev,
+			"%s channel:%d Reg 0x%x	Error:%d\n",
+			__func__, channel, regnum, result);
+	}
+	kfree(dmadata);
+	return result;
+}
+
+/* Read from UART Block registers in XR21V141x */
+int xrusb_get_block_reg(struct xrusb *xrusb, int block,
+	int regnum, short *value)
+{
+	int result;
+	void *dmadata = kmalloc(2, GFP_KERNEL);
+
+	if (!dmadata)
+		return -ENOMEM;
+
+	result = usb_control_msg(xrusb->dev,		// usb device
+		usb_rcvctrlpipe(xrusb->dev, 0),		// endpoint pipe
+		XRUSB_GET_XR21V141X,			// request
+		USB_DIR_IN | USB_TYPE_VENDOR,		// request_type
+		0,					// request value
+		regnum | (block << 8),			// index
+		dmadata,				// data
+		1,					// size
+		5000);					// timeout
+	memcpy(value, dmadata, 1);
+
+	if (result < 0) {
+		dev_err(&xrusb->control->dev, "%s Error:%d\n",
+			__func__, result);
+	}
+	kfree(dmadata);
+	return result;
+}
+
+int check_portparamfile(struct xrusb *xrusb)
+{
+	char filename[256];
+	int level_num, port_num, channel_num, config_num;
+
+	memset(filename, 0, sizeof(filename));
+
+	level_num = xrusb->dev->level;
+	port_num = xrusb->dev->portnum;
+	channel_num = xrusb->channel % 4;
+
+	// config_num is a unique number identifying USB level, USB port
+	// and UART/serial port channel. Driver assumes that devices are
+	// plugged into a single USB hub at each level.
+	config_num = (level_num * 100) + (port_num * 10) + channel_num;
+
+	sprintf(filename, "/etc/port_config%d", config_num);
+
+	port_param_file[config_num] =
+		filp_open(filename, O_RDWR | O_LARGEFILE, 0600);
+	if (IS_ERR(port_param_file[config_num])) {
+		xrusb->have_txcvr_config = 0;
+		return 0;
+	}
+	filp_close(port_param_file[config_num], NULL);
+	xrusb->have_txcvr_config = 1;
+	return 1;
+}
+
+int load_portparamfile(struct xrusb *xrusb)
+{
+	int length;
+	loff_t offset = 0;
+	mm_segment_t old_fs;
+	char filename[256];
+	int level_num, port_num, channel_num, config_num;
+
+	memset(filename, 0, sizeof(filename));
+
+	level_num = xrusb->dev->level;
+	port_num = xrusb->dev->portnum;
+	channel_num = xrusb->channel % 4;
+
+	// config_num is a unique number identifying USB level, USB port
+	// and UART/serial port channel. Driver assumes that devices are
+	// plugged into a single USB hub at each level.
+	config_num = (level_num * 100) + (port_num * 10) + channel_num;
+
+	sprintf(filename, "/etc/port_config%d", config_num);
+
+	port_param_file[config_num] =
+		filp_open(filename, O_RDWR | O_LARGEFILE, 0600);
+	if (IS_ERR(port_param_file[config_num])) {
+		dev_err(&xrusb->control->dev, "filp_open failed\n");
+		return PTR_ERR(port_param_file[config_num]);
+	}
+	old_fs = get_fs();
+	set_fs(KERNEL_DS);
+	length = vfs_read(port_param_file[config_num],
+		(char *) &(xrusb->port_setting),
+		sizeof(struct xrusb_setting),
+		&offset);
+	//dev_err(&xrusb->control->dev, "vfs_read :%d\n", length);
+	set_fs(old_fs);
+	filp_close(port_param_file[config_num], NULL);
+	return 0;
+}
+
+int update_portparamfile(struct xrusb *xrusb)
+{
+	int length;
+	loff_t offset = 0;
+	mm_segment_t old_fs;
+	char filename[256];
+	int level_num, port_num, channel_num, config_num;
+
+	memset(filename, 0, sizeof(filename));
+
+	level_num = xrusb->dev->level;
+	port_num = xrusb->dev->portnum;
+	channel_num = xrusb->channel % 4;
+
+	// config_num is a unique number identifying USB level, USB port
+	// and UART/serial port channel. Driver assumes that devices are
+	// plugged into a single USB hub at each level.
+	config_num = (level_num * 100) + (port_num * 10) + channel_num;
+
+	sprintf(filename, "/etc/port_config%d", config_num);
+	port_param_file[config_num] =
+		filp_open(filename, O_CREAT | O_RDWR | O_LARGEFILE, 0600);
+	if (IS_ERR(port_param_file[config_num])) {
+		dev_err(&xrusb->control->dev, "filp_open failed\n");
+		return PTR_ERR(port_param_file[config_num]);
+	}
+	old_fs = get_fs();
+	set_fs(KERNEL_DS);
+	length = vfs_write(port_param_file[config_num],
+		(char *) &(xrusb->port_setting),
+		sizeof(struct xrusb_setting),
+		&offset);
+	if (length != sizeof(struct xrusb_setting)) {
+		dev_err(&xrusb->control->dev, "vfs_write failed :%d\n", length);
+		set_fs(old_fs);
+		filp_close(port_param_file[config_num], NULL);
+		return -1;
+	}
+	//dev_err(&xrusb->control->dev, "successful vfs_write :%d\n", length);
+	set_fs(old_fs);
+	filp_close(port_param_file[config_num], NULL);
+	xrusb->have_txcvr_config = 1;
+	return 0;
+}
+
+static int set_txcvr_mode(struct xrusb *xrusb, unsigned int mode)
+{
+	unsigned short tmp_value;
+	int rv;
+	unsigned int mode1_mask, mode0_mask;
+
+	mode1_mask = (1<<(xrusb->port_setting.txcvr_mode1_pin));
+	mode0_mask = (1<<(xrusb->port_setting.txcvr_mode0_pin));
+	tmp_value = 0;
+
+	dev_err(&xrusb->control->dev, "Ch %d: ", (xrusb->channel % 4));
+
+	switch (mode) {
+	case 0:
+		dev_err(&xrusb->control->dev, "Transceiver Mode = LOOPBACK\n");
+		tmp_value &= ~mode0_mask;
+		tmp_value &= ~mode1_mask;
+		break;
+	case 1:
+		dev_err(&xrusb->control->dev, "Transceiver Mode = RS232\n");
+		tmp_value |= mode0_mask;
+		tmp_value &= ~mode1_mask;
+		break;
+	case 2:
+		dev_err(&xrusb->control->dev,
+			"Transceiver Mode = RS485 Half-Duplex\n");
+		tmp_value &= ~mode0_mask;
+		tmp_value |= mode1_mask;
+		break;
+	case 3:
+		dev_err(&xrusb->control->dev,
+			"Transceiver Mode = RS485/RS422 Full-Duplex\n");
+		tmp_value |= mode0_mask;
+		tmp_value |= mode1_mask;
+		break;
+	default:
+		dev_err(&xrusb->control->dev,
+			"Invalid Parameter. No mode change.\n");
+		break;
+	}
+
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set,
+			(int)tmp_value);
+	if (rv < 0)
+		return -EFAULT;
+
+	tmp_value = ~tmp_value;
+	tmp_value &= (mode1_mask | mode0_mask);
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr,
+			(int)tmp_value);
+	if (rv < 0)
+		return -EFAULT;
+	return rv;
+}
+
+static int set_rs485_pin(struct xrusb *xrusb, unsigned int option)
+{
+	short reg_value;
+	int rv;
+
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, &reg_value);
+	if (rv < 0)
+		return -EFAULT;
+
+	//dev_err(&xrusb->control->dev, "set_rs485_pin option = %d\n", option);
+
+	switch (option) {
+	case 0:	// disable RS485 pin
+		reg_value &= (GPIO9_RXT | GPIO8_TXT);
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode,
+				(int)reg_value);
+		if (rv < 0)
+			return -EFAULT;
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_flow,
+			UART_FLOW_MODE_NONE);
+		if (rv < 0)
+			return -EFAULT;
+		break;
+	case 5: // enable GPIO5/RTS as active-high RS-485 control output
+		reg_value = UART_GPIO_MODE_DIR_RTS_HI;
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode,
+				(int)reg_value);
+		if (rv < 0)
+			return -EFAULT;
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_flow,
+			UART_FLOW_MODE_NONE);
+		if (rv < 0)
+			return -EFAULT;
+		break;
+	case 7: // Enable GPIO7/XEN as active-high RS-485 control output
+		reg_value = UART_GPIO_MODE_DIR_XEN_HI;
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode,
+				(int)reg_value);
+		if (rv < 0)
+			return -EFAULT;
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_flow,
+			UART_FLOW_MODE_NONE);
+		if (rv < 0)
+			return -EFAULT;
+		break;
+	default: // Invalid option
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+static int set_txcvr_term_mode(struct xrusb *xrusb, unsigned int option)
+{
+	unsigned int term_mask;
+	int rv;
+
+	term_mask =  (1<<(xrusb->port_setting.txcvr_term_pin));
+
+	if (option) { //Set GPIO pin High
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set,
+				term_mask);
+		if (rv < 0)
+			return -EFAULT;
+	} else { //Set GPIO pin Low
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr,
+				term_mask);
+		if (rv < 0)
+			return -EFAULT;
+	}
+	return 0;
+}
+static int set_txcvr_slew_mode(struct xrusb *xrusb, unsigned int option)
+{
+	int rv;
+	unsigned int slew_mask;
+
+	slew_mask =  (1<<(xrusb->port_setting.txcvr_slew_pin));
+	if (option) { //Slew pin is high
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set,
+			slew_mask);
+		if (rv < 0)
+			return -EFAULT;
+	} else { // Slew pin is low
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr,
+			slew_mask);
+		if (rv < 0)
+			return -EFAULT;
+	}
+	return 0;
+}
+
+int init_portparam(struct xrusb *xrusb)
+{
+	int rv;
+
+	if (xrusb->port_setting.txcvr_mode < 2)
+		rv = set_rs485_pin(xrusb, 0);
+	else
+		rv = set_rs485_pin(xrusb, xrusb->port_setting.txcvr_dir_pin);
+	if (rv < 0)
+		return -EFAULT;
+
+	rv = set_txcvr_mode(xrusb, xrusb->port_setting.txcvr_mode);
+	if (rv < 0)
+		return -EFAULT;
+
+	rv = set_txcvr_term_mode(xrusb,
+			xrusb->port_setting.term_mode);
+	if (rv < 0)
+		return -EFAULT;
+
+	rv = set_txcvr_slew_mode(xrusb, xrusb->port_setting.slew_mode);
+	if (rv < 0)
+		return -EFAULT;
+
+	return rv;
+}
+
+int xrusb_gpio_dir_out(struct xrusb *xrusb, int gpio_mask)
+{
+	int rv = 0;
+	short reg_value;
+
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_dir, &reg_value);
+	if (rv < 0)
+		return -EFAULT;
+
+	reg_value |= gpio_mask;
+
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, reg_value);
+	if (rv < 0)
+		return -EFAULT;
+
+	return rv;
+}
+
+int xrusb_gpio_dir_in(struct xrusb *xrusb, int gpio_mask)
+{
+	int rv = 0;
+	short reg_value;
+
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_dir, &reg_value);
+	if (rv < 0)
+		return -EFAULT;
+
+	gpio_mask = ~gpio_mask;
+	reg_value &= gpio_mask;
+
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, reg_value);
+	if (rv < 0)
+		return -EFAULT;
+
+	return rv;
+}
+
+struct xr21v141x_baud_rate {
+	unsigned int tx;
+	unsigned int rx0;
+	unsigned int rx1; };
+
+static struct xr21v141x_baud_rate xr21v141x_baud_rates[] = {
+	{ 0x000, 0x000, 0x000 },
+	{ 0x000, 0x000, 0x000 },
+	{ 0x100, 0x000, 0x100 },
+	{ 0x020, 0x400, 0x020 },
+	{ 0x010, 0x100, 0x010 },
+	{ 0x208, 0x040, 0x208 },
+	{ 0x104, 0x820, 0x108 },
+	{ 0x844, 0x210, 0x884 },
+	{ 0x444, 0x110, 0x444 },
+	{ 0x122, 0x888, 0x224 },
+	{ 0x912, 0x448, 0x924 },
+	{ 0x492, 0x248, 0x492 },
+	{ 0x252, 0x928, 0x292 },
+	{ 0X94A, 0X4A4, 0XA52 },
+	{ 0X52A, 0XAA4, 0X54A },
+	{ 0XAAA, 0x954, 0X4AA },
+	{ 0XAAA, 0x554, 0XAAA },
+	{ 0x555, 0XAD4, 0X5AA },
+	{ 0XB55, 0XAB4, 0X55A },
+	{ 0X6B5, 0X5AC, 0XB56 },
+	{ 0X5B5, 0XD6C, 0X6D6 },
+	{ 0XB6D, 0XB6A, 0XDB6 },
+	{ 0X76D, 0X6DA, 0XBB6 },
+	{ 0XEDD, 0XDDA, 0X76E },
+	{ 0XDDD, 0XBBA, 0XEEE },
+	{ 0X7BB, 0XF7A, 0XDDE },
+	{ 0XF7B, 0XEF6, 0X7DE },
+	{ 0XDF7, 0XBF6, 0XF7E },
+	{ 0X7F7, 0XFEE, 0XEFE },
+	{ 0XFDF, 0XFBE, 0X7FE },
+	{ 0XF7F, 0XEFE, 0XFFE },
+	{ 0XFFF, 0XFFE, 0XFFD },
+};
+
+
+static int xr21v141x_set_baud_rate(struct xrusb *xrusb,	unsigned int rate)
+{
+	unsigned int divisor = 48000000 / rate;
+	unsigned int i = ((32 * 48000000) / rate) & 0x1f;
+	unsigned int tx_mask = xr21v141x_baud_rates[i].tx;
+	unsigned int rx_mask = (divisor & 1) ?
+		xr21v141x_baud_rates[i].rx1 :
+		xr21v141x_baud_rates[i].rx0;
+
+	xrusb_set_reg(xrusb, XR21V141X_CLOCK_DIVISOR_0, (divisor >>  0) & 0xff);
+	xrusb_set_reg(xrusb, XR21V141X_CLOCK_DIVISOR_1, (divisor >>  8) & 0xff);
+	xrusb_set_reg(xrusb, XR21V141X_CLOCK_DIVISOR_2, (divisor >> 16) & 0xff);
+	xrusb_set_reg(xrusb, XR21V141X_TX_CLOCK_MASK_0,	(tx_mask >>  0) & 0xff);
+	xrusb_set_reg(xrusb, XR21V141X_TX_CLOCK_MASK_1, (tx_mask >>  8) & 0xff);
+	xrusb_set_reg(xrusb, XR21V141X_RX_CLOCK_MASK_0, (rx_mask >>  0) & 0xff);
+	xrusb_set_reg(xrusb, XR21V141X_RX_CLOCK_MASK_1, (rx_mask >>  8) & 0xff);
+
+	return 0;
+}
+
+/* devices aren't required to support these requests.
+ * the cdc xrusb descriptor tells whether they do...
+ */
+int xrusb_set_control(struct xrusb *xrusb, unsigned int control)
+{
+	int rv = 0;
+
+	// Use custom vendor request for XR21V1410/12/14
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		if (control & XRUSB_CTRL_DTR)
+			xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr,
+				GPIO3_DTR);
+		else
+			xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set,
+				GPIO3_DTR);
+
+		if (control & XRUSB_CTRL_RTS)
+			xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr,
+				GPIO5_RTS);
+		else
+			xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set,
+				GPIO5_RTS);
+	}
+	// Use CDC command for XR21B14xx and XR2280x
+	else
+		rv = xrusb_ctrl_msg(xrusb,
+			USB_CDC_REQ_SET_CONTROL_LINE_STATE,
+			control,
+			NULL,
+			0);
+
+	return rv;
+}
+
+int xrusb_set_line(struct xrusb *xrusb,	struct usb_cdc_line_coding *line)
+{
+	int rv = 0;
+	unsigned int data_size, data_parity, data_stop, format;
+
+	// Use custom vendor request for XR21V1410/12/14
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		xr21v141x_set_baud_rate(xrusb, line->dwDTERate);
+		data_size = line->bDataBits;
+		data_parity = line->bParityType;
+		data_stop = line->bCharFormat;
+		format = data_size | (data_parity << 4) | (data_stop << 7);
+		xrusb_set_reg(xrusb, xrusb->reg_map.uart_format, format);
+	}
+	// Use CDC command for XR21B14xx and XR2280x
+	else
+		rv = xrusb_ctrl_msg(xrusb,
+			USB_CDC_REQ_SET_LINE_CODING,
+			0,
+			line,
+			sizeof *(line));
+
+	return rv;
+}
+
+int xrusb_set_flow_mode(struct xrusb *xrusb,
+		struct tty_struct *tty, unsigned int cflag)
+{
+	unsigned int flow;
+	unsigned int gpio_mode;
+	short gpio_mode_reg;
+
+	xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, &gpio_mode_reg);
+	gpio_mode = gpio_mode_reg;
+	if (cflag & CRTSCTS) {
+		flow = UART_FLOW_MODE_HW;
+		gpio_mode |= UART_GPIO_MODE_SEL_RTS_CTS;
+	} else if (I_IXOFF(tty) || I_IXON(tty)) {
+		unsigned char start_char = START_CHAR(tty);
+		unsigned char stop_char = STOP_CHAR(tty);
+
+		flow = UART_FLOW_MODE_SW;
+		gpio_mode |= UART_GPIO_MODE_SEL_GPIO;
+
+		xrusb_set_reg(xrusb, xrusb->reg_map.uart_xon_char, start_char);
+		xrusb_set_reg(xrusb, xrusb->reg_map.uart_xoff_char, stop_char);
+	} else {
+		flow = UART_FLOW_MODE_NONE;
+		gpio_mode |= UART_GPIO_MODE_SEL_GPIO;
+	}
+
+	// Mode configured in BIOS for Caracalla
+	// Do nothing for set_flow_mode
+	if (xrusb->found_smbios_caracalla_config)
+		return 0;
+
+	// If mode configured as RS485 or RS422 by application
+	// and stored in /etc/port_config file
+	if (xrusb->have_txcvr_config) {
+		xrusb_gpio_dir_out(xrusb,
+			GPIO9_RXT | GPIO8_TXT | GPIO7_XEN | GPIO6_CLK);
+		gpio_mode &= 0x00FF; // disable TXT and RXT
+	}
+	if (xrusb->port_setting.txcvr_mode < 2) {
+		xrusb_set_reg(xrusb, xrusb->reg_map.uart_flow, flow);
+		xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, gpio_mode);
+	}
+	return 0;
+}
+
+int xrusb_send_break(struct xrusb *xrusb, int state)
+{
+	int rv = 0;
+
+	// Use custom vendor request for XR21V1410/12/14
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		if (state)
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_tx_break,
+				0xffff);
+		else
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_tx_break,
+				0);
+	}
+	// Use CDC command for XR21B14xx and XR2280x
+	else
+		rv = xrusb_ctrl_msg(xrusb,
+			USB_CDC_REQ_SEND_BREAK,
+			state,
+			NULL,
+			0);
+
+	return rv;
+}
+
+int xrusb_enable(struct xrusb *xrusb)
+{
+	int rv = 0;
+	int channel = xrusb->channel;
+
+	if (channel)
+		channel--;
+
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		rv = xrusb_set_block_reg(xrusb,
+			XR21V141x_URM_REG_BLOCK,
+			XR21V141x_URM_FIFO_ENABLE_REG + channel,
+			XR21V141x_URM_ENABLE_TX_FIFO);
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_enable,
+			UART_ENABLE_TX | UART_ENABLE_RX);
+		rv = xrusb_set_block_reg(xrusb,
+			XR21V141x_URM_REG_BLOCK,
+			XR21V141x_URM_FIFO_ENABLE_REG + channel,
+			XR21V141x_URM_ENABLE_TX_FIFO |
+			XR21V141x_URM_ENABLE_RX_FIFO);
+	} else
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_enable,
+			UART_ENABLE_TX | UART_ENABLE_RX);
+
+	return rv;
+}
+
+int xrusb_disable(struct xrusb *xrusb)
+{
+	int rv = 0;
+	int channel = xrusb->channel;
+
+	if (channel)
+		channel--;
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_enable, 0);
+
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		rv = xrusb_set_block_reg(xrusb,
+			XR21V141x_URM_REG_BLOCK,
+			XR21V141x_URM_FIFO_ENABLE_REG + channel, 0);
+	}
+
+	return rv;
+}
+
+int xrusb_fifo_reset(struct xrusb *xrusb)
+{
+	int rv = 0;
+	int channel = xrusb->channel;
+
+	if (channel)
+		channel--;
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		rv = xrusb_set_block_reg(xrusb,
+			XR21V141x_URM_REG_BLOCK,
+			XR21V141x_URM_RX_FIFO_RESET + channel,
+			0xff);
+		rv |= xrusb_set_block_reg(xrusb,
+			XR21V141x_URM_REG_BLOCK,
+			XR21V141x_URM_TX_FIFO_RESET + channel,
+			0xff);
+	} else
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_enable,
+			UART_ENABLE_TX | UART_ENABLE_RX);
+
+	return rv;
+}
+
+int xrusb_set_loopback(struct xrusb *xrusb, int channel)
+{
+	int rv = 0;
+
+	xrusb_disable(xrusb);
+
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		switch (channel) {
+		case 0:
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_loopback,
+				XR21V141x_LOOP_ENABLE_A);
+			break;
+		case 1:
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_loopback,
+				XR21V141x_LOOP_ENABLE_B);
+			break;
+		case 2:
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_loopback,
+				XR21V141x_LOOP_ENABLE_C);
+			break;
+		case 3:
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_loopback,
+				XR21V141x_LOOP_ENABLE_D);
+			break;
+		default:
+			break;
+		}
+	} else
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_loopback,
+			LOOP_ENABLE);
+
+	xrusb_enable(xrusb);
+	return rv;
+}
+
+int xrusb_set_wide_mode(struct xrusb *xrusb, int wide_mode)
+{
+	int rv = 0;
+	int channel = xrusb->channel;
+
+	xrusb_disable(xrusb);
+
+	if ((xrusb->DeviceProduct & 0xFFF0) == 0x1400) {
+		xrusb_set_reg(xrusb, XR2280X_TX_WIDE_MODE_REG, wide_mode);
+		xrusb_set_reg(xrusb, XR2280X_RX_WIDE_MODE_REG, wide_mode);
+	} else if ((xrusb->DeviceProduct & 0xFFF0) == 0x1420) {
+		xrusb_set_reg(xrusb, XR21B142X_TX_WIDE_MODE_REG, wide_mode);
+		xrusb_set_reg(xrusb, XR21B142X_RX_WIDE_MODE_REG, wide_mode);
+	} else if (xrusb->DeviceProduct == 0x1411) {
+		xrusb_set_reg(xrusb, XR21B1411_WIDE_MODE_REG, wide_mode);
+	} else if ((xrusb->DeviceProduct & 0xFFF0) == 0x1410) {
+		if (channel)
+			channel--;
+		xrusb_set_block_reg(xrusb,
+			XR21V141x_UART_CUSTOM_BLOCK,
+			channel*8 + XR21V141X_WIDE_MODE_REG,
+			wide_mode);
+	}
+
+	xrusb_enable(xrusb);
+	return rv;
+}
+
+int xrusb_disable_txt_rxt(struct xrusb *xrusb)
+{
+	int rv = 0;
+	short reg_value;
+
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, &reg_value);
+	if (rv < 0)
+		return -EFAULT;
+
+	reg_value &= 0xFF;
+
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, reg_value);
+	if (rv < 0)
+		return -EFAULT;
+
+	return rv;
+}
+
+static int xrusb_tiocmget(struct xrusb *xrusb)
+
+{
+	short data;
+	int result;
+
+	result = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_status, &data);
+
+	if (result)
+		return ((data & 0x8) ? 0 : TIOCM_DTR) |
+			((data & 0x20) ? 0 : TIOCM_RTS) |
+			((data & 0x4) ? 0 : TIOCM_DSR) |
+			((data & 0x1) ? 0 : TIOCM_RI) |
+			((data & 0x2) ? 0 : TIOCM_CD) |
+			((data & 0x10) ? 0 : TIOCM_CTS);
+	else
+		return -EFAULT;
+}
+
+static int xrusb_tiocmset(struct xrusb *xrusb,
+		unsigned int set, unsigned int clear)
+{
+
+	unsigned int newctrl = 0;
+
+	newctrl = xrusb->ctrlout;
+	set = (set & TIOCM_DTR ? XRUSB_CTRL_DTR : 0) |
+		(set & TIOCM_RTS ? XRUSB_CTRL_RTS : 0);
+	clear = (clear & TIOCM_DTR ? XRUSB_CTRL_DTR : 0) |
+		(clear & TIOCM_RTS ? XRUSB_CTRL_RTS : 0);
+	newctrl = (newctrl & ~clear) | set;
+
+	if (xrusb->ctrlout == newctrl)
+		return 0;
+
+	xrusb->ctrlout = newctrl;
+
+	if (newctrl & XRUSB_CTRL_DTR)
+		xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_clr,
+			GPIO3_DTR);
+	else
+		xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_set,
+			GPIO3_DTR);
+
+	if (newctrl & XRUSB_CTRL_RTS)
+		xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_clr,
+			GPIO5_RTS);
+	else
+		xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_set,
+			GPIO5_RTS);
+
+	return 0;
+}
+
+static void init_xr2280x_reg_map(void)
+{
+	xr2280x_reg_map.uart_enable = 0x40;
+	xr2280x_reg_map.uart_flow = 0x46;
+	xr2280x_reg_map.uart_xon_char = 0x47;
+	xr2280x_reg_map.uart_xoff_char = 0x48;
+	xr2280x_reg_map.uart_tx_break = 0x4a;
+	xr2280x_reg_map.uart_rs485_delay = 0x4b;
+	xr2280x_reg_map.uart_gpio_mode = 0x4c;
+	xr2280x_reg_map.uart_gpio_dir = 0x4d;
+	xr2280x_reg_map.uart_gpio_set = 0x4e;
+	xr2280x_reg_map.uart_gpio_clr = 0x4f;
+	xr2280x_reg_map.uart_gpio_status = 0x50;
+	xr2280x_reg_map.uart_gpio_int_mask = 0x51;
+	xr2280x_reg_map.uart_customized_int = 0x52;
+	xr2280x_reg_map.uart_gpio_pull_up_enable = 0x54;
+	xr2280x_reg_map.uart_gpio_pull_down_enable = 0x55;
+	xr2280x_reg_map.uart_loopback = 0x56;
+	xr2280x_reg_map.uart_low_latency = 0x66;
+	xr2280x_reg_map.uart_custom_driver = 0x81;
+}
+
+static void init_xr21b1411_reg_map(void)
+{
+	xr21b1411_reg_map.uart_enable = 0xc00;
+	xr21b1411_reg_map.uart_flow = 0xc06;
+	xr21b1411_reg_map.uart_xon_char = 0xc07;
+	xr21b1411_reg_map.uart_xoff_char = 0xc08;
+	xr21b1411_reg_map.uart_tx_break = 0xc0a;
+	xr21b1411_reg_map.uart_gpio_mode = 0xc0c;
+	xr21b1411_reg_map.uart_gpio_dir = 0xc0d;
+	xr21b1411_reg_map.uart_gpio_set = 0xc0e;
+	xr21b1411_reg_map.uart_gpio_clr = 0xc0f;
+	xr21b1411_reg_map.uart_gpio_status = 0xc10;
+	xr21b1411_reg_map.uart_gpio_int_mask = 0xc11;
+	xr21b1411_reg_map.uart_customized_int = 0xc12;
+	xr21b1411_reg_map.uart_gpio_pull_up_enable = 0xc14;
+	xr21b1411_reg_map.uart_gpio_pull_down_enable = 0xc15;
+	xr21b1411_reg_map.uart_loopback = 0xc16;
+	xr21b1411_reg_map.uart_low_latency = 0xcc2;
+	xr21b1411_reg_map.uart_custom_driver = 0x20d;
+}
+
+static void init_xr21v141x_reg_map(void)
+{
+	xr21v141x_reg_map.uart_enable = 0x03;
+	xr21v141x_reg_map.uart_format = 0x0b;
+	xr21v141x_reg_map.uart_flow = 0x0c;
+	xr21v141x_reg_map.uart_xon_char = 0x10;
+	xr21v141x_reg_map.uart_xoff_char = 0x11;
+	xr21v141x_reg_map.uart_loopback = 0x12;
+	xr21v141x_reg_map.uart_tx_break = 0x14;
+	xr21v141x_reg_map.uart_rs485_delay = 0x15;
+	xr21v141x_reg_map.uart_gpio_mode = 0x1a;
+	xr21v141x_reg_map.uart_gpio_dir = 0x1b;
+	xr21v141x_reg_map.uart_gpio_int_mask = 0x1c;
+	xr21v141x_reg_map.uart_gpio_set = 0x1d;
+	xr21v141x_reg_map.uart_gpio_clr = 0x1e;
+	xr21v141x_reg_map.uart_gpio_status = 0x1f;
+}
+
+static void init_xr21b142x_reg_map(void)
+{
+	xr21b142x_reg_map.uart_enable = 0x00;
+	xr21b142x_reg_map.uart_flow = 0x06;
+	xr21b142x_reg_map.uart_xon_char = 0x07;
+	xr21b142x_reg_map.uart_xoff_char = 0x08;
+	xr21b142x_reg_map.uart_tx_break = 0x0a;
+	xr21b142x_reg_map.uart_rs485_delay = 0x0b;
+	xr21b142x_reg_map.uart_gpio_mode = 0x0c;
+	xr21b142x_reg_map.uart_gpio_dir = 0x0d;
+	xr21b142x_reg_map.uart_gpio_set = 0x0e;
+	xr21b142x_reg_map.uart_gpio_clr = 0x0f;
+	xr21b142x_reg_map.uart_gpio_status = 0x10;
+	xr21b142x_reg_map.uart_gpio_int_mask = 0x11;
+	xr21b142x_reg_map.uart_customized_int = 0x12;
+	xr21b142x_reg_map.uart_gpio_open_drain = 0x13;
+	xr21b142x_reg_map.uart_gpio_pull_up_enable = 0x14;
+	xr21b142x_reg_map.uart_gpio_pull_down_enable = 0x15;
+	xr21b142x_reg_map.uart_loopback = 0x16;
+	xr21b142x_reg_map.uart_custom_driver = 0x60;
+	xr21b142x_reg_map.uart_low_latency = 0x46;
+}
+
+int xrusb_reg_init(struct xrusb *xrusb)
+{
+	int rv = 0, gpio_mode = 0;
+	unsigned char ch1_caracalla_config = 0xff;
+	unsigned char ch2_caracalla_config = 0xff;
+
+	init_xr2280x_reg_map();
+	init_xr21b142x_reg_map();
+	init_xr21b1411_reg_map();
+	init_xr21v141x_reg_map();
+
+	if ((xrusb->DeviceProduct & 0xfff0) == 0x1400)
+		memcpy(&(xrusb->reg_map), &xr2280x_reg_map,
+			sizeof(struct reg_addr_map));
+	else if ((xrusb->DeviceProduct & 0xFFF0) == 0x1420)
+		memcpy(&(xrusb->reg_map), &xr21b142x_reg_map,
+			sizeof(struct reg_addr_map));
+	else if (xrusb->DeviceProduct == 0x1411)
+		memcpy(&(xrusb->reg_map), &xr21b1411_reg_map,
+			sizeof(struct reg_addr_map));
+	else if ((xrusb->DeviceProduct & 0xfff0) == 0x1410)
+		memcpy(&(xrusb->reg_map), &xr21v141x_reg_map,
+			sizeof(struct reg_addr_map));
+	else
+		rv = -1;
+
+	if (xrusb->reg_map.uart_custom_driver)
+		xrusb_set_reg(xrusb, xrusb->reg_map.uart_custom_driver, 1);
+
+	// Enable TXT and RXT function for XR21B1420/22/24
+	if ((xrusb->DeviceProduct & 0xfff0) == 0x1420)
+		gpio_mode |= (GPIO9_RXT | GPIO8_TXT);
+
+	xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, gpio_mode);
+
+	// Enable RTS and DTR as outputs and high (de-asserted)
+	xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir,
+		GPIO5_RTS | GPIO3_DTR);
+	xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set,
+		GPIO5_RTS | GPIO3_DTR);
+
+	// Check for Caracalla board using XR21V1412 (2-ch)
+	xrusb->found_smbios_caracalla_config = 0;
+	if (smbios_check_caracalla_config(&ch1_caracalla_config,
+		&ch2_caracalla_config) == 0) {
+		if (xrusb->channel == 1) {
+			xrusb->found_smbios_caracalla_config = 1;
+			xrusb->channel_config =	ch1_caracalla_config;
+			switch (ch1_caracalla_config) {
+			case 1: // RS-232 Mode
+				xrusb_set_reg(xrusb,
+					xrusb->reg_map.uart_gpio_mode, 0);
+				break;
+			case 2: // RS-485 Half-Duplex, active high RTS
+				xrusb_set_reg(xrusb,
+					xrusb->reg_map.uart_gpio_mode,
+					UART_GPIO_MODE_DIR_RTS_HI);
+				xrusb_set_reg(xrusb,
+					xrusb->reg_map.uart_flow, 0);
+				break;
+			case 3: // RS-485/422 Full-Duplex, active high RTS
+				xrusb_set_reg(xrusb,
+					xrusb->reg_map.uart_gpio_mode,
+					UART_GPIO_MODE_DIR_RTS_HI);
+				xrusb_set_reg(xrusb,
+					xrusb->reg_map.uart_flow, 0);
+				break;
+			default: // No Caracalla BIOS setting found
+				xrusb->found_smbios_caracalla_config = 0;
+				break;
+			}
+		} else if (xrusb->channel == 2) {
+			xrusb->found_smbios_caracalla_config = 1;
+			xrusb->channel_config = ch2_caracalla_config;
+			switch (ch2_caracalla_config) {
+			case 1: // RS-232 Mode
+				xrusb_set_reg(xrusb,
+					xrusb->reg_map.uart_gpio_mode, 0);
+				break;
+			case 2: // RS-485 Half-Duplex, active high RTS
+				xrusb_set_reg(xrusb,
+					xrusb->reg_map.uart_gpio_mode,
+					UART_GPIO_MODE_DIR_RTS_HI);
+				xrusb_set_reg(xrusb,
+					xrusb->reg_map.uart_flow, 0);
+				break;
+			case 3: // RS-485/422 Full-Duplex, active high RTS
+				xrusb_set_reg(xrusb,
+					xrusb->reg_map.uart_gpio_mode,
+					UART_GPIO_MODE_DIR_RTS_HI);
+				xrusb_set_reg(xrusb,
+					xrusb->reg_map.uart_flow, 0);
+				break;
+			default: // No Caracalla BIOS setting found
+				xrusb->found_smbios_caracalla_config = 0;
+				break;
+			}
+		}
+	}
+	if (xrusb->found_smbios_caracalla_config)
+		return rv;
+
+	// Check for previously saved port_config files in /etc/
+	// for designs using XR21B142x using GPIO9-6 for transceiver control
+	if (check_portparamfile(xrusb)) {
+		xrusb_gpio_dir_out(xrusb,
+			GPIO9_RXT | GPIO8_TXT | GPIO7_XEN | GPIO6_CLK);
+		load_portparamfile(xrusb);
+		init_portparam(xrusb);
+	}
+	return rv;
+}
+
+/*
+ * Write buffer management.
+ * All of these assume proper locks taken by the caller.
+ */
+
+static int xrusb_wb_alloc(struct xrusb *xrusb)
+{
+	int i, wbn;
+	struct xrusb_wb *wb;
+
+	wbn = 0;
+	i = 0;
+	for (;;) {
+		wb = &xrusb->wb[wbn];
+		if (!wb->use) {
+			wb->use = 1;
+			return wbn;
+		}
+		wbn = (wbn + 1) % XRUSB_NW;
+		if (++i >= XRUSB_NW)
+			return -1;
+	}
+}
+
+static int xrusb_wb_is_avail(struct xrusb *xrusb)
+{
+	int i, n;
+	unsigned long flags;
+
+	n = XRUSB_NW;
+	spin_lock_irqsave(&xrusb->write_lock, flags);
+	for (i = 0; i < XRUSB_NW; i++)
+		n -= xrusb->wb[i].use;
+	spin_unlock_irqrestore(&xrusb->write_lock, flags);
+	return n;
+}
+
+/*
+ * Finish write. Caller must hold xrusb->write_lock
+ */
+static void xrusb_write_done(struct xrusb *xrusb,
+		struct xrusb_wb *wb)
+{
+	wb->use = 0;
+	xrusb->transmitting--;
+	usb_autopm_put_interface_async(xrusb->control);
+}
+
+/*
+ * Poke write.
+ *
+ * the caller is responsible for locking
+ */
+
+static int xrusb_start_wb(struct xrusb *xrusb, struct xrusb_wb *wb)
+{
+	int rc;
+
+	xrusb->transmitting++;
+
+	wb->urb->transfer_buffer = wb->buf;
+	wb->urb->transfer_dma = wb->dmah;
+	wb->urb->transfer_buffer_length = wb->len;
+	wb->urb->dev = xrusb->dev;
+
+	rc = usb_submit_urb(wb->urb, GFP_ATOMIC);
+	if (rc < 0) {
+		dev_err(&xrusb->data->dev,
+			"%s - usb_submit_urb(write bulk) failed: %d\n",
+			__func__, rc);
+		xrusb_write_done(xrusb, wb);
+	}
+	return rc;
+}
+
+/*
+ * attributes exported through sysfs
+ */
+
+static ssize_t bmCapabilities_show
+(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct usb_interface *intf = to_usb_interface(dev);
+	struct xrusb *xrusb = usb_get_intfdata(intf);
+
+	return sprintf(buf, "%d", xrusb->ctrl_caps);
+}
+static DEVICE_ATTR_RO(bmCapabilities);
+
+/*
+ * Interrupt handlers for various XRUSB device responses
+ */
+
+/* control interface reports status changes with "interrupt" transfers */
+static void xrusb_ctrl_irq(struct urb *urb)
+{
+	struct xrusb *xrusb = urb->context;
+	struct usb_cdc_notification *dr = urb->transfer_buffer;
+
+	unsigned char *data;
+	int newctrl;
+	int rv;
+	int status = urb->status;
+	int i;
+	unsigned char *p;
+
+	switch (status) {
+	case 0:
+		p = (unsigned char *)(urb->transfer_buffer);
+		for (i = 0; i < urb->actual_length; i++)
+			dev_dbg(&xrusb->control->dev, "0x%02x\n", p[i]);
+		/* success */
+		break;
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		/* this urb is terminated, clean up */
+		dev_dbg(&xrusb->control->dev,
+			"%s - urb shutting down with status: %d\n", __func__,
+			status);
+		return;
+	default:
+		dev_dbg(&xrusb->control->dev,
+			"%s - nonzero urb status received: %d\n",
+			__func__, status);
+		goto exit;
+	}
+
+	usb_mark_last_busy(xrusb->dev);
+
+	data = (unsigned char *)(dr + 1);
+	switch (dr->bNotificationType) {
+	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
+		dev_dbg(&xrusb->control->dev, "%s - network connection: %d\n",
+			__func__, dr->wValue);
+		break;
+
+	case USB_CDC_NOTIFY_SERIAL_STATE:
+
+		newctrl = get_unaligned_le16(data);
+		if (!xrusb->clocal &&
+			(xrusb->ctrlin & ~newctrl & XRUSB_CTRL_DCD)) {
+
+			dev_dbg(&xrusb->control->dev,
+				"%s - calling hangup\n",
+				__func__);
+			tty_port_tty_hangup(&xrusb->port, false);
+		}
+
+		xrusb->ctrlin = newctrl;
+		break;
+
+	default:
+		dev_dbg(&xrusb->control->dev,
+			"%s - unknown notification %d received: index %d ",
+			__func__, dr->bNotificationType, dr->wIndex);
+		dev_dbg(&xrusb->control->dev, "len %d data0 %d data1 %d\n",
+			dr->wLength, data[0], data[1]);
+		break;
+	}
+exit:
+	rv = usb_submit_urb(urb, GFP_ATOMIC);
+	if (rv)
+		dev_err(&xrusb->control->dev,
+			"%s - usb_submit_urb failed: %d\n", __func__, rv);
+}
+
+static int xrusb_submit_read_urb(struct xrusb *xrusb,
+		int index, gfp_t mem_flags)
+{
+	int res;
+
+	if (!test_and_clear_bit(index, &xrusb->read_urbs_free))
+		return 0;
+
+	//dev_vdbg(&xrusb->data->dev, "%s - urb %d\n", __func__, index);
+
+	res = usb_submit_urb(xrusb->read_urbs[index], mem_flags);
+	if (res) {
+		if (res != -EPERM) {
+			dev_err(&xrusb->data->dev,
+					"%s - usb_submit_urb failed: %d\n",
+					__func__, res);
+		}
+		set_bit(index, &xrusb->read_urbs_free);
+		return res;
+	}
+
+	return 0;
+}
+
+static int xrusb_submit_read_urbs(struct xrusb *xrusb,
+		gfp_t mem_flags)
+{
+	int res;
+	int i;
+
+	for (i = 0; i < xrusb->rx_buflimit; ++i) {
+		res = xrusb_submit_read_urb(xrusb, i, mem_flags);
+		if (res)
+			return res;
+	}
+
+	return 0;
+}
+
+static void xrusb_process_read_urb(struct xrusb *xrusb,	struct urb *urb)
+{
+	int wide_mode = xrusb->wide_mode;
+	int have_extra_byte;
+	int length;
+
+	if (!urb->actual_length)
+		return;
+
+	if (wide_mode) {
+		char *dp = urb->transfer_buffer;
+		int i, ch, ch_flags;
+
+		length = urb->actual_length;
+		length = length + (xrusb->have_extra_byte ? 1 : 0);
+		have_extra_byte = (wide_mode && (length & 1));
+		length = (wide_mode) ? (length / 2) : length;
+		for (i = 0; i < length; ++i) {
+			char tty_flag;
+
+			if (i == 0) {
+				if (xrusb->have_extra_byte)
+					ch = xrusb->extra_byte;
+				else
+					ch = *dp++;
+			} else
+				ch = *dp++;
+
+			ch_flags = *dp++;
+			if (ch_flags & WIDE_MODE_PARITY)
+				tty_flag = TTY_PARITY;
+			else if (ch_flags & WIDE_MODE_BREAK)
+				tty_flag = TTY_BREAK;
+			else if (ch_flags & WIDE_MODE_FRAME)
+				tty_flag = TTY_FRAME;
+			else if (ch_flags & WIDE_MODE_OVERRUN)
+				tty_flag = TTY_OVERRUN;
+			else
+				tty_flag = TTY_NORMAL;
+
+			tty_insert_flip_char(&xrusb->port,
+				ch, tty_flag);
+			tty_flip_buffer_push(&xrusb->port);
+		}
+	} else {
+		tty_insert_flip_string(&xrusb->port,
+			urb->transfer_buffer, urb->actual_length);
+		tty_flip_buffer_push(&xrusb->port);
+	}
+}
+
+static void xrusb_read_bulk_callback(struct urb *urb)
+{
+	struct xrusb_rb *rb = urb->context;
+	struct xrusb *xrusb = rb->instance;
+	unsigned long flags;
+
+	set_bit(rb->index, &xrusb->read_urbs_free);
+
+	if (!xrusb->dev) {
+		dev_dbg(&xrusb->data->dev,
+			"%s - disconnected\n", __func__);
+		return;
+	}
+	usb_mark_last_busy(xrusb->dev);
+
+	if (urb->status) {
+		dev_dbg(&xrusb->data->dev,
+			"%s - non-zero urb status: %d\n",
+			__func__, urb->status);
+		return;
+	}
+	xrusb_process_read_urb(xrusb, urb);
+
+	/* throttle device if requested by tty */
+	spin_lock_irqsave(&xrusb->read_lock, flags);
+	xrusb->throttled = xrusb->throttle_req;
+	if (!xrusb->throttled && !xrusb->susp_count) {
+		spin_unlock_irqrestore(&xrusb->read_lock, flags);
+		xrusb_submit_read_urb(xrusb,
+			rb->index, GFP_ATOMIC);
+	} else {
+		spin_unlock_irqrestore(&xrusb->read_lock, flags);
+	}
+}
+
+/* data interface wrote those outgoing bytes */
+static void xrusb_write_bulk(struct urb *urb)
+{
+	struct xrusb_wb *wb = urb->context;
+	struct xrusb *xrusb = wb->instance;
+	unsigned long flags;
+
+	spin_lock_irqsave(&xrusb->write_lock, flags);
+	xrusb_write_done(xrusb, wb);
+	spin_unlock_irqrestore(&xrusb->write_lock, flags);
+	schedule_work(&xrusb->work);
+}
+
+static void xrusb_softint(struct work_struct *work)
+{
+	struct xrusb *xrusb = container_of(work, struct xrusb, work);
+
+	tty_port_tty_wakeup(&xrusb->port);
+}
+
+/*
+ * TTY handlers
+ */
+
+static int xrusb_tty_install(struct tty_driver *driver,
+		struct tty_struct *tty)
+{
+	struct xrusb *xrusb;
+	int rv;
+
+	dev_dbg(tty->dev, "%s\n", __func__);
+
+	xrusb = xrusb_get_by_index(tty->index);
+	if (!xrusb)
+		return -ENODEV;
+
+	rv = tty_standard_install(driver, tty);
+	if (rv)
+		goto error_init_termios;
+
+	tty->driver_data = xrusb;
+
+	return 0;
+
+error_init_termios:
+	tty_port_put(&xrusb->port);
+	return rv;
+}
+
+static int xrusb_tty_open(struct tty_struct *tty, struct file *filp)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	int result;
+
+	result = xrusb_fifo_reset(xrusb);
+	dev_dbg(tty->dev, "%s\n", __func__);
+
+	return tty_port_open(&xrusb->port, tty, filp);
+}
+
+static int xrusb_port_activate(struct tty_port *port, struct tty_struct *tty)
+{
+	struct xrusb *xrusb = container_of(port, struct xrusb, port);
+	int rv = -ENODEV;
+
+	dev_dbg(&xrusb->control->dev, "%s\n", __func__);
+
+	mutex_lock(&xrusb->mutex);
+	if (xrusb->disconnected)
+		goto disconnected;
+
+	rv = usb_autopm_get_interface(xrusb->control);
+	if (rv)
+		goto error_get_interface;
+
+	/*
+	 * FIXME: Why do we need this? Allocating 64K of physically contiguous
+	 * memory is really nasty...
+	 */
+	set_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
+	xrusb->control->needs_remote_wakeup = 1;
+
+	xrusb->ctrlurb->dev = xrusb->dev;
+	if (usb_submit_urb(xrusb->ctrlurb, GFP_KERNEL)) {
+		dev_err(&xrusb->control->dev,
+			"%s - usb_submit_urb(ctrl irq) failed\n", __func__);
+		goto error_submit_urb;
+	}
+
+	xrusb->ctrlout = XRUSB_CTRL_DTR |
+		XRUSB_CTRL_RTS;
+	if (xrusb_set_control(xrusb, xrusb->ctrlout) < 0
+		&& (xrusb->ctrl_caps & USB_CDC_CAP_LINE))
+		goto error_set_control;
+
+	usb_autopm_put_interface(xrusb->control);
+
+	/*
+	 * Unthrottle device in case the TTY was closed while throttled.
+	 */
+	spin_lock_irq(&xrusb->read_lock);
+	xrusb->throttled = 0;
+	xrusb->throttle_req = 0;
+	spin_unlock_irq(&xrusb->read_lock);
+
+	if (xrusb_submit_read_urbs(xrusb, GFP_KERNEL))
+		goto error_submit_read_urbs;
+
+	mutex_unlock(&xrusb->mutex);
+
+	return 0;
+
+error_submit_read_urbs:
+	xrusb->ctrlout = 0;
+	xrusb_set_control(xrusb, xrusb->ctrlout);
+error_set_control:
+	usb_kill_urb(xrusb->ctrlurb);
+error_submit_urb:
+	usb_autopm_put_interface(xrusb->control);
+error_get_interface:
+disconnected:
+	mutex_unlock(&xrusb->mutex);
+	return rv;
+}
+
+static void xrusb_port_destruct(struct tty_port *port)
+{
+	struct xrusb *xrusb = container_of(port, struct xrusb, port);
+
+	//dev_dbg(&xrusb->control->dev, "%s\n", __func__);
+
+	xrusb_release_minor(xrusb);
+	usb_put_intf(xrusb->control);
+	kfree(xrusb);
+}
+
+static void xrusb_port_shutdown(struct tty_port *port)
+{
+	struct xrusb *xrusb = container_of(port, struct xrusb, port);
+	int i;
+
+	//dev_dbg(&xrusb->control->dev, "%s\n", __func__);
+
+	mutex_lock(&xrusb->mutex);
+	if (!xrusb->disconnected) {
+		usb_autopm_get_interface(xrusb->control);
+		xrusb_set_control(xrusb,
+			xrusb->ctrlout = 0);
+		usb_kill_urb(xrusb->ctrlurb);
+		for (i = 0; i < XRUSB_NW; i++)
+			usb_kill_urb(xrusb->wb[i].urb);
+		for (i = 0; i < xrusb->rx_buflimit; i++)
+			usb_kill_urb(xrusb->read_urbs[i]);
+		xrusb->control->needs_remote_wakeup = 0;
+		usb_autopm_put_interface(xrusb->control);
+	}
+	mutex_unlock(&xrusb->mutex);
+}
+
+static void xrusb_tty_cleanup(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	//dev_dbg(&xrusb->control->dev, "%s\n", __func__);
+	tty_port_put(&xrusb->port);
+}
+
+static void xrusb_tty_hangup(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	//dev_dbg(&xrusb->control->dev, "%s\n", __func__);
+	tty_port_hangup(&xrusb->port);
+}
+
+static void xrusb_tty_close(struct tty_struct *tty, struct file *filp)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	//dev_dbg(&xrusb->control->dev, "%s\n", __func__);
+	tty_port_close(&xrusb->port, tty, filp);
+}
+
+static int xrusb_tty_write(struct tty_struct *tty,
+		const unsigned char *buf, int count)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	int stat;
+	unsigned long flags;
+	int wbn;
+	struct xrusb_wb *wb;
+
+	if (!count)
+		return 0;
+
+	//dev_vdbg(&xrusb->data->dev, "%s - count %d\n", __func__, count);
+
+	spin_lock_irqsave(&xrusb->write_lock, flags);
+	wbn = xrusb_wb_alloc(xrusb);
+	if (wbn < 0) {
+		spin_unlock_irqrestore(&xrusb->write_lock, flags);
+		return 0;
+	}
+	wb = &xrusb->wb[wbn];
+
+	if (!xrusb->dev) {
+		wb->use = 0;
+		spin_unlock_irqrestore(&xrusb->write_lock, flags);
+		return -ENODEV;
+	}
+
+	count = (count > xrusb->writesize) ? xrusb->writesize : count;
+	//dev_vdbg(&xrusb->data->dev, "%s - write %d\n", __func__, count);
+	memcpy(wb->buf, buf, count);
+	wb->len = count;
+
+	usb_autopm_get_interface_async(xrusb->control);
+	if (xrusb->susp_count) {
+		if (!xrusb->delayed_wb)
+			xrusb->delayed_wb = wb;
+		else
+			usb_autopm_put_interface_async(xrusb->control);
+		spin_unlock_irqrestore(&xrusb->write_lock, flags);
+		return count;	/* A white lie */
+	}
+	usb_mark_last_busy(xrusb->dev);
+
+	stat = xrusb_start_wb(xrusb, wb);
+	spin_unlock_irqrestore(&xrusb->write_lock, flags);
+
+	if (stat < 0)
+		return stat;
+	return count;
+}
+
+static int xrusb_tty_write_room(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	/*
+	 * Do not let the line discipline to know that we have a reserve,
+	 * or it might get too enthusiastic.
+	 */
+	return xrusb_wb_is_avail(xrusb) ?
+		xrusb->writesize : 0;
+}
+
+static int xrusb_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	/*
+	 * if the device was unplugged then any remaining characters fell out
+	 * of the connector ;)
+	 */
+	if (xrusb->disconnected)
+		return 0;
+	/*
+	 * This is inaccurate (overcounts), but it works.
+	 */
+	return (XRUSB_NW - xrusb_wb_is_avail(xrusb)) *
+		xrusb->writesize;
+}
+
+static void xrusb_tty_throttle(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+
+	spin_lock_irq(&xrusb->read_lock);
+	xrusb->throttle_req = 1;
+	spin_unlock_irq(&xrusb->read_lock);
+}
+
+static void xrusb_tty_unthrottle(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	unsigned int was_throttled;
+
+	spin_lock_irq(&xrusb->read_lock);
+	was_throttled = xrusb->throttled;
+	xrusb->throttled = 0;
+	xrusb->throttle_req = 0;
+	spin_unlock_irq(&xrusb->read_lock);
+
+	if (was_throttled)
+		xrusb_submit_read_urbs(xrusb, GFP_KERNEL);
+}
+
+static int xrusb_tty_break_ctl(struct tty_struct *tty, int state)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	int rv;
+
+	rv = xrusb_send_break(xrusb, state ? 0xffff : 0);
+	if (rv < 0)
+		dev_err(&xrusb->control->dev, "%s - send break failed\n",
+			__func__);
+	return rv;
+}
+
+static int xrusb_tty_tiocmget(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+
+	return xrusb_tiocmget(xrusb);
+}
+
+static int xrusb_tty_tiocmset(struct tty_struct *tty,
+			    unsigned int set, unsigned int clear)
+{
+	struct xrusb *xrusb = tty->driver_data;
+
+	return xrusb_tiocmset(xrusb, set, clear);
+}
+
+static int get_serial_info(struct xrusb *xrusb,
+		struct serial_struct __user *info)
+{
+	struct serial_struct tmp;
+
+	if (!info)
+		return -EINVAL;
+
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.flags = ASYNC_LOW_LATENCY;
+	tmp.xmit_fifo_size = xrusb->writesize;
+	tmp.baud_base = le32_to_cpu(xrusb->line.dwDTERate);
+	tmp.close_delay	= xrusb->port.close_delay / 10;
+	tmp.closing_wait = xrusb->port.closing_wait ==
+		ASYNC_CLOSING_WAIT_NONE ? ASYNC_CLOSING_WAIT_NONE :
+		xrusb->port.closing_wait / 10;
+
+	if (copy_to_user(info, &tmp, sizeof(tmp)))
+		return -EFAULT;
+	else
+		return 0;
+}
+
+static int set_serial_info(struct xrusb *xrusb,
+		struct serial_struct __user *newinfo)
+{
+	struct serial_struct new_serial;
+	unsigned int closing_wait, close_delay;
+	int rv = 0;
+
+	if (copy_from_user(&new_serial, newinfo, sizeof(new_serial)))
+		return -EFAULT;
+
+	close_delay = new_serial.close_delay * 10;
+	closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
+			ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10;
+
+	mutex_lock(&xrusb->port.mutex);
+
+	if (!capable(CAP_SYS_ADMIN)) {
+		if ((close_delay != xrusb->port.close_delay) ||
+		    (closing_wait != xrusb->port.closing_wait))
+			rv = -EPERM;
+		else
+			rv = -EOPNOTSUPP;
+	} else {
+		xrusb->port.close_delay  = close_delay;
+		xrusb->port.closing_wait = closing_wait;
+	}
+
+	mutex_unlock(&xrusb->port.mutex);
+	return rv;
+}
+
+static int xrusb_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
+		unsigned long arg)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	int rv = -ENOIOCTLCMD;
+	unsigned int  channel, param_rw;
+	int baud_rate = 0;
+	struct usb_cdc_line_coding newline;
+	short reg_value;
+
+	channel = xrusb->channel;
+	switch (cmd) {
+	case TIOCGSERIAL: /* gets serial port data */
+		rv = get_serial_info(xrusb,
+			(struct serial_struct __user *) arg);
+		break;
+	case TIOCSSERIAL:
+		rv = set_serial_info(xrusb,
+			(struct serial_struct __user *) arg);
+		break;
+	case XRUSB_SET_BAUD:
+		if (get_user(baud_rate, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev,
+				"get_user error\n");
+			return -EFAULT;
+		}
+		xrusb->line.dwDTERate = baud_rate;
+		memcpy(&newline, &(xrusb->line),
+			sizeof(struct usb_cdc_line_coding));
+		xrusb_disable(xrusb);
+		rv = xrusb_set_line(xrusb, &newline);
+		xrusb_enable(xrusb);
+		break;
+	case XRUSB_LOOPBACK:
+		rv = xrusb_set_loopback(xrusb, channel);
+		if (rv < 0)
+			return -EFAULT;
+		break;
+	case XRUSB_SET_GPIO_MODE:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev,
+				"get_user error\n");
+			return -EFAULT;
+		}
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_mode, param_rw);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_GET_GPIO_MODE:
+		rv = xrusb_get_reg(xrusb,
+			xrusb->reg_map.uart_gpio_mode, &reg_value);
+		if (rv < 0)
+			return -EFAULT;
+		if (put_user(reg_value, (int __user *)arg)) {
+			dev_err(&xrusb->control->dev,
+				"put_user error\n");
+			return -EFAULT;
+		}
+		rv = 0;
+		break;
+	case XRUSB_SET_GPIO_DIR:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev,
+				"get_user error\n");
+			return -EFAULT;
+		}
+
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_dir,
+			(int)param_rw);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_GET_GPIO_DIR:
+		rv = xrusb_get_reg(xrusb,
+			xrusb->reg_map.uart_gpio_dir,
+			&reg_value);
+		if (rv < 0)
+			return -EFAULT;
+		if (put_user(reg_value, (int __user *)arg)) {
+			dev_err(&xrusb->control->dev,
+				"put_user error\n");
+			return -EFAULT;
+		}
+		rv = 0;
+		break;
+	case XRUSB_SET_GPIO_STATE:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev,
+				"get_user error\n");
+			return -EFAULT;
+		}
+
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_set,
+			(int)param_rw);
+
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_CLEAR_GPIO_STATE:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev,
+				"get_user error\n");
+			return -EFAULT;
+		}
+
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_clr,
+			(int)param_rw);
+
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_GET_GPIO_STATE:
+		rv = xrusb_get_reg(xrusb,
+			xrusb->reg_map.uart_gpio_status,
+			&reg_value);
+		if (rv < 0)
+			return -EFAULT;
+		if (put_user(reg_value, (int __user *)arg)) {
+			dev_err(&xrusb->control->dev,
+				"put_user error\n");
+			return -EFAULT;
+		}
+		rv = 0;
+		break;
+	case XRUSB_SET_GPIO_INT_MASK:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev,
+				"get_user error\n");
+			return -EFAULT;
+		}
+
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_int_mask,
+			(int)param_rw);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_GET_GPIO_INT_MASK:
+		rv = xrusb_get_reg(xrusb,
+			xrusb->reg_map.uart_gpio_int_mask,
+			&reg_value);
+		if (put_user(reg_value, (int __user *)arg)) {
+			dev_err(&xrusb->control->dev,
+				"put_user error\n");
+			return -EFAULT;
+		}
+		rv = 0;
+		break;
+	case XRUSB_SET_GPIO_PULL_UP:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev,
+				"get_user error\n");
+			return -EFAULT;
+		}
+
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_pull_up_enable,
+			(int)param_rw);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_GET_GPIO_PULL_UP:
+		rv = xrusb_get_reg(xrusb,
+			xrusb->reg_map.uart_gpio_pull_up_enable,
+			&reg_value);
+		if (put_user(reg_value, (int __user *)arg)) {
+			dev_err(&xrusb->control->dev,
+				"Cannot put user result\n");
+			return -EFAULT;
+		}
+		rv = 0;
+		break;
+	case XRUSB_SET_GPIO_PULL_DOWN:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev,
+				"get_user error\n");
+			return -EFAULT;
+		}
+
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_pull_down_enable,
+			(int)param_rw);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_GET_GPIO_PULL_DOWN:
+		rv = xrusb_get_reg(xrusb,
+			xrusb->reg_map.uart_gpio_pull_down_enable,
+			&reg_value);
+		if (put_user(reg_value, (int __user *)arg)) {
+			dev_err(&xrusb->control->dev,
+				"Cannot put user result\n");
+			return -EFAULT;
+		}
+		rv = 0;
+		break;
+	case XRUSB_SET_GPIO_OPEN_DRAIN:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev,
+				"get_user error\n");
+			return -EFAULT;
+		}
+
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_open_drain,
+			(int)param_rw);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_GET_GPIO_OPEN_DRAIN:
+		rv = xrusb_get_reg(xrusb,
+			xrusb->reg_map.uart_gpio_open_drain,
+			&reg_value);
+		if (put_user(reg_value, (int __user *)arg)) {
+			dev_err(&xrusb->control->dev,
+				"Cannot put user result\n");
+			return -EFAULT;
+		}
+		rv = 0;
+		break;
+
+	case XRUSB_WIDE_MODE:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev,
+				"get_user error\n");
+			return -EFAULT;
+		}
+		if (param_rw)
+			xrusb->wide_mode = 1; // enable wide mode
+		else
+			xrusb->wide_mode = 0; // disable wide mode
+		rv = xrusb_set_wide_mode(xrusb,
+			xrusb->wide_mode);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_LOW_LATENCY_MODE:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev,
+				"get_user error\n");
+			return -EFAULT;
+		}
+		if (param_rw) {
+			// enable low latency mode
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_low_latency, 1);
+			if (rv < 0)
+				return -EFAULT;
+
+		} else {
+			// disable low latency mode
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_low_latency, 0);
+			if (rv < 0)
+				return -EFAULT;
+		}
+		xrusb->port_setting.low_latency_mode = param_rw;
+		rv = 0;
+		break;
+	case XRUSB_GET_TXCVR_MODE:
+		if (xrusb->have_txcvr_config) {
+			if (copy_to_user((void __user *)arg,
+				(void *)&(xrusb->port_setting),
+				sizeof(struct xrusb_setting))) {
+				return -EFAULT;
+			}
+			rv = 0;
+		}
+		break;
+	case XRUSB_SET_TXCVR_MODE:
+		if (copy_from_user((void *)&(xrusb->port_setting),
+			(void __user *)arg, sizeof(struct xrusb_setting)))
+			return -EFAULT;
+
+		rv = xrusb_disable_txt_rxt(xrusb);
+		if (rv < 0)
+			return -EFAULT;
+
+		// Enable GPIOs 9-6 as outputs
+		rv = xrusb_gpio_dir_out(xrusb, 0x3c0);
+		if (rv < 0)
+			return -EFAULT;
+
+		rv = set_txcvr_mode(xrusb,
+			xrusb->port_setting.txcvr_mode);
+		if (rv < 0)
+			return -EFAULT;
+
+		rv = update_portparamfile(xrusb);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_SET_TERM_MODE:
+		if (copy_from_user((void *)&(xrusb->port_setting),
+			(void __user *)arg, sizeof(struct xrusb_setting)))
+			return -EFAULT;
+
+		rv = xrusb_disable_txt_rxt(xrusb);
+		if (rv < 0)
+			return -EFAULT;
+
+		// Enable GPIOs 9-6 as outputs
+		rv = xrusb_gpio_dir_out(xrusb, 0x3c0);
+		if (rv < 0)
+			return -EFAULT;
+
+		rv = set_txcvr_term_mode(xrusb,
+			xrusb->port_setting.term_mode);
+		if (rv < 0)
+			return -EFAULT;
+
+		rv = update_portparamfile(xrusb);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_SET_SLEW_MODE:
+		if (copy_from_user((void *)&(xrusb->port_setting),
+			(void __user *)arg, sizeof(struct xrusb_setting)))
+			return -EFAULT;
+
+		rv = xrusb_disable_txt_rxt(xrusb);
+		if (rv < 0)
+			return -EFAULT;
+
+		// Enable GPIOs 9-6 as outputs
+		rv = xrusb_gpio_dir_out(xrusb, 0x3c0);
+		if (rv < 0)
+			return -EFAULT;
+
+		rv = set_txcvr_slew_mode(xrusb,
+			xrusb->port_setting.slew_mode);
+		if (rv < 0)
+			return -EFAULT;
+
+		rv = update_portparamfile(xrusb);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_SET_RS485_PIN:
+		if (copy_from_user((void *)&(xrusb->port_setting),
+			(void __user *)arg, sizeof(struct xrusb_setting)))
+			return -EFAULT;
+
+		if (xrusb->port_setting.txcvr_mode < 2)
+			rv = set_rs485_pin(xrusb, 0);
+		else
+			rv = set_rs485_pin(xrusb,
+				xrusb->port_setting.txcvr_dir_pin);
+
+		if (rv < 0)
+			return -EFAULT;
+
+		rv = update_portparamfile(xrusb);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_ENABLE_RS485_LOW:
+		rv = xrusb_get_reg(xrusb,
+			xrusb->reg_map.uart_gpio_mode, &reg_value);
+		if (rv < 0)
+			return -EFAULT;
+		reg_value &= 0x3f7; // bit-3 = 0 for active low
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode,
+			(int)reg_value);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_RS485_DELAY:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev, "get_user error\n");
+			return -EFAULT;
+		}
+		if (param_rw) {
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_rs485_delay, 15);
+			if (rv < 0)
+				return -EFAULT;
+		} else {
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_rs485_delay, 0);
+			if (rv < 0)
+				return -EFAULT;
+		}
+		rv = 0;
+		break;
+	case XRUSB_ENABLE_DTRDSR_FLOW:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev, "get_user error\n");
+			return -EFAULT;
+		}
+		// Value = Read GPIO_MODE register & 0x300;
+		// GPIO_MODE = Value | 0x02;
+		// FLOW = 0x1;
+		rv = xrusb_get_reg(xrusb,
+			xrusb->reg_map.uart_gpio_mode, &reg_value);
+		if (rv < 0)
+			return -EFAULT;
+		reg_value &= 0x300;
+		reg_value |= 0x02;
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_mode,
+			(int)reg_value);
+		if (rv < 0)
+			return -EFAULT;
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_flow,
+			UART_FLOW_MODE_HW);
+		if (rv < 0)
+			return -EFAULT;
+		rv = 0;
+		break;
+	case XRUSB_DISABLE_FLOW_CONTROL:
+		if (get_user(param_rw, (int __user *)arg)) {
+			dev_dbg(&xrusb->control->dev, "get_user error\n");
+			return -EFAULT;
+		}
+		//	Value = Read GPIO_MODE register & 0x300;
+		//	GPIO_MODE = Value;
+		//	FLOW = 0x00;
+		rv = xrusb_get_reg(xrusb,
+			xrusb->reg_map.uart_gpio_mode, &reg_value);
+		if (rv < 0)
+			return -EFAULT;
+		reg_value &= 0x300;
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_mode,
+			(int)reg_value);
+		if (rv < 0)
+			return -EFAULT;
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_flow,
+			UART_FLOW_MODE_NONE);
+		if (rv < 0)
+			return -EFAULT;
+		break;
+	case XRUSB_GET_FLOW_CONTROL:
+		rv = xrusb_get_reg(xrusb,
+			xrusb->reg_map.uart_flow, &reg_value);
+		if (put_user(reg_value, (int __user *)arg)) {
+			dev_err(&xrusb->control->dev, "put_user error\n");
+			return -EFAULT;
+		}
+		rv = 0;
+		break;
+	}
+	return rv;
+}
+
+static void xrusb_tty_set_termios(struct tty_struct *tty,
+		struct ktermios *termios_old)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	struct ktermios *termios = &tty->termios;
+	unsigned int   cflag = termios->c_cflag;
+	struct usb_cdc_line_coding newline;
+	int newctrl = xrusb->ctrlout;
+
+	xrusb_disable(xrusb);
+	newline.dwDTERate = cpu_to_le32(tty_get_baud_rate(tty));
+	newline.bCharFormat = termios->c_cflag & CSTOPB ? 1 : 0;
+	newline.bParityType = termios->c_cflag & PARENB ?
+				(termios->c_cflag & PARODD ? 1 : 2) +
+				(termios->c_cflag & CMSPAR ? 2 : 0) : 0;
+	xrusb->wide_mode = 0;
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:/*using CS5 replace of the 9 bit data mode*/
+		newline.bDataBits = 9;
+		xrusb->wide_mode = 1;
+		break;
+	case CS6:
+		newline.bDataBits = 6;
+		break;
+	case CS7:
+		newline.bDataBits = 7;
+		break;
+	case CS8:
+	default:
+		newline.bDataBits = 8;
+		break;
+	}
+	/* FIXME: Needs to clear unsupported bits in the termios */
+	xrusb->clocal = ((termios->c_cflag & CLOCAL) != 0);
+
+	if (!newline.dwDTERate) {
+		newline.dwDTERate = xrusb->line.dwDTERate;
+		newctrl &= ~XRUSB_CTRL_DTR;
+	} else
+		newctrl |=  XRUSB_CTRL_DTR;
+
+	if (newctrl != xrusb->ctrlout)
+		xrusb_set_control(xrusb,
+			xrusb->ctrlout = newctrl);
+
+	xrusb_set_flow_mode(xrusb, tty, cflag);
+	if (xrusb->wide_mode)
+		xrusb_set_wide_mode(xrusb, 1);
+	else if (!xrusb->wide_mode)
+		xrusb_set_wide_mode(xrusb, 0);
+
+	if (memcmp(&xrusb->line, &newline, sizeof(newline))) {
+		memcpy(&xrusb->line, &newline, sizeof(newline));
+		xrusb_set_line(xrusb, &xrusb->line);
+	}
+	xrusb_enable(xrusb);
+}
+
+static const struct tty_port_operations xrusb_port_ops = {
+	.shutdown = xrusb_port_shutdown,
+	.activate = xrusb_port_activate,
+	.destruct = xrusb_port_destruct,
+};
+
+/*
+ * USB probe and disconnect routines.
+ */
+
+/* Little helpers: write/read buffers free */
+static void xrusb_write_buffers_free(struct xrusb *xrusb)
+{
+	int i;
+	struct xrusb_wb *wb;
+
+	for (wb = &xrusb->wb[0], i = 0; i < XRUSB_NW; i++, wb++)
+		usb_free_coherent(xrusb->dev,
+			xrusb->writesize, wb->buf, wb->dmah);
+}
+
+static void xrusb_read_buffers_free(struct xrusb *xrusb)
+{
+	int i;
+
+	for (i = 0; i < xrusb->rx_buflimit; i++)
+		usb_free_coherent(xrusb->dev, xrusb->readsize,
+			  xrusb->read_buffers[i].base,
+			  xrusb->read_buffers[i].dma);
+}
+
+/* Little helper: write buffers allocate */
+static int xrusb_write_buffers_alloc(struct xrusb *xrusb)
+{
+	int i;
+	struct xrusb_wb *wb;
+
+	for (wb = &xrusb->wb[0], i = 0; i < XRUSB_NW; i++, wb++) {
+		wb->buf = usb_alloc_coherent(xrusb->dev,
+			xrusb->writesize, GFP_KERNEL,
+			&wb->dmah);
+		if (!wb->buf) {
+			while (i != 0) {
+				--i;
+				--wb;
+				usb_free_coherent(xrusb->dev,
+					xrusb->writesize,
+					wb->buf, wb->dmah);
+			}
+			return -ENOMEM;
+		}
+	}
+	return 0;
+}
+
+static int xrusb_probe(struct usb_interface *intf,
+		     const struct usb_device_id *id)
+{
+	struct usb_cdc_union_desc *union_header = NULL;
+	unsigned char *buffer = intf->altsetting->extra;
+	int buflen = intf->altsetting->extralen;
+	struct usb_interface *control_interface;
+	struct usb_interface *data_interface;
+	struct usb_endpoint_descriptor *epctrl = NULL;
+	struct usb_endpoint_descriptor *epread = NULL;
+	struct usb_endpoint_descriptor *epwrite = NULL;
+	struct usb_device *usb_dev = interface_to_usbdev(intf);
+	struct xrusb *xrusb;
+	int minor;
+	int ctrlsize, readsize;
+	u8 *buf;
+	u8 ac_management_function = 0;
+	u8 call_management_function = 0;
+	int call_interface_num = -1;
+	int data_interface_num = -1;
+	unsigned long quirks;
+	int num_rx_buf;
+	int i;
+	int combined_interfaces = 0;
+	struct device *tty_dev;
+	int rv = -ENOMEM;
+
+	/* normal quirks */
+	quirks = (unsigned long)id->driver_info;
+
+	if (quirks == IGNORE_DEVICE)
+		return -ENODEV;
+
+	num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : XRUSB_NR;
+
+	/* handle quirks deadly to normal probing*/
+	if (quirks == NO_UNION_NORMAL) {
+		data_interface = usb_ifnum_to_if(usb_dev, 1);
+		control_interface = usb_ifnum_to_if(usb_dev, 0);
+		goto skip_normal_probe;
+	}
+
+	/* normal probing*/
+	if (!buffer) {
+		dev_err(&intf->dev, "Weird descriptor references\n");
+		return -EINVAL;
+	}
+
+	if (!buflen) {
+		if (intf->cur_altsetting->endpoint &&
+				intf->cur_altsetting->endpoint->extralen &&
+				intf->cur_altsetting->endpoint->extra) {
+			dev_dbg(&intf->dev,
+				"Seeking extra descriptors on endpoint\n");
+			buflen = intf->cur_altsetting->endpoint->extralen;
+			buffer = intf->cur_altsetting->endpoint->extra;
+		} else {
+			dev_err(&intf->dev,
+				"Zero length descriptor references\n");
+			return -EINVAL;
+		}
+	}
+
+	while (buflen > 0) {
+		if (buffer[1] != USB_DT_CS_INTERFACE) {
+			dev_err(&intf->dev, "skipping garbage\n");
+			goto next_desc;
+		}
+
+		switch (buffer[2]) {
+		case USB_CDC_UNION_TYPE: /* we've found it */
+			if (union_header) {
+				dev_err(&intf->dev, "> 1  union descriptor");
+				dev_err(&intf->dev, "skipping ...\n");
+				goto next_desc;
+			}
+			union_header = (struct usb_cdc_union_desc *)buffer;
+			break;
+		case USB_CDC_HEADER_TYPE: /* maybe check version */
+			break; /* for now we ignore it */
+		case USB_CDC_ACM_TYPE:
+			ac_management_function = buffer[3];
+			break;
+		case USB_CDC_CALL_MANAGEMENT_TYPE:
+			call_management_function = buffer[3];
+			call_interface_num = buffer[4];
+			//if ((quirks & NOT_A_MODEM) == 0 &&
+			//	(call_management_function & 3) != 3)
+			//	dev_err(&intf->dev, "This device cannot
+			//		do calls on its own. It is
+			//		not a modem.\n");
+			break;
+		default:
+			/* there are LOTS more CDC descriptors that
+			 * could legitimately be found here.
+			 */
+			dev_dbg(&intf->dev, "Ignoring descriptor: ");
+			dev_dbg(&intf->dev, "type %02x, length %d\n",
+				buffer[2], buffer[0]);
+			break;
+		}
+next_desc:
+		buflen -= buffer[0];
+		buffer += buffer[0];
+	}
+
+	if (!union_header) {
+		if (call_interface_num > 0) {
+			dev_dbg(&intf->dev, "No union descriptor, using");
+			dev_dbg(&intf->dev, " call management descriptor\n");
+			/* quirks for Droids MuIn LCD */
+			if (quirks & NO_DATA_INTERFACE)
+				data_interface = usb_ifnum_to_if(usb_dev, 0);
+			else
+				data_interface = usb_ifnum_to_if(usb_dev,
+					(data_interface_num =
+						call_interface_num));
+			control_interface = intf;
+		} else {
+			if (intf->cur_altsetting->desc.bNumEndpoints != 3) {
+				dev_dbg(&intf->dev, "No union descriptor,");
+				dev_dbg(&intf->dev, "giving up\n");
+				return -ENODEV;
+			} else {
+				dev_warn(&intf->dev, "No union descriptor, ");
+				dev_warn(&intf->dev, "testing for castrated device\n");
+				combined_interfaces = 1;
+				control_interface = data_interface = intf;
+				goto look_for_collapsed_interface;
+			}
+		}
+	} else {
+		control_interface = usb_ifnum_to_if(usb_dev,
+			union_header->bMasterInterface0);
+		data_interface = usb_ifnum_to_if(usb_dev,
+			(data_interface_num = union_header->bSlaveInterface0));
+		if (!control_interface || !data_interface) {
+			dev_dbg(&intf->dev, "no interfaces\n");
+			return -ENODEV;
+		}
+	}
+
+	if (data_interface_num != call_interface_num) {
+		dev_dbg(&intf->dev, "Separate call control interface. ");
+		dev_dbg(&intf->dev, "That is not fully supported.\n");
+	}
+
+	if (control_interface == data_interface) {
+		/* some broken devices designed for windows work this way */
+		dev_warn(&intf->dev, "Control and data interfaces");
+		dev_warn(&intf->dev, "are not separated!\n");
+		combined_interfaces = 1;
+		/* a popular other OS doesn't use it */
+		quirks |= NO_CAP_LINE;
+		if (data_interface->cur_altsetting->desc.bNumEndpoints != 3) {
+			dev_err(&intf->dev, "This needs exactly 3 endpoints\n");
+			return -EINVAL;
+		}
+look_for_collapsed_interface:
+		for (i = 0; i < 3; i++) {
+			struct usb_endpoint_descriptor *ep;
+
+			ep = &data_interface->cur_altsetting->endpoint[i].desc;
+
+			if (usb_endpoint_is_int_in(ep))
+				epctrl = ep;
+			else if (usb_endpoint_is_bulk_out(ep))
+				epwrite = ep;
+			else if (usb_endpoint_is_bulk_in(ep))
+				epread = ep;
+			else
+				return -EINVAL;
+		}
+		if (!epctrl || !epread || !epwrite)
+			return -ENODEV;
+		else
+			goto made_compressed_probe;
+	}
+
+skip_normal_probe:
+
+	/*workaround for switched interfaces */
+	if (data_interface->cur_altsetting->desc.bInterfaceClass
+						!= CDC_DATA_INTERFACE_TYPE) {
+		if (control_interface->cur_altsetting->desc.bInterfaceClass
+						== CDC_DATA_INTERFACE_TYPE) {
+			struct usb_interface *t;
+
+			dev_dbg(&intf->dev,
+				"Your device has switched interfaces.\n");
+			t = control_interface;
+			control_interface = data_interface;
+			data_interface = t;
+		} else {
+			return -EINVAL;
+		}
+	}
+
+	/* Accept probe requests only for the control interface */
+	if (!combined_interfaces && intf != control_interface)
+		return -ENODEV;
+
+	if (!combined_interfaces && usb_interface_claimed(data_interface)) {
+		/* valid in this context */
+		dev_dbg(&intf->dev, "The data interface isn't available\n");
+		return -EBUSY;
+	}
+
+
+	if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 ||
+	    control_interface->cur_altsetting->desc.bNumEndpoints == 0)
+		return -EINVAL;
+
+	epctrl = &control_interface->cur_altsetting->endpoint[0].desc;
+	epread = &data_interface->cur_altsetting->endpoint[0].desc;
+	epwrite = &data_interface->cur_altsetting->endpoint[1].desc;
+
+
+	/* workaround for switched endpoints */
+	if (!usb_endpoint_dir_in(epread)) {
+		/* descriptors are swapped */
+		struct usb_endpoint_descriptor *t;
+
+		dev_dbg(&intf->dev,
+			"The data interface has switched endpoints\n");
+		t = epread;
+		epread = epwrite;
+		epwrite = t;
+	}
+made_compressed_probe:
+	dev_dbg(&intf->dev, "interfaces are valid\n");
+
+	xrusb = kzalloc(sizeof(struct xrusb), GFP_KERNEL);
+	if (xrusb == NULL) {
+		dev_err(&intf->dev, "out of memory (xrusb kzalloc)\n");
+		goto alloc_fail;
+	}
+
+	minor = xrusb_alloc_minor(xrusb);
+	if (minor == XRUSB_TTY_MINORS) {
+		dev_err(&intf->dev, "no more free xrusb devices\n");
+		kfree(xrusb);
+		return -ENODEV;
+	}
+
+	ctrlsize = usb_endpoint_maxp(epctrl);
+	readsize = usb_endpoint_maxp(epread) *
+				(quirks == SINGLE_RX_URB ? 1 : 2);
+	xrusb->combined_interfaces = combined_interfaces;
+	xrusb->writesize = usb_endpoint_maxp(epwrite) * 20;
+	xrusb->control = control_interface;
+	xrusb->data = data_interface;
+	xrusb->minor = minor;
+	xrusb->dev = usb_dev;
+	xrusb->ctrl_caps = ac_management_function;
+	if (quirks & NO_CAP_LINE)
+		xrusb->ctrl_caps &= ~USB_CDC_CAP_LINE;
+	xrusb->ctrlsize = ctrlsize;
+	xrusb->readsize = readsize;
+	xrusb->rx_buflimit = num_rx_buf;
+	INIT_WORK(&xrusb->work, xrusb_softint);
+	spin_lock_init(&xrusb->write_lock);
+	spin_lock_init(&xrusb->read_lock);
+	mutex_init(&xrusb->mutex);
+	xrusb->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress);
+	xrusb->is_int_ep = usb_endpoint_xfer_int(epread);
+	if (xrusb->is_int_ep)
+		xrusb->bInterval = epread->bInterval;
+	tty_port_init(&xrusb->port);
+	xrusb->port.ops = &xrusb_port_ops;
+	xrusb->DeviceVendor = id->idVendor;
+	xrusb->DeviceProduct = id->idProduct;
+
+	xrusb->channel = epwrite->bEndpointAddress;
+	//dev_err(&intf->dev, "epwrite->bEndpointAddress =%d\n",
+	//	epwrite->bEndpointAddress);
+
+	buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL,
+		&xrusb->ctrl_dma);
+	if (!buf) {
+		dev_err(&intf->dev, "out of memory (ctrl buffer alloc)\n");
+		goto alloc_fail2;
+	}
+	xrusb->ctrl_buffer = buf;
+
+	if (xrusb_write_buffers_alloc(xrusb) < 0) {
+		dev_err(&intf->dev, "out of memory (write buffer alloc)\n");
+		goto alloc_fail4;
+	}
+
+	xrusb->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!xrusb->ctrlurb) {
+		dev_err(&intf->dev, "out of memory (ctrlurb kmalloc)\n");
+		goto alloc_fail5;
+	}
+	for (i = 0; i < num_rx_buf; i++) {
+		struct xrusb_rb *rb = &(xrusb->read_buffers[i]);
+		struct urb *urb;
+
+		rb->base = usb_alloc_coherent(xrusb->dev, readsize, GFP_KERNEL,
+								&rb->dma);
+		if (!rb->base) {
+			dev_err(&intf->dev, "out of memory ");
+			dev_err(&intf->dev, "(read bufs usb_alloc_coherent)\n");
+			goto alloc_fail6;
+		}
+		rb->index = i;
+		rb->instance = xrusb;
+
+		urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!urb) {
+			dev_err(&intf->dev, "out of memory ");
+			dev_err(&intf->dev, "(read bufs usb_alloc_urb)\n");
+			goto alloc_fail6;
+		}
+		urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+		urb->transfer_dma = rb->dma;
+		if (xrusb->is_int_ep) {
+			usb_fill_int_urb(urb, xrusb->dev,
+					 xrusb->rx_endpoint,
+					 rb->base,
+					 xrusb->readsize,
+					 xrusb_read_bulk_callback, rb,
+					 xrusb->bInterval);
+		} else {
+			usb_fill_bulk_urb(urb, xrusb->dev,
+					  xrusb->rx_endpoint,
+					  rb->base,
+					  xrusb->readsize,
+					  xrusb_read_bulk_callback, rb);
+		}
+
+		xrusb->read_urbs[i] = urb;
+		__set_bit(i, &xrusb->read_urbs_free);
+	}
+	for (i = 0; i < XRUSB_NW; i++) {
+		struct xrusb_wb *snd = &(xrusb->wb[i]);
+
+		snd->urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (snd->urb == NULL) {
+			dev_err(&intf->dev, "out of memory ");
+			dev_err(&intf->dev, "(write urbs usb_alloc_urb)\n");
+			goto alloc_fail7;
+		}
+
+		if (usb_endpoint_xfer_int(epwrite))
+			usb_fill_int_urb(snd->urb,
+				usb_dev,
+				usb_sndintpipe(usb_dev,
+					epwrite->bEndpointAddress),
+				NULL,
+				xrusb->writesize,
+				xrusb_write_bulk,
+				snd,
+				epwrite->bInterval);
+		else
+			usb_fill_bulk_urb(snd->urb,
+				usb_dev,
+				usb_sndbulkpipe(usb_dev,
+					epwrite->bEndpointAddress),
+				NULL,
+				xrusb->writesize,
+				xrusb_write_bulk,
+				snd);
+		snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+		snd->instance = xrusb;
+	}
+
+	usb_set_intfdata(intf, xrusb);
+
+	i = device_create_file(&intf->dev, &dev_attr_bmCapabilities);
+	if (i < 0)
+		goto alloc_fail7;
+
+	usb_fill_int_urb(xrusb->ctrlurb, usb_dev,
+			 usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress),
+			 xrusb->ctrl_buffer, ctrlsize, xrusb_ctrl_irq, xrusb,
+			 /* works around buggy devices */
+			 epctrl->bInterval ? epctrl->bInterval : 0xff);
+	xrusb->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+	xrusb->ctrlurb->transfer_dma = xrusb->ctrl_dma;
+
+	//dev_info(&intf->dev, "ttyXRUSB%d: USB XRUSB device\n", minor);
+
+	xrusb_reg_init(xrusb);
+
+	xrusb_set_control(xrusb, xrusb->ctrlout);
+
+	xrusb->line.dwDTERate = cpu_to_le32(9600);
+	xrusb->line.bDataBits = 8;
+	xrusb_set_line(xrusb, &xrusb->line);
+
+	usb_driver_claim_interface(&xrusb_driver, data_interface, xrusb);
+	usb_set_intfdata(data_interface, xrusb);
+
+	usb_get_intf(control_interface);
+
+	tty_dev = tty_port_register_device(&xrusb->port,
+		xrusb_tty_driver, minor, &control_interface->dev);
+
+	if (IS_ERR(tty_dev)) {
+		rv = PTR_ERR(tty_dev);
+		goto alloc_fail8;
+	}
+
+	return 0;
+
+alloc_fail8:
+	device_remove_file(&xrusb->control->dev, &dev_attr_bmCapabilities);
+alloc_fail7:
+	usb_set_intfdata(intf, NULL);
+	for (i = 0; i < XRUSB_NW; i++)
+		usb_free_urb(xrusb->wb[i].urb);
+alloc_fail6:
+	for (i = 0; i < num_rx_buf; i++)
+		usb_free_urb(xrusb->read_urbs[i]);
+	xrusb_read_buffers_free(xrusb);
+	usb_free_urb(xrusb->ctrlurb);
+alloc_fail5:
+	xrusb_write_buffers_free(xrusb);
+alloc_fail4:
+	usb_free_coherent(usb_dev, ctrlsize,
+		xrusb->ctrl_buffer, xrusb->ctrl_dma);
+alloc_fail2:
+	xrusb_release_minor(xrusb);
+	kfree(xrusb);
+alloc_fail:
+	return rv;
+}
+
+static void stop_data_traffic(struct xrusb *xrusb)
+{
+	int i;
+
+	dev_dbg(&xrusb->control->dev, "%s\n", __func__);
+
+	usb_kill_urb(xrusb->ctrlurb);
+	for (i = 0; i < XRUSB_NW; i++)
+		usb_kill_urb(xrusb->wb[i].urb);
+	for (i = 0; i < xrusb->rx_buflimit; i++)
+		usb_kill_urb(xrusb->read_urbs[i]);
+
+	cancel_work_sync(&xrusb->work);
+}
+
+static void xrusb_disconnect(struct usb_interface *intf)
+{
+	struct xrusb *xrusb = usb_get_intfdata(intf);
+	struct tty_struct *tty;
+
+	/* sibling interface is already cleaning up */
+	if (!xrusb)
+		return;
+
+	mutex_lock(&xrusb->mutex);
+	xrusb->disconnected = true;
+
+	device_remove_file(&xrusb->control->dev,
+		&dev_attr_bmCapabilities);
+	usb_set_intfdata(xrusb->control, NULL);
+	usb_set_intfdata(xrusb->data, NULL);
+	mutex_unlock(&xrusb->mutex);
+
+	tty = tty_port_tty_get(&xrusb->port);
+	if (tty) {
+		tty_vhangup(tty);
+		tty_kref_put(tty);
+	}
+
+	stop_data_traffic(xrusb);
+
+	tty_unregister_device(xrusb_tty_driver, xrusb->minor);
+
+	xrusb_write_buffers_free(xrusb);
+	usb_free_coherent(xrusb->dev, xrusb->ctrlsize,
+		xrusb->ctrl_buffer, xrusb->ctrl_dma);
+	xrusb_read_buffers_free(xrusb);
+
+	if (!xrusb->combined_interfaces)
+		usb_driver_release_interface(&xrusb_driver,
+			intf == xrusb->control ?
+			xrusb->data : xrusb->control);
+
+	tty_port_put(&xrusb->port);
+}
+
+#ifdef CONFIG_PM
+static int xrusb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct xrusb *xrusb = usb_get_intfdata(intf);
+	int cnt;
+
+	if (PMSG_IS_AUTO(message)) {
+		int b;
+
+		spin_lock_irq(&xrusb->write_lock);
+		b = xrusb->transmitting;
+		spin_unlock_irq(&xrusb->write_lock);
+		if (b)
+			return -EBUSY;
+	}
+
+	spin_lock_irq(&xrusb->read_lock);
+	spin_lock(&xrusb->write_lock);
+	cnt = xrusb->susp_count++;
+	spin_unlock(&xrusb->write_lock);
+	spin_unlock_irq(&xrusb->read_lock);
+
+	if (cnt)
+		return 0;
+
+	if (test_bit(ASYNCB_INITIALIZED, &xrusb->port.flags))
+		stop_data_traffic(xrusb);
+
+	return 0;
+}
+
+static int xrusb_resume(struct usb_interface *intf)
+{
+	struct xrusb *xrusb = usb_get_intfdata(intf);
+	struct xrusb_wb *wb;
+	int rv = 0;
+	int cnt;
+
+	xrusb_reg_init(xrusb);
+
+	spin_lock_irq(&xrusb->read_lock);
+	xrusb->susp_count -= 1;
+	cnt = xrusb->susp_count;
+	spin_unlock_irq(&xrusb->read_lock);
+
+	if (cnt)
+		return 0;
+
+	if (test_bit(ASYNCB_INITIALIZED, &xrusb->port.flags)) {
+		rv = usb_submit_urb(xrusb->ctrlurb, GFP_NOIO);
+
+		spin_lock_irq(&xrusb->write_lock);
+		if (xrusb->delayed_wb) {
+			wb = xrusb->delayed_wb;
+			xrusb->delayed_wb = NULL;
+			spin_unlock_irq(&xrusb->write_lock);
+			xrusb_start_wb(xrusb, wb);
+		} else {
+			spin_unlock_irq(&xrusb->write_lock);
+		}
+
+		/*
+		 * delayed error checking because we must
+		 * do the write path at all cost
+		 */
+		if (rv < 0)
+			goto err_out;
+
+		rv = xrusb_submit_read_urbs(xrusb, GFP_NOIO);
+	}
+
+err_out:
+	return rv;
+}
+
+static int xrusb_reset_resume(struct usb_interface *intf)
+{
+	struct xrusb *xrusb = usb_get_intfdata(intf);
+
+	if (test_bit(ASYNCB_INITIALIZED, &xrusb->port.flags))
+		tty_port_tty_hangup(&xrusb->port, false);
+	return xrusb_resume(intf);
+}
+
+#endif /* CONFIG_PM */
+
+/*
+ * USB driver structure.
+ */
+static const struct usb_device_id xrusb_ids[] = {
+	{ USB_DEVICE(0x04e2, 0x1410)},
+	{ USB_DEVICE(0x04e2, 0x1411)},
+	{ USB_DEVICE(0x04e2, 0x1412)},
+	{ USB_DEVICE(0x04e2, 0x1414)},
+	{ USB_DEVICE(0x04e2, 0x1420)},
+	{ USB_DEVICE(0x04e2, 0x1422)},
+	{ USB_DEVICE(0x04e2, 0x1424)},
+	{ USB_DEVICE(0x04e2, 0x1400)}, // XR2280x ch A
+	{ USB_DEVICE(0x04e2, 0x1401)}, // XR2280x ch B
+	{ USB_DEVICE(0x04e2, 0x1402)}, // XR2280x ch C
+	{ USB_DEVICE(0x04e2, 0x1403)}, // XR2280x ch D
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, xrusb_ids);
+
+static struct usb_driver xrusb_driver = {
+	.name =			"xrusb_serial",
+	.probe =		xrusb_probe,
+	.disconnect =		xrusb_disconnect,
+#ifdef CONFIG_PM
+	.suspend =		xrusb_suspend,
+	.resume =		xrusb_resume,
+	.reset_resume =		xrusb_reset_resume,
+#endif
+	.id_table =		xrusb_ids,
+#ifdef CONFIG_PM
+	.supports_autosuspend = 1,
+#endif
+	.disable_hub_initiated_lpm = 1,
+};
+
+/*
+ * TTY driver structures.
+ */
+
+static const struct tty_operations xrusb_ops = {
+	.install =		xrusb_tty_install,
+	.open =			xrusb_tty_open,
+	.close =		xrusb_tty_close,
+	.cleanup =		xrusb_tty_cleanup,
+	.hangup =		xrusb_tty_hangup,
+	.write =		xrusb_tty_write,
+	.write_room =		xrusb_tty_write_room,
+	.ioctl =		xrusb_tty_ioctl,
+	.throttle =		xrusb_tty_throttle,
+	.unthrottle =		xrusb_tty_unthrottle,
+	.chars_in_buffer =	xrusb_tty_chars_in_buffer,
+	.break_ctl =		xrusb_tty_break_ctl,
+	.set_termios =		xrusb_tty_set_termios,
+	.tiocmget =		xrusb_tty_tiocmget,
+	.tiocmset =		xrusb_tty_tiocmset,
+};
+
+/*
+ * Init / exit.
+ */
+
+static int __init xrusb_init(void)
+{
+	int rv;
+
+	xrusb_tty_driver = alloc_tty_driver(XRUSB_TTY_MINORS);
+	if (!xrusb_tty_driver)
+		return -ENOMEM;
+	xrusb_tty_driver->driver_name = "xrusb",
+	xrusb_tty_driver->name = "ttyXRUSB",
+	xrusb_tty_driver->major = XRUSB_TTY_MAJOR,
+	xrusb_tty_driver->minor_start = 0,
+	xrusb_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
+	xrusb_tty_driver->subtype = SERIAL_TYPE_NORMAL,
+	xrusb_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	xrusb_tty_driver->init_termios = tty_std_termios;
+	xrusb_tty_driver->init_termios.c_cflag = B9600 | CS8 |
+		CREAD | HUPCL | CLOCAL;
+	tty_set_operations(xrusb_tty_driver, &xrusb_ops);
+
+	rv = tty_register_driver(xrusb_tty_driver);
+	if (rv) {
+		put_tty_driver(xrusb_tty_driver);
+		return rv;
+	}
+
+	rv = usb_register(&xrusb_driver);
+	if (rv) {
+		tty_unregister_driver(xrusb_tty_driver);
+		put_tty_driver(xrusb_tty_driver);
+		return rv;
+	}
+
+	printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n");
+
+	return 0;
+}
+
+static void __exit xrusb_exit(void)
+{
+	usb_deregister(&xrusb_driver);
+	tty_unregister_driver(xrusb_tty_driver);
+	put_tty_driver(xrusb_tty_driver);
+}
+
+module_init(xrusb_init);
+module_exit(xrusb_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_CHARDEV_MAJOR(XRUSB_TTY_MAJOR);
diff --git a/drivers/usb/serial/xrusb_serial.h b/drivers/usb/serial/xrusb_serial.h
new file mode 100644
index 000000000000..c4b214b3e576
--- /dev/null
+++ b/drivers/usb/serial/xrusb_serial.h
@@ -0,0 +1,448 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Includes for xrusb_serial.c
+ */
+
+/*
+ * CMSPAR, some architectures can't have space and mark parity.
+ */
+
+#ifndef CMSPAR
+#define CMSPAR		0
+#endif
+
+/*
+ * Major and minor numbers.
+ */
+
+#define XRUSB_TTY_MAJOR		266
+#define XRUSB_TTY_MINORS	256
+
+/*
+ * Requests.
+ */
+
+#define USB_RT_XRUSB		(USB_TYPE_CLASS | USB_RECIP_INTERFACE)
+
+/*
+ * Output control lines.
+ */
+
+#define XRUSB_CTRL_DTR		0x01
+#define XRUSB_CTRL_RTS		0x02
+
+/*
+ * Input control lines and line errors.
+ */
+
+#define XRUSB_CTRL_DCD		0x01
+#define XRUSB_CTRL_DSR		0x02
+#define XRUSB_CTRL_BRK		0x04
+#define XRUSB_CTRL_RI		0x08
+
+#define XRUSB_CTRL_FRAMING	0x10
+#define XRUSB_CTRL_PARITY	0x20
+#define XRUSB_CTRL_OVERRUN	0x40
+
+/*
+ * Internal driver structures.
+ */
+
+/*
+ * The only reason to have several buffers is to accommodate assumptions
+ * in line disciplines. They ask for empty space amount, receive our URB size,
+ * and proceed to issue several 1-character writes, assuming they will fit.
+ * The very first write takes a complete URB. Fortunately, this only happens
+ * when processing onlcr, so we only need 2 buffers. These values must be
+ * powers of 2.
+ */
+
+#define XRUSB_NW  16
+#define XRUSB_NR  16
+
+#define WIDE_MODE_PARITY	0x1
+#define WIDE_MODE_BREAK		0x2
+#define WIDE_MODE_FRAME		0x4
+#define WIDE_MODE_OVERRUN	0x8
+
+struct xrusb_wb {
+	unsigned char *buf;
+	dma_addr_t dmah;
+	int len;
+	int use;
+	struct urb	*urb;
+	struct xrusb	*instance;
+};
+
+struct xrusb_rb {
+	int	size;
+	unsigned char	*base;
+	dma_addr_t	dma;
+	int	index;
+	struct xrusb	*instance;
+};
+
+struct reg_addr_map {
+	unsigned int uart_enable;
+	unsigned int uart_format;
+	unsigned int uart_flow;
+	unsigned int uart_xon_char;
+	unsigned int uart_xoff_char;
+	unsigned int uart_tx_break;
+	unsigned int uart_rs485_delay;
+	unsigned int uart_gpio_mode;
+	unsigned int uart_gpio_dir;
+	unsigned int uart_gpio_set;
+	unsigned int uart_gpio_clr;
+	unsigned int uart_gpio_status;
+	unsigned int uart_gpio_int_mask;
+	unsigned int uart_customized_int;
+	unsigned int uart_gpio_open_drain;
+	unsigned int uart_gpio_pull_up_enable;
+	unsigned int uart_gpio_pull_down_enable;
+	unsigned int uart_loopback;
+	unsigned int uart_low_latency;
+	unsigned int uart_custom_driver;
+};
+
+struct xrusb_setting {
+	unsigned int txcvr_mode1_pin;
+	unsigned int txcvr_mode0_pin;
+	unsigned int txcvr_dir_pin;
+	unsigned int txcvr_term_pin;
+	unsigned int txcvr_slew_pin;
+	unsigned int txcvr_mode;
+	unsigned int term_mode;
+	unsigned int slew_mode;
+	unsigned int low_latency_mode;
+};
+
+struct xrusb {
+	struct usb_device *dev;				/* the corresponding usb device */
+	struct usb_interface *control;			/* control interface */
+	struct usb_interface *data;			/* data interface */
+	struct tty_port port;				/* our tty port data */
+	struct urb *ctrlurb;				/* urbs */
+	u8 *ctrl_buffer;				/* buffers of urbs */
+	dma_addr_t ctrl_dma;				/* dma handles of buffers */
+	struct xrusb_wb wb[XRUSB_NW];
+	unsigned long read_urbs_free;
+	struct urb *read_urbs[XRUSB_NR];
+	struct xrusb_rb read_buffers[XRUSB_NR];
+	struct xrusb_wb *putbuffer;
+	int rx_buflimit;
+	int rx_endpoint;
+	spinlock_t read_lock;
+	int transmitting;
+	spinlock_t write_lock;
+	struct mutex mutex;
+	bool disconnected;
+	struct usb_cdc_line_coding line;		/* bits, stop, parity */
+	struct work_struct work;			/* work queue entry for line discipline waking up */
+	unsigned int ctrlin;				/* input control lines (DCD, DSR, RI, break, overruns) */
+	unsigned int ctrlout;				/* output control lines (DTR, RTS) */
+	unsigned int writesize;				/* max packet size for the output bulk endpoint */
+	unsigned int readsize, ctrlsize;			/* buffer sizes for freeing */
+	unsigned int minor;				/* xrusb minor number */
+	unsigned char clocal;				/* termios CLOCAL */
+	unsigned int ctrl_caps;				/* control capabilities from the class specific header */
+	unsigned int susp_count;			/* number of suspended interfaces */
+	unsigned int combined_interfaces:1;		/* control and data collapsed */
+	unsigned int is_int_ep:1;			/* interrupt endpoints contrary to spec used */
+	unsigned int throttled:1;			/* actually throttled */
+	unsigned int throttle_req:1;			/* throttle requested */
+	u8 bInterval;
+
+	struct xrusb_wb *delayed_wb;		/* write queued for a device about to be woken */
+	unsigned int channel;
+	int wide_mode;					/* USB: wide mode, TTY: flags per character */
+	int have_extra_byte;
+	int extra_byte;
+
+	unsigned short DeviceVendor;
+	unsigned short DeviceProduct;
+
+	int found_smbios_caracalla_config;		/* Modes pre-programmed in BIOS for Caracalla board */
+	unsigned char channel_config;			/* Mode setting for each channel */
+
+	struct reg_addr_map reg_map;
+	struct xrusb_setting port_setting;
+	unsigned char have_txcvr_config;
+};
+
+/*
+ * SMBIOS code snippets below borrowed from
+ * https://sourceforge.net/projects/smbios/
+ *
+ */
+
+#ifndef __SM_BIOS_H__
+#define __SM_BIOS_H__
+
+#ifdef _EN_DEBUG_
+#define SM_BIOS_DEBUG(fmt, args...) printk(KERN_DEBUG "smbios: " fmt, ## args)
+#else
+#define SM_BIOS_DEBUG(fmt, args...) /* not debugging: nothing */
+#endif
+
+/*
+ *   Magic numbers
+ */
+
+/** start address of BIOS segment to scanned for SM-BIOS and DMI-BIOS */
+#define BIOS_START_ADDRESS      0xF0000
+/** length of the scanned BIOS area for SM-BIOS and DMI-BIOS */
+#define BIOS_MAP_LENGTH         0x10000
+/** magic 4 bytes to identify SM-BIOS entry point, paragraph boundary */
+#define SMBIOS_MAGIC_DWORD      0x5F4D535F /* anchor string "_SM_" */
+/** identifier for SM-BIOS structures within SM-BIOS entry point */
+#define DMI_STRING              "_DMI_"
+
+/*
+ *   Structures
+ */
+
+/** SM-BIOS entry point structure
+ * the SMBIOS Entry Point structure described below can be located by
+ * application software by searching for the anchor string on paragraph
+ * (16 byte) boundaries within the physical memory address range 000F0000h to
+ * 000FFFFFh. This entry point encapsulates an intermediate anchor string
+ * that is used by some existing DMI browsers.
+ *
+ * @note While the SMBIOS Major and Minor Versions (offsets 06h and 07h)
+ * currently duplicate the information present in the SMBIOS BCD Revision
+ * (offset 1Dh), they provide a path for future growth in this specification.
+ * The BCD Revision, for example, provides only a single digit for each of
+ * the major and minor version numbers.
+ */
+struct smbios_entry_point_struct {
+	/** "_SM_", specified as four ASCII characters (5F 53 4D 5F) */
+	__u32 anchor_string;
+	/** checksum of the Entry Point Structure (EPS). This value, when added to
+	 * all other bytes in the EPS, will result in the value 00h (using 8 bit
+	 * addition calculations). Values in the EPS are summed starting at offset
+	 * 00h, for Entry Point Length bytes.
+	 */
+	__u8  entry_point_checksum;
+	/** Length of the Entry Point Structure, starting with the Anchor String
+	 * field, in bytes, currently 1Fh.
+	 */
+	__u8  entry_point_length;
+	/** identifies the major version of this specification implemented in
+	 * the table structures, e.g. the value will be 0Ah for revision 10.22
+	 * and 02h for revision 2.1
+	 */
+	__u8  major_version;
+	/** identifies the minor version of this specification implemented in
+	 * the table structures, e.g. the value will be 16h for revision 10.22
+	 * and 01h for revision 2.1
+	 */
+	__u8  minor_version;
+	/** size of the largest SMBIOS structure, in bytes, and encompasses the
+	 * structure's formatted area and text strings. This is the value returned
+	 * as StructureSize from the Plug-n-Play 'Get SMBIOS Information' function
+	 */
+	__u16 max_struct_size;
+	/** identifies the EPS revision implemented in this structure and identifies
+	 * the formatting of offsets 0Bh to 0Fh, one of:
+	 * 00h     Entry Point based on SMBIOS 2.1 definition; formatted area is
+	 *         reserved and set to all 00h.
+	 * 01h-FFh reserved for assignment via this specification
+	 */
+	__u8  revision;
+	/** the value present in the Entry Point Revision field defines the
+	 * interpretation to be placed upon these5 bytes.
+	 */
+	__u8  formated_area[5];
+	/** "_DMI_", specified as five ASCII characters (5F 44 4D 49 5F) */
+	__u8  intermediate_string[5];
+	/** checksum of the Intermediate Entry Point Structure (IEPS). This value,
+	 * when added to all other bytes in the IEPS, will result in the value 00h
+	 * (using 8 bit addition calculations). Values in the IEPS are summed
+	 * starting at offset 10h, for 0Fh bytes
+	 */
+	__u8  intermediate_checksum;
+	/** the 32 bit physical starting address of the read-only SMBIOS Structure
+	 * Table, that can start at any 32 bit address. This area contains all of the
+	 * SMBIOS structures fully packed together. These structures can then be
+	 * parsed to produce exactly the same format as that returned from a 'Get
+	 * SMBIOS Structure' function call.
+	 */
+	__u16 struct_table_length;
+	__u32 struct_table_address;
+	__u16 no_of_structures;
+	__u8  bcd_revision;
+} __attribute__ ((packed));
+
+/** SM-BIOS structure header */
+struct smbios_struct {
+	__u8  type;
+	__u8  length;
+	__u16 handle;
+	__u8  subtype;
+/* ... other fields are structure dependend ... */
+} __attribute__ ((packed));
+
+extern void *smbios_structures_base;                       /* base of SMBIOS raw structures */
+extern unsigned char smbios_types_with_subtypes[];
+extern char smbios_version_string[32];                      /* e.g. V2.31 */
+
+/*
+ *   Functions
+ */
+
+/* for the description see the implementation file */
+struct smbios_entry_point_struct *smbios_find_entry_point(void *base);
+struct dmibios_entry_point_struct *dmibios_find_entry_point(void *base);
+unsigned char smbios_check_entry_point(void *addr);
+int smbios_type_has_subtype(unsigned char type);
+int smbios_get_struct_length(struct smbios_struct *struct_ptr);
+int dmibios_get_struct_length(struct smbios_struct *struct_ptr);
+unsigned int smbios_get_readable_name_ext(char *readable_name, struct smbios_struct *struct_ptr);
+unsigned int smbios_get_readable_name(char *readable_name, struct smbios_struct *struct_ptr);
+int smbios_check_if_have_exar_config(unsigned char *config0, unsigned char *config1);
+
+
+#endif /* __BIOS_H__ */
+
+static void *smbios_base;
+struct smbios_entry_point_struct *smbios_entry_point;	/** SM-BIOS entry point structure */
+void *smbios_structures_base;
+
+#define CDC_DATA_INTERFACE_TYPE	0x0a
+
+/* constants describing various quirks and errors */
+#define NO_UNION_NORMAL			1
+#define SINGLE_RX_URB			2
+#define NO_CAP_LINE			4
+#define NOT_A_MODEM			8
+#define NO_DATA_INTERFACE		16
+#define IGNORE_DEVICE			32
+
+/* USB Requests */
+#define XRUSB_GET_CHIP_ID		0xFF
+#define XRUSB_SET_XR2280X		5
+#define XRUSB_GET_XR2280X		5
+#define XRUSB_SET_XR21B142X		0
+#define XRUSB_GET_XR21B142X		0
+#define XRUSB_SET_XR21V141X		0
+#define XRUSB_GET_XR21V141X		1
+#define XRUSB_SET_XR21B1411		0
+#define XRUSB_GET_XR21B1411		1
+
+/* XR21V141x Baud Rate Generator Registers */
+#define XR21V141X_CLOCK_DIVISOR_0	0x004
+#define XR21V141X_CLOCK_DIVISOR_1	0x005
+#define XR21V141X_CLOCK_DIVISOR_2	0x006
+#define XR21V141X_TX_CLOCK_MASK_0	0x007
+#define XR21V141X_TX_CLOCK_MASK_1	0x008
+#define XR21V141X_RX_CLOCK_MASK_0	0x009
+#define XR21V141X_RX_CLOCK_MASK_1	0x00a
+
+/* XR21V141x UART Block Registers */
+#define XR21V141x_URM_REG_BLOCK		4
+#define XR21V141x_UART_CUSTOM_BLOCK	0x66
+
+/* XR21V141x UART Manager Register values */
+#define XR21V141x_URM_FIFO_ENABLE_REG	0x10
+#define XR21V141x_URM_ENABLE_TX_FIFO	0x1
+#define XR21V141x_URM_ENABLE_RX_FIFO	0x2
+#define XR21V141x_URM_RX_FIFO_RESET	0x18
+#define XR21V141x_URM_TX_FIFO_RESET	0x1C
+
+#define UART_ENABLE_TX			1
+#define UART_ENABLE_RX			2
+
+/* GPIOs */
+#define GPIO9_RXT			0x200
+#define GPIO8_TXT			0x100
+#define GPIO7_XEN			0x80
+#define GPIO6_CLK			0x40
+#define GPIO5_RTS			0x20
+#define GPIO4_CTS			0x10
+#define GPIO3_DTR			0x8
+#define GPIO2_DSR			0x4
+#define GPIO1_CD			0x2
+#define GPIO0_RI			0x1
+
+/* Loopback Register Values */
+
+#define LOOP_ENABLE			0x7
+#define XR21V141x_LOOP_ENABLE_A		0x40
+#define XR21V141x_LOOP_ENABLE_B		0x41
+#define XR21V141x_LOOP_ENABLE_C		0x42
+#define XR21V141x_LOOP_ENABLE_D		0x43
+
+/* FLOW_MODE Register Values */
+
+#define UART_FLOW_MODE_NONE		0x0
+#define UART_FLOW_MODE_HW		0x1
+#define UART_FLOW_MODE_SW		0x2
+
+/* GPIO_MODE Register Values */
+
+#define UART_GPIO_MODE_SEL_GPIO		0x0
+#define UART_GPIO_MODE_SEL_RTS_CTS	0x1
+#define UART_GPIO_MODE_DIR_RTS_HI	0xb
+#define UART_GPIO_MODE_DIR_RTS_LOW	0x3
+
+/* Additional GPIO_MODE Register Values - XR21B142x */
+
+#define UART_GPIO_MODE_DIR_XEN_HI	0x38
+#define UART_GPIO_MODE_DIR_XEN_LOW	0x30
+#define UART_GPIO_MODE_RXT		0x200
+#define UART_GPIO_MODE_TXT		0x100
+
+/* Randomly located registers */
+#define XR21V141X_WIDE_MODE_REG         3
+
+#define XR21B1411_UART_ENABLE		0xC00
+#define XR21B1411_WIDE_MODE_REG		0xD02
+
+#define XR21B142x_UART_ENABLE		0x00
+#define XR21B142X_TX_WIDE_MODE_REG	0x42
+#define XR21B142X_RX_WIDE_MODE_REG	0x45
+
+#define XR2280X_TX_WIDE_MODE_REG	0x62
+#define XR2280X_RX_WIDE_MODE_REG	0x65
+
+#define XR2280x_FUNC_MGR_OFFSET		0x40
+
+#define XRUSB_IOC_MAGIC			'v'
+
+#define XRUSB_ENABLE			_IO(XRUSB_IOC_MAGIC, 3)
+#define XRUSB_SET_BAUD			_IO(XRUSB_IOC_MAGIC, 4)
+#define XRUSB_LOOPBACK			_IO(XRUSB_IOC_MAGIC, 5)
+
+#define XRUSB_SET_GPIO_MODE		_IO(XRUSB_IOC_MAGIC, 7)
+#define	XRUSB_GET_GPIO_MODE		_IO(XRUSB_IOC_MAGIC, 8)
+#define	XRUSB_SET_GPIO_STATE		_IO(XRUSB_IOC_MAGIC, 9)
+#define	XRUSB_GET_GPIO_STATE		_IO(XRUSB_IOC_MAGIC, 10)
+#define XRUSB_CLEAR_GPIO_STATE		_IO(XRUSB_IOC_MAGIC, 11)
+#define	XRUSB_SET_GPIO_INT_MASK		_IO(XRUSB_IOC_MAGIC, 12)
+#define	XRUSB_GET_GPIO_INT_MASK		_IO(XRUSB_IOC_MAGIC, 13)
+#define XRUSB_SET_GPIO_PULL_UP		_IO(XRUSB_IOC_MAGIC, 14)
+#define	XRUSB_GET_GPIO_PULL_UP		_IO(XRUSB_IOC_MAGIC, 15)
+#define XRUSB_SET_GPIO_PULL_DOWN	_IO(XRUSB_IOC_MAGIC, 16)
+#define	XRUSB_GET_GPIO_PULL_DOWN	_IO(XRUSB_IOC_MAGIC, 17)
+#define XRUSB_SET_GPIO_OPEN_DRAIN	_IO(XRUSB_IOC_MAGIC, 18)
+#define	XRUSB_GET_GPIO_OPEN_DRAIN	_IO(XRUSB_IOC_MAGIC, 19)
+
+#define XRUSB_WIDE_MODE			_IO(XRUSB_IOC_MAGIC, 20)
+#define XRUSB_MULTIDROP_MODE		_IO(XRUSB_IOC_MAGIC, 21)
+#define XRUSB_LOW_LATENCY_MODE		_IO(XRUSB_IOC_MAGIC, 22)
+#define XRUSB_SET_RS485_PIN		_IO(XRUSB_IOC_MAGIC, 23)
+#define XRUSB_RS485_PIN_OPTION		_IO(XRUSB_IOC_MAGIC, 25)
+#define XRUSB_RS485_DELAY		_IO(XRUSB_IOC_MAGIC, 26)
+#define XRUSB_ENABLE_RS485_LOW		_IO(XRUSB_IOC_MAGIC, 27)
+
+#define XRUSB_ENABLE_DTRDSR_FLOW	_IO(XRUSB_IOC_MAGIC, 28)
+#define XRUSB_DISABLE_FLOW_CONTROL	_IO(XRUSB_IOC_MAGIC, 29)
+#define XRUSB_GET_FLOW_CONTROL		_IO(XRUSB_IOC_MAGIC, 30)
+#define XRUSB_SET_GPIO_DIR		_IO(XRUSB_IOC_MAGIC, 31)
+#define XRUSB_GET_GPIO_DIR		_IO(XRUSB_IOC_MAGIC, 32)
+#define XRUSB_SET_TXCVR_MODE		_IO(XRUSB_IOC_MAGIC, 33)
+#define XRUSB_GET_TXCVR_MODE		_IO(XRUSB_IOC_MAGIC, 34)
+#define XRUSB_SET_TERM_MODE		_IO(XRUSB_IOC_MAGIC, 35)
+#define XRUSB_SET_SLEW_MODE		_IO(XRUSB_IOC_MAGIC, 36)
+#define XRUSB_SET_GPIO_MAP_TABLE	_IO(XRUSB_IOC_MAGIC, 37)

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial Adapters
@ 2018-08-16 10:05 Greg Kroah-Hartman
  0 siblings, 0 replies; 15+ messages in thread
From: Greg Kroah-Hartman @ 2018-08-16 10:05 UTC (permalink / raw)
  To: Patong Yang; +Cc: pyang, johan, linux-usb

On Thu, Aug 16, 2018 at 01:28:54AM -0700, Patong Yang wrote:
> On Thu, Aug 16, 2018 at 08:34:47AM +0200, Greg KH wrote:
> > On Wed, Aug 15, 2018 at 10:56:47PM -0700, Patong Yang wrote:
> > > Greg,
> > > 
> > > Please see my response inline below.
> > > 
> > > Patong
> > > 
> > > > But there is a bigger problem here:
> > > > 
> > > > > +	xrusb_tty_driver = alloc_tty_driver(XRUSB_TTY_MINORS);
> > > > > +	if (!xrusb_tty_driver)
> > > > > +		return -ENOMEM;
> > > > 
> > > > Why are you not using the usb serial core here?  You need to do that,
> > > > not try to provide your own custom tty driver.  That way userspace
> > > > programs will "just work" with your new device, no changes needed as
> > > > your major/minor number and device name would be custom only for your
> > > > device, which is not acceptable.
> > > 
> > > The MaxLinear/USB serial devices support the CDC-ACM commands.
> > > Therefore, we used the cdc-acm driver instead of the usb serial driver
> > > as the starting point for developing the driver.  We replaced "ACM" 
> > > with "XRUSB" throughout the driver.  Would it be better if we just used 
> > > the same major/minor number as the CDC-ACM driver since it was based on
> > > the cdc-acm driver?  
> > 
> > No, just use the cdc-acm driver itself and add your product/device id to
> > it and it should work just fine.  Why do you need to write a whole new
> > driver at all?
> 
> The basic TX/RX functionality works fine now with the cdc-acm driver.
> That's the driver that is loaded because it's advertised as a cdc-acm
> compatible device in the device descriptors.    
> 
> However, the cdc-acm driver (and spec) does not have support all of the 
> features in the MaxLinear/Exar USB UARTs.  Hence the reason for a separate
> and new driver.  

So your device should not be exposing itself as a cdc-acm driver if it
is doing vendor-specific things, right?

Ok, that's fine, but then you still need to tie into the usb-serial
core, just use that and do not implement all of the duplicated tty
handling logic that you have done here.  You will end up with a ttyUSB*
device node, which is what you, and your users want, not some
custom-name that no program supports.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial Adapters
@ 2018-08-16  8:28 Patong Yang
  0 siblings, 0 replies; 15+ messages in thread
From: Patong Yang @ 2018-08-16  8:28 UTC (permalink / raw)
  To: Greg KH; +Cc: pyang, johan, linux-usb

On Thu, Aug 16, 2018 at 08:34:47AM +0200, Greg KH wrote:
> On Wed, Aug 15, 2018 at 10:56:47PM -0700, Patong Yang wrote:
> > Greg,
> > 
> > Please see my response inline below.
> > 
> > Patong
> > 
> > > But there is a bigger problem here:
> > > 
> > > > +	xrusb_tty_driver = alloc_tty_driver(XRUSB_TTY_MINORS);
> > > > +	if (!xrusb_tty_driver)
> > > > +		return -ENOMEM;
> > > 
> > > Why are you not using the usb serial core here?  You need to do that,
> > > not try to provide your own custom tty driver.  That way userspace
> > > programs will "just work" with your new device, no changes needed as
> > > your major/minor number and device name would be custom only for your
> > > device, which is not acceptable.
> > 
> > The MaxLinear/USB serial devices support the CDC-ACM commands.
> > Therefore, we used the cdc-acm driver instead of the usb serial driver
> > as the starting point for developing the driver.  We replaced "ACM" 
> > with "XRUSB" throughout the driver.  Would it be better if we just used 
> > the same major/minor number as the CDC-ACM driver since it was based on
> > the cdc-acm driver?  
> 
> No, just use the cdc-acm driver itself and add your product/device id to
> it and it should work just fine.  Why do you need to write a whole new
> driver at all?

The basic TX/RX functionality works fine now with the cdc-acm driver.
That's the driver that is loaded because it's advertised as a cdc-acm
compatible device in the device descriptors.    

However, the cdc-acm driver (and spec) does not have support all of the 
features in the MaxLinear/Exar USB UARTs.  Hence the reason for a separate
and new driver.  

Features not supported by the cdc-acm driver are 
enabling/disabling flow control, enabling/disabling RS-485 mode, 
GPIOs and GPIO modes, etc. Support for these features had to be added in this
new driver.  

Please let me know if there are any futher questions and advise on how
to proceed.

Thanks,
Patong

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial Adapters
@ 2018-08-16  8:26 Oliver Neukum
  0 siblings, 0 replies; 15+ messages in thread
From: Oliver Neukum @ 2018-08-16  8:26 UTC (permalink / raw)
  To: Patong Yang, Greg KH; +Cc: johan, pyang, linux-usb

On Do, 2018-08-16 at 01:28 -0700, Patong Yang wrote:
> Features not supported by the cdc-acm driver are 
> enabling/disabling flow control, enabling/disabling RS-485 mode, 
> GPIOs and GPIO modes, etc. Support for these features had to be added in this
> new driver.

Hi,

I am afraid RS-485 in cdc-acm is a no-go.
I don't like the duplication, but it is the lesser evil.
We might want to include more stuff in the new driver,
so we duplicate only compiled code.

	Regards
		Oliver

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial Adapters
@ 2018-08-16  6:34 Greg Kroah-Hartman
  0 siblings, 0 replies; 15+ messages in thread
From: Greg Kroah-Hartman @ 2018-08-16  6:34 UTC (permalink / raw)
  To: Patong Yang; +Cc: johan, linux-usb, pyang

On Wed, Aug 15, 2018 at 10:56:47PM -0700, Patong Yang wrote:
> Greg,
> 
> Please see my response inline below.
> 
> Patong
> 
> > But there is a bigger problem here:
> > 
> > > +	xrusb_tty_driver = alloc_tty_driver(XRUSB_TTY_MINORS);
> > > +	if (!xrusb_tty_driver)
> > > +		return -ENOMEM;
> > 
> > Why are you not using the usb serial core here?  You need to do that,
> > not try to provide your own custom tty driver.  That way userspace
> > programs will "just work" with your new device, no changes needed as
> > your major/minor number and device name would be custom only for your
> > device, which is not acceptable.
> 
> The MaxLinear/USB serial devices support the CDC-ACM commands.
> Therefore, we used the cdc-acm driver instead of the usb serial driver
> as the starting point for developing the driver.  We replaced "ACM" 
> with "XRUSB" throughout the driver.  Would it be better if we just used 
> the same major/minor number as the CDC-ACM driver since it was based on
> the cdc-acm driver?  

No, just use the cdc-acm driver itself and add your product/device id to
it and it should work just fine.  Why do you need to write a whole new
driver at all?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial Adapters
@ 2018-08-16  5:56 Patong Yang
  0 siblings, 0 replies; 15+ messages in thread
From: Patong Yang @ 2018-08-16  5:56 UTC (permalink / raw)
  To: Greg KH; +Cc: johan, linux-usb, pyang

Greg,

Please see my response inline below.

Patong

> But there is a bigger problem here:
> 
> > +	xrusb_tty_driver = alloc_tty_driver(XRUSB_TTY_MINORS);
> > +	if (!xrusb_tty_driver)
> > +		return -ENOMEM;
> 
> Why are you not using the usb serial core here?  You need to do that,
> not try to provide your own custom tty driver.  That way userspace
> programs will "just work" with your new device, no changes needed as
> your major/minor number and device name would be custom only for your
> device, which is not acceptable.

The MaxLinear/USB serial devices support the CDC-ACM commands.
Therefore, we used the cdc-acm driver instead of the usb serial driver
as the starting point for developing the driver.  We replaced "ACM" 
with "XRUSB" throughout the driver.  Would it be better if we just used 
the same major/minor number as the CDC-ACM driver since it was based on
the cdc-acm driver?  

> 
> By doing that, your code will also be much smaller, always a good
> benefit as well.
>

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial Adapters
@ 2018-07-26 10:57 Greg Kroah-Hartman
  0 siblings, 0 replies; 15+ messages in thread
From: Greg Kroah-Hartman @ 2018-07-26 10:57 UTC (permalink / raw)
  To: Patong Yang; +Cc: linux-usb, johan, pyang

On Tue, Jul 24, 2018 at 03:36:36PM -0700, Patong Yang wrote:
> The original driver/patch was submitted on April 4, 2018.  This is the
> second version based on the feedback received on the original patch.
> 
> v2: Removed custom IOCTLs, as suggested by Greg KH
>     Using standard Linux GPIO APIs, as suggested by Greg KH
>     Removed file reads/writes as suggested by Greg KH
> 
> Signed-off-by: Patong Yang <patong.mxl@gmail.com>
> ---
>  drivers/usb/serial/xrusb_serial.c | 2380 +++++++++++++++++++++++++++++
>  drivers/usb/serial/xrusb_serial.h |  234 +++

Why do you need a .h file for a single driver?  Please just put it all
into one file.

But there is a bigger problem here:

> +	xrusb_tty_driver = alloc_tty_driver(XRUSB_TTY_MINORS);
> +	if (!xrusb_tty_driver)
> +		return -ENOMEM;

Why are you not using the usb serial core here?  You need to do that,
not try to provide your own custom tty driver.  That way userspace
programs will "just work" with your new device, no changes needed as
your major/minor number and device name would be custom only for your
device, which is not acceptable.

By doing that, your code will also be much smaller, always a good
benefit as well.

thanks,

greg k-h
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" 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] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial Adapters
@ 2018-07-25  7:38 Oliver Neukum
  0 siblings, 0 replies; 15+ messages in thread
From: Oliver Neukum @ 2018-07-25  7:38 UTC (permalink / raw)
  To: Patong Yang, linux-usb; +Cc: johan, gregkh, pyang

On Di, 2018-07-24 at 15:36 -0700, Patong Yang wrote:
> +static int xrusb_ctrl_msg(struct xrusb *xrusb,
> +               int request, int value, void *buf, int len)
> +{
> +       int rv = usb_control_msg(xrusb->dev,
> +               usb_sndctrlpipe(xrusb->dev, 0),
> +               request,
> +               USB_RT_XRUSB,
> +               value,
> +               xrusb->control->altsetting[0].desc.bInterfaceNumber,
> +               buf,
> +               len,
> +               5000);

Please use the symbolic constant.

> +       return rv < 0 ? rv : 0;
> +}

	Regards
		Oliver
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" 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] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial Adapters
@ 2018-07-24 22:36 Patong Yang
  0 siblings, 0 replies; 15+ messages in thread
From: Patong Yang @ 2018-07-24 22:36 UTC (permalink / raw)
  To: linux-usb; +Cc: johan, pyang, gregkh

The original driver/patch was submitted on April 4, 2018.  This is the
second version based on the feedback received on the original patch.

v2: Removed custom IOCTLs, as suggested by Greg KH
    Using standard Linux GPIO APIs, as suggested by Greg KH
    Removed file reads/writes as suggested by Greg KH

Signed-off-by: Patong Yang <patong.mxl@gmail.com>
---
 drivers/usb/serial/xrusb_serial.c | 2380 +++++++++++++++++++++++++++++
 drivers/usb/serial/xrusb_serial.h |  234 +++
 2 files changed, 2614 insertions(+)
 create mode 100644 drivers/usb/serial/xrusb_serial.c
 create mode 100644 drivers/usb/serial/xrusb_serial.h

diff --git a/drivers/usb/serial/xrusb_serial.c b/drivers/usb/serial/xrusb_serial.c
new file mode 100644
index 000000000000..707c470d32db
--- /dev/null
+++ b/drivers/usb/serial/xrusb_serial.c
@@ -0,0 +1,2380 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * xrusb_serial.c
+ *
+ * Copyright (c) 2018 Patong Yang <patong.mxl@gmail.com>
+ *
+ * USB Serial Driver based on the cdc-acm.c driver for the
+ * MaxLinear/Exar USB UARTs/Serial adapters
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/log2.h>
+#include <linux/serial.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/dmi.h>
+#include <asm/byteorder.h>
+#include <asm/unaligned.h>
+#include <linux/idr.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <linux/gpio.h>
+
+#include "linux/version.h"
+#include "xrusb_serial.h"
+
+#define DRIVER_AUTHOR "Patong Yang <patong.mxl@gmail.com>"
+#define DRIVER_DESC "MaxLinear/Exar USB UART (serial port) driver"
+
+static struct usb_driver xrusb_driver;
+static struct tty_driver *xrusb_tty_driver;
+static struct xrusb *xrusb_table[XRUSB_TTY_MINORS];
+static struct reg_addr_map xr2280x_reg_map;
+static struct reg_addr_map xr21b1411_reg_map;
+static struct reg_addr_map xr21v141x_reg_map;
+static struct reg_addr_map xr21b142x_reg_map;
+
+static DEFINE_MUTEX(xrusb_table_lock);
+
+/*
+ * Look up an XRUSB structure by index. If found and not disconnected,
+ * increment its refcount and return it with its mutex held.
+ */
+
+static struct xrusb *xrusb_get_by_index(unsigned int index)
+{
+	struct xrusb *xrusb;
+
+	mutex_lock(&xrusb_table_lock);
+	xrusb = xrusb_table[index];
+	if (xrusb) {
+		mutex_lock(&xrusb->mutex);
+		if (xrusb->disconnected) {
+			mutex_unlock(&xrusb->mutex);
+			xrusb = NULL;
+		} else {
+			tty_port_get(&xrusb->port);
+			mutex_unlock(&xrusb->mutex);
+		}
+	}
+	mutex_unlock(&xrusb_table_lock);
+	return xrusb;
+}
+
+/*
+ * Try to find an available minor number and if found, associate it with
+ * 'xrusb'.
+ */
+static int xrusb_alloc_minor(struct xrusb *xrusb)
+{
+	int minor;
+
+	mutex_lock(&xrusb_table_lock);
+	for (minor = 0; minor < XRUSB_TTY_MINORS; minor++) {
+		if (!xrusb_table[minor]) {
+			xrusb_table[minor] = xrusb;
+			break;
+		}
+	}
+	mutex_unlock(&xrusb_table_lock);
+
+	return minor;
+}
+
+/* Release the minor number associated with 'xrusb'.  */
+static void xrusb_release_minor(struct xrusb *xrusb)
+{
+	mutex_lock(&xrusb_table_lock);
+	xrusb_table[xrusb->minor] = NULL;
+	mutex_unlock(&xrusb_table_lock);
+}
+
+/*
+ * Functions for XRUSB control messages.
+ */
+
+static int xrusb_ctrl_msg(struct xrusb *xrusb,
+		int request, int value,	void *buf, int len)
+{
+	int rv = usb_control_msg(xrusb->dev,
+		usb_sndctrlpipe(xrusb->dev, 0),
+		request,
+		USB_RT_XRUSB,
+		value,
+		xrusb->control->altsetting[0].desc.bInterfaceNumber,
+		buf,
+		len,
+		5000);
+	return rv < 0 ? rv : 0;
+}
+
+int xrusb_set_reg(struct xrusb *xrusb, int regnum, int value)
+{
+	int result;
+	int channel = 0;
+	int XR2280xaddr = XR2280x_FUNC_MGR_OFFSET + regnum;
+
+	if ((xrusb->DeviceProduct & 0xfff0) == 0x1400) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_sndctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_SET_XR2280X,			// request
+			USB_DIR_OUT | USB_TYPE_VENDOR,		// request_type
+			value,					// request value
+			XR2280xaddr,				// index
+			NULL,					// data
+			0,					// size
+			5000);					// timeout
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1420) {
+		channel = (xrusb->channel - 4)*2;
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_sndctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_SET_XR21B142X,			// request
+			USB_DIR_OUT | USB_TYPE_VENDOR | 1,	// request_type
+			value,					// request value
+			regnum | (channel << 8),		// index
+			NULL,					// data
+			0,					// size
+			5000);					// timeout
+	} else if (xrusb->DeviceProduct == 0x1411) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_sndctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_SET_XR21B1411,			// request
+			USB_DIR_OUT | USB_TYPE_VENDOR,		// request_type
+			value,					// request value
+			regnum,					// index
+			NULL,					// data
+			0,					// size
+			5000);					// timeout
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1410) {
+		if (xrusb->channel)
+			channel = xrusb->channel - 1;
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_sndctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_SET_XR21V141X,			// request
+			USB_DIR_OUT | USB_TYPE_VENDOR,		// request_type
+			value,					// request value
+			regnum | (channel << 8),		// index
+			NULL,					// data
+			0,					// size
+			5000);					// timeout
+	} else {
+		result = -1;
+	}
+
+	if (result < 0) {
+		dev_err(&xrusb->control->dev, "%s Error:%d\n",
+			__func__, result);
+	}
+
+	return result;
+}
+
+int xrusb_set_reg_ext(struct xrusb *xrusb, int channel, int regnum, int value)
+{
+	int result;
+	int XR2280xaddr = XR2280x_FUNC_MGR_OFFSET + regnum;
+
+	if ((xrusb->DeviceProduct & 0xfff0) == 0x1400) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_sndctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_SET_XR2280X,			// request
+			USB_DIR_OUT | USB_TYPE_VENDOR,		// request_type
+			value,					// request value
+			XR2280xaddr,				// index
+			NULL,					// data
+			0,					// size
+			5000);					// timeout
+
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1420) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_sndctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_SET_XR21B142X,			// request
+			USB_DIR_OUT | USB_TYPE_VENDOR | 1,	// request_type
+			value,					// request value
+			regnum | (channel << 8),		// index
+			NULL,					// data
+			0,					// size
+			5000);					// timeout
+	} else if (xrusb->DeviceProduct == 0x1411) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_sndctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_SET_XR21B1411,			// request
+			USB_DIR_OUT | USB_TYPE_VENDOR,		// request_type
+			value,					// request value
+			regnum,					// index
+			NULL,					// data
+			0,					// size
+			5000);					// timeout
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1410) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_sndctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_SET_XR21V141X,			// request
+			USB_DIR_OUT | USB_TYPE_VENDOR,		// request_type
+			value,					// request value
+			regnum | (channel << 8),		// index
+			NULL,					// data
+			0,					// size
+			5000);					// timeout
+	} else {
+		result = -1;
+	}
+
+	if (result < 0) {
+		dev_err(&xrusb->control->dev, "%s Error:%d\n",
+			__func__, result);
+	}
+
+	return result;
+}
+
+
+int xrusb_get_reg(struct xrusb *xrusb, int regnum, short *value)
+{
+	int result;
+	int channel = 0;
+	int XR2280xaddr = XR2280x_FUNC_MGR_OFFSET + regnum;
+	void *dmadata = kmalloc(2, GFP_KERNEL);
+
+	if (!dmadata)
+		return -ENOMEM;
+
+	if ((xrusb->DeviceProduct & 0xfff0) == 0x1400) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_rcvctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_GET_XR2280X,			// request
+			USB_DIR_IN | USB_TYPE_VENDOR,		// request_type
+			0,					// request value
+			XR2280xaddr,				// index
+			dmadata,				// data
+			2,					// size
+			5000);					// timeout
+		memcpy(value, dmadata, 2);
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1420) {
+		channel = (xrusb->channel - 4)*2;
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_rcvctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_GET_XR21B142X,			// request
+			USB_DIR_IN | USB_TYPE_VENDOR | 1,	// request_type
+			0,					// request value
+			regnum | (channel << 8),		// index
+			dmadata,				// data
+			2,					// size
+			5000);					// timeout
+			memcpy(value, dmadata, 2);
+
+	} else if (xrusb->DeviceProduct == 0x1411) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_rcvctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_GET_XR21B1411,			// request
+			USB_DIR_IN | USB_TYPE_VENDOR,		// request_type
+			0,					// request value
+			regnum,					// index
+			dmadata,				// data
+			2,					// size
+			5000);					// timeout
+			memcpy(value, dmadata, 2);
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1410) {
+		if (xrusb->channel)
+			channel = xrusb->channel - 1;
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_rcvctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_GET_XR21V141X,			// request
+			USB_DIR_IN | USB_TYPE_VENDOR,		// request_type
+			0,					// request value
+			regnum | (channel << 8),		// index
+			dmadata,				// data
+			1,					// size
+			5000);					// timeout
+			memcpy(value, dmadata, 1);
+	} else {
+		result = -1;
+	}
+
+	if (result < 0) {
+		dev_err(&xrusb->control->dev,
+			"%s channel:%d Reg 0x%x	Error:%d\n",
+			__func__, channel, regnum, result);
+	}
+	kfree(dmadata);
+	return result;
+}
+
+
+int xrusb_get_reg_ext(struct xrusb *xrusb, int channel,
+	int regnum, short *value)
+{
+	int result;
+	int XR2280xaddr = XR2280x_FUNC_MGR_OFFSET + regnum;
+	void *dmadata = kmalloc(2, GFP_KERNEL);
+
+	if (!dmadata)
+		return -ENOMEM;
+
+	if ((xrusb->DeviceProduct&0xfff0) == 0x1400) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_rcvctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_GET_XR2280X,			// request
+			USB_DIR_IN | USB_TYPE_VENDOR,		// request_type
+			0,					// request value
+			XR2280xaddr,				// index
+			dmadata,				// data
+			2,					// size
+			5000);					// timeout
+		memcpy(value, dmadata, 2);
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1420) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_rcvctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_GET_XR21B142X,			// request
+			USB_DIR_IN | USB_TYPE_VENDOR | 1,	// request_type
+			0,					// request value
+			regnum | (channel << 8),		// index
+			dmadata,				// data
+			2,					// size
+			5000);					// timeout
+		memcpy(value, dmadata, 2);
+	} else if (xrusb->DeviceProduct == 0x1411) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_rcvctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_GET_XR21B1411,			// request
+			USB_DIR_IN | USB_TYPE_VENDOR,		// request_type
+			0,					// request value
+			regnum | (channel << 8),		// index
+			dmadata,				// data
+			2,					// size
+			5000);					// timeout
+		memcpy(value, dmadata, 2);
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1410) {
+		result = usb_control_msg(xrusb->dev,		// usb device
+			usb_rcvctrlpipe(xrusb->dev, 0),		// endpoint pipe
+			XRUSB_GET_XR21V141X,			// request
+			USB_DIR_IN | USB_TYPE_VENDOR,		// request_type
+			0,					// request value
+			regnum | (channel << 8),		// index
+			dmadata,				// data
+			1,					// size
+			5000);					// timeout
+		memcpy(value, dmadata, 1);
+	} else {
+		result = -1;
+	}
+
+	if (result < 0) {
+		dev_err(&xrusb->control->dev, "%s Error:%d\n",
+			__func__, result);
+	}
+	kfree(dmadata);
+	return result;
+}
+
+struct xr21v141x_baud_rate {
+	unsigned int tx;
+	unsigned int rx0;
+	unsigned int rx1; };
+
+static struct xr21v141x_baud_rate xr21v141x_baud_rates[] = {
+	{ 0x000, 0x000, 0x000 },
+	{ 0x000, 0x000, 0x000 },
+	{ 0x100, 0x000, 0x100 },
+	{ 0x020, 0x400, 0x020 },
+	{ 0x010, 0x100, 0x010 },
+	{ 0x208, 0x040, 0x208 },
+	{ 0x104, 0x820, 0x108 },
+	{ 0x844, 0x210, 0x884 },
+	{ 0x444, 0x110, 0x444 },
+	{ 0x122, 0x888, 0x224 },
+	{ 0x912, 0x448, 0x924 },
+	{ 0x492, 0x248, 0x492 },
+	{ 0x252, 0x928, 0x292 },
+	{ 0X94A, 0X4A4, 0XA52 },
+	{ 0X52A, 0XAA4, 0X54A },
+	{ 0XAAA, 0x954, 0X4AA },
+	{ 0XAAA, 0x554, 0XAAA },
+	{ 0x555, 0XAD4, 0X5AA },
+	{ 0XB55, 0XAB4, 0X55A },
+	{ 0X6B5, 0X5AC, 0XB56 },
+	{ 0X5B5, 0XD6C, 0X6D6 },
+	{ 0XB6D, 0XB6A, 0XDB6 },
+	{ 0X76D, 0X6DA, 0XBB6 },
+	{ 0XEDD, 0XDDA, 0X76E },
+	{ 0XDDD, 0XBBA, 0XEEE },
+	{ 0X7BB, 0XF7A, 0XDDE },
+	{ 0XF7B, 0XEF6, 0X7DE },
+	{ 0XDF7, 0XBF6, 0XF7E },
+	{ 0X7F7, 0XFEE, 0XEFE },
+	{ 0XFDF, 0XFBE, 0X7FE },
+	{ 0XF7F, 0XEFE, 0XFFE },
+	{ 0XFFF, 0XFFE, 0XFFD },
+};
+
+
+static int xr21v141x_set_baud_rate(struct xrusb *xrusb,	unsigned int rate)
+{
+	unsigned int divisor = 48000000 / rate;
+	unsigned int i = ((32 * 48000000) / rate) & 0x1f;
+	unsigned int tx_mask = xr21v141x_baud_rates[i].tx;
+	unsigned int rx_mask = (divisor & 1) ?
+		xr21v141x_baud_rates[i].rx1 :
+		xr21v141x_baud_rates[i].rx0;
+
+	xrusb_set_reg(xrusb, XR21V141X_CLOCK_DIVISOR_0, (divisor >>  0) & 0xff);
+	xrusb_set_reg(xrusb, XR21V141X_CLOCK_DIVISOR_1, (divisor >>  8) & 0xff);
+	xrusb_set_reg(xrusb, XR21V141X_CLOCK_DIVISOR_2, (divisor >> 16) & 0xff);
+	xrusb_set_reg(xrusb, XR21V141X_TX_CLOCK_MASK_0,	(tx_mask >>  0) & 0xff);
+	xrusb_set_reg(xrusb, XR21V141X_TX_CLOCK_MASK_1, (tx_mask >>  8) & 0xff);
+	xrusb_set_reg(xrusb, XR21V141X_RX_CLOCK_MASK_0, (rx_mask >>  0) & 0xff);
+	xrusb_set_reg(xrusb, XR21V141X_RX_CLOCK_MASK_1, (rx_mask >>  8) & 0xff);
+
+	return 0;
+}
+
+/* devices aren't required to support these requests.
+ * the cdc xrusb descriptor tells whether they do...
+ */
+int xrusb_set_control(struct xrusb *xrusb, unsigned int control)
+{
+	int rv = 0;
+
+	// Use custom vendor request for XR21V1410/12/14
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		if (control & XRUSB_CTRL_DTR) {
+			xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr,
+				UART_GPIO_CLR_DTR);
+		} else {
+			xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set,
+				UART_GPIO_SET_DTR);
+		}
+
+		if (control & XRUSB_CTRL_RTS) {
+			xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr,
+				UART_GPIO_CLR_RTS);
+		} else {
+			xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set,
+				UART_GPIO_SET_RTS);
+		}
+	} else {
+	// Use CDC command for XR21B14xx and XR2280x
+		rv = xrusb_ctrl_msg(xrusb,
+			USB_CDC_REQ_SET_CONTROL_LINE_STATE,
+			control,
+			NULL,
+			0);
+	}
+	return rv;
+}
+
+int xrusb_set_line(struct xrusb *xrusb,	struct usb_cdc_line_coding *line)
+{
+	int rv = 0;
+	unsigned int data_size, data_parity, data_stop, format;
+
+	// Use custom vendor request for XR21V1410/12/14
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		xr21v141x_set_baud_rate(xrusb, line->dwDTERate);
+		data_size = line->bDataBits;
+		data_parity = line->bParityType;
+		data_stop = line->bCharFormat;
+		format = data_size | (data_parity << 4) | (data_stop << 7);
+		xrusb_set_reg(xrusb, xrusb->reg_map.uart_format, format);
+	}
+	// Use CDC command for XR21B14xx and XR2280x
+	else
+		rv = xrusb_ctrl_msg(xrusb,
+			USB_CDC_REQ_SET_LINE_CODING,
+			0,
+			line,
+			sizeof *(line));
+
+	return rv;
+}
+
+int xrusb_set_flow_mode(struct xrusb *xrusb,
+		struct tty_struct *tty, unsigned int cflag)
+{
+	unsigned int flow;
+	unsigned int gpio_mode;
+	short gpio_mode_reg;
+
+	xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, &gpio_mode_reg);
+	gpio_mode = gpio_mode_reg;
+	if (cflag & CRTSCTS) {
+		flow = UART_FLOW_MODE_HW;
+		gpio_mode |= UART_GPIO_MODE_SEL_RTS_CTS;
+	} else if (I_IXOFF(tty) || I_IXON(tty)) {
+		unsigned char start_char = START_CHAR(tty);
+		unsigned char stop_char = STOP_CHAR(tty);
+
+		flow = UART_FLOW_MODE_SW;
+		gpio_mode |= UART_GPIO_MODE_SEL_GPIO;
+
+		xrusb_set_reg(xrusb, xrusb->reg_map.uart_xon_char, start_char);
+		xrusb_set_reg(xrusb, xrusb->reg_map.uart_xoff_char, stop_char);
+	} else {
+		flow = UART_FLOW_MODE_NONE;
+	}
+
+	// Mode configured in BIOS for Caracalla
+	// Do nothing for xrusb_set_flow_mode
+	if (xrusb->found_smbios_caracalla_config)
+		return 0;
+
+	xrusb_set_reg(xrusb, xrusb->reg_map.uart_flow, flow);
+	xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, gpio_mode);
+
+	return 0;
+}
+
+int xrusb_send_break(struct xrusb *xrusb, int state)
+{
+	int rv = 0;
+
+	// Use custom vendor request for XR21V1410/12/14
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		if (state) {
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_tx_break,
+				0xffff);
+		} else {
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_tx_break,
+				0);
+		}
+	} else {// Use CDC command for XR21B14xx and XR2280x
+		rv = xrusb_ctrl_msg(xrusb,
+			USB_CDC_REQ_SEND_BREAK,
+			state,
+			NULL,
+			0);
+	}
+	return rv;
+}
+
+int xrusb_enable(struct xrusb *xrusb)
+{
+	int rv = 0;
+	int channel = xrusb->channel;
+
+	if (channel)
+		channel--;
+
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		rv = xrusb_set_reg_ext(xrusb,
+			XR21V141x_URM_REG_BLOCK,
+			XR21V141x_URM_FIFO_ENABLE_REG + channel,
+			XR21V141x_URM_ENABLE_TX_FIFO);
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_enable,
+			UART_ENABLE_TX | UART_ENABLE_RX);
+		rv = xrusb_set_reg_ext(xrusb,
+			XR21V141x_URM_REG_BLOCK,
+			XR21V141x_URM_FIFO_ENABLE_REG + channel,
+			XR21V141x_URM_ENABLE_TX_FIFO |
+			XR21V141x_URM_ENABLE_RX_FIFO);
+	} else {
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_enable,
+			UART_ENABLE_TX | UART_ENABLE_RX);
+	}
+
+	return rv;
+}
+
+int xrusb_disable(struct xrusb *xrusb)
+{
+	int rv = 0;
+	int channel = xrusb->channel;
+
+	if (channel)
+		channel--;
+
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_enable, 0);
+
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		rv = xrusb_set_reg_ext(xrusb,
+			XR21V141x_URM_REG_BLOCK,
+			XR21V141x_URM_FIFO_ENABLE_REG + channel, 0);
+	}
+
+	return rv;
+}
+
+int xrusb_fifo_reset(struct xrusb *xrusb)
+{
+	int rv = 0;
+	int channel = xrusb->channel;
+
+	if (channel)
+		channel--;
+	if ((xrusb->DeviceProduct & 0x1411) == 0x1410) {
+		rv = xrusb_set_reg_ext(xrusb,
+			XR21V141x_URM_REG_BLOCK,
+			XR21V141x_URM_RX_FIFO_RESET + channel,
+			0xff);
+		rv |= xrusb_set_reg_ext(xrusb,
+			XR21V141x_URM_REG_BLOCK,
+			XR21V141x_URM_TX_FIFO_RESET + channel,
+			0xff);
+	} else
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_enable,
+			UART_ENABLE_TX | UART_ENABLE_RX);
+
+	return rv;
+}
+
+int xrusb_set_wide_mode(struct xrusb *xrusb, int wide_mode)
+{
+	int rv = 0;
+	int channel = xrusb->channel;
+
+	xrusb_disable(xrusb);
+
+	if ((xrusb->DeviceProduct & 0xFFF0) == 0x1400) {
+		xrusb_set_reg(xrusb, XR2280X_TX_WIDE_MODE_REG, wide_mode);
+		xrusb_set_reg(xrusb, XR2280X_RX_WIDE_MODE_REG, wide_mode);
+	} else if ((xrusb->DeviceProduct & 0xFFF0) == 0x1420) {
+		xrusb_set_reg(xrusb, XR21B142X_TX_WIDE_MODE_REG, wide_mode);
+		xrusb_set_reg(xrusb, XR21B142X_RX_WIDE_MODE_REG, wide_mode);
+	} else if (xrusb->DeviceProduct == 0x1411) {
+		xrusb_set_reg(xrusb, XR21B1411_WIDE_MODE_REG, wide_mode);
+	} else if ((xrusb->DeviceProduct & 0xFFF0) == 0x1410) {
+		if (channel)
+			channel--;
+		xrusb_set_reg_ext(xrusb,
+			XR21V141x_UART_CUSTOM_BLOCK,
+			channel*8 + XR21V141X_WIDE_MODE_REG,
+			wide_mode);
+	}
+
+	xrusb_enable(xrusb);
+	return rv;
+}
+
+int xrusb_gpio_dir_out(struct xrusb *xrusb, int gpio_mask)
+{
+	int rv = 0;
+	short reg_value;
+
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_dir, &reg_value);
+	if (rv < 0)
+		return -EFAULT;
+
+	reg_value |= gpio_mask;
+
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, reg_value);
+	if (rv < 0)
+		return -EFAULT;
+
+	return rv;
+}
+
+int xrusb_gpio_dir_in(struct xrusb *xrusb, int gpio_mask)
+{
+	int rv = 0;
+	short reg_value;
+
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_dir, &reg_value);
+	if (rv < 0)
+		return -EFAULT;
+
+	gpio_mask = ~gpio_mask;
+	reg_value &= gpio_mask;
+
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, reg_value);
+	if (rv < 0)
+		return -EFAULT;
+
+	return rv;
+}
+
+static int xrusb_tiocmget(struct xrusb *xrusb)
+
+{
+	short data;
+	int result;
+
+	result = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_status, &data);
+
+	if (result) {
+		return ((data & 0x8) ? 0 : TIOCM_DTR) |
+			((data & 0x20) ? 0 : TIOCM_RTS) |
+			((data & 0x4) ? 0 : TIOCM_DSR) |
+			((data & 0x1) ? 0 : TIOCM_RI) |
+			((data & 0x2) ? 0 : TIOCM_CD) |
+			((data & 0x10) ? 0 : TIOCM_CTS);
+	} else {
+		return -EFAULT;
+	}
+}
+
+static int xrusb_tiocmset(struct xrusb *xrusb,
+		unsigned int set, unsigned int clear)
+{
+
+	unsigned int newctrl = 0;
+
+	newctrl = xrusb->ctrlout;
+	set = (set & TIOCM_DTR ? XRUSB_CTRL_DTR : 0) |
+		(set & TIOCM_RTS ? XRUSB_CTRL_RTS : 0);
+	clear = (clear & TIOCM_DTR ? XRUSB_CTRL_DTR : 0) |
+		(clear & TIOCM_RTS ? XRUSB_CTRL_RTS : 0);
+	newctrl = (newctrl & ~clear) | set;
+
+	if (xrusb->ctrlout == newctrl)
+		return 0;
+
+	xrusb->ctrlout = newctrl;
+
+	if (newctrl & XRUSB_CTRL_DTR) {
+		xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_clr,
+			UART_GPIO_CLR_DTR);
+	} else {
+		xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_set,
+			UART_GPIO_SET_DTR);
+	}
+
+	if (newctrl & XRUSB_CTRL_RTS) {
+		xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_clr,
+			UART_GPIO_CLR_RTS);
+	} else {
+		xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_set,
+			UART_GPIO_SET_RTS);
+	}
+
+	return 0;
+}
+
+static void init_xr2280x_reg_map(void)
+{
+	xr2280x_reg_map.uart_enable = 0x40;
+	xr2280x_reg_map.uart_flow = 0x46;
+	xr2280x_reg_map.uart_xon_char = 0x47;
+	xr2280x_reg_map.uart_xoff_char = 0x48;
+	xr2280x_reg_map.uart_tx_break = 0x4a;
+	xr2280x_reg_map.uart_rs485_delay = 0x4b;
+	xr2280x_reg_map.uart_gpio_mode = 0x4c;
+	xr2280x_reg_map.uart_gpio_dir = 0x4d;
+	xr2280x_reg_map.uart_gpio_set = 0x4e;
+	xr2280x_reg_map.uart_gpio_clr = 0x4f;
+	xr2280x_reg_map.uart_gpio_status = 0x50;
+	xr2280x_reg_map.uart_gpio_int_mask = 0x51;
+	xr2280x_reg_map.uart_customized_int = 0x52;
+	xr2280x_reg_map.uart_gpio_pull_up_enable = 0x54;
+	xr2280x_reg_map.uart_gpio_pull_down_enable = 0x55;
+	xr2280x_reg_map.uart_loopback = 0x56;
+	xr2280x_reg_map.uart_low_latency = 0x66;
+	xr2280x_reg_map.uart_custom_driver = 0x81;
+}
+
+static void init_xr21b1411_reg_map(void)
+{
+	xr21b1411_reg_map.uart_enable = 0xc00;
+	xr21b1411_reg_map.uart_flow = 0xc06;
+	xr21b1411_reg_map.uart_xon_char = 0xc07;
+	xr21b1411_reg_map.uart_xoff_char = 0xc08;
+	xr21b1411_reg_map.uart_tx_break = 0xc0a;
+	xr21b1411_reg_map.uart_rs485_delay = 0xc0b;
+	xr21b1411_reg_map.uart_gpio_mode = 0xc0c;
+	xr21b1411_reg_map.uart_gpio_dir = 0xc0d;
+	xr21b1411_reg_map.uart_gpio_set = 0xc0e;
+	xr21b1411_reg_map.uart_gpio_clr = 0xc0f;
+	xr21b1411_reg_map.uart_gpio_status = 0xc10;
+	xr21b1411_reg_map.uart_gpio_int_mask = 0xc11;
+	xr21b1411_reg_map.uart_customized_int = 0xc12;
+	xr21b1411_reg_map.uart_gpio_pull_up_enable = 0xc14;
+	xr21b1411_reg_map.uart_gpio_pull_down_enable = 0xc15;
+	xr21b1411_reg_map.uart_loopback = 0xc16;
+	xr21b1411_reg_map.uart_low_latency = 0xcc2;
+	xr21b1411_reg_map.uart_custom_driver = 0x20d;
+}
+
+static void init_xr21v141x_reg_map(void)
+{
+	xr21v141x_reg_map.uart_enable = 0x03;
+	xr21v141x_reg_map.uart_format = 0x0b;
+	xr21v141x_reg_map.uart_flow = 0x0c;
+	xr21v141x_reg_map.uart_xon_char = 0x10;
+	xr21v141x_reg_map.uart_xoff_char = 0x11;
+	xr21v141x_reg_map.uart_loopback = 0x12;
+	xr21v141x_reg_map.uart_tx_break = 0x14;
+	xr21v141x_reg_map.uart_rs485_delay = 0x15;
+	xr21v141x_reg_map.uart_gpio_mode = 0x1a;
+	xr21v141x_reg_map.uart_gpio_dir = 0x1b;
+	xr21v141x_reg_map.uart_gpio_int_mask = 0x1c;
+	xr21v141x_reg_map.uart_gpio_set = 0x1d;
+	xr21v141x_reg_map.uart_gpio_clr = 0x1e;
+	xr21v141x_reg_map.uart_gpio_status = 0x1f;
+}
+
+static void init_xr21b142x_reg_map(void)
+{
+	xr21b142x_reg_map.uart_enable = 0x00;
+	xr21b142x_reg_map.uart_flow = 0x06;
+	xr21b142x_reg_map.uart_xon_char = 0x07;
+	xr21b142x_reg_map.uart_xoff_char = 0x08;
+	xr21b142x_reg_map.uart_tx_break = 0x0a;
+	xr21b142x_reg_map.uart_rs485_delay = 0x0b;
+	xr21b142x_reg_map.uart_gpio_mode = 0x0c;
+	xr21b142x_reg_map.uart_gpio_dir = 0x0d;
+	xr21b142x_reg_map.uart_gpio_set = 0x0e;
+	xr21b142x_reg_map.uart_gpio_clr = 0x0f;
+	xr21b142x_reg_map.uart_gpio_status = 0x10;
+	xr21b142x_reg_map.uart_gpio_int_mask = 0x11;
+	xr21b142x_reg_map.uart_customized_int = 0x12;
+	xr21b142x_reg_map.uart_gpio_open_drain = 0x13;
+	xr21b142x_reg_map.uart_gpio_pull_up_enable = 0x14;
+	xr21b142x_reg_map.uart_gpio_pull_down_enable = 0x15;
+	xr21b142x_reg_map.uart_loopback = 0x16;
+	xr21b142x_reg_map.uart_custom_driver = 0x60;
+	xr21b142x_reg_map.uart_low_latency = 0x46;
+}
+
+int xrusb_reg_init(struct xrusb *xrusb)
+{
+	int rv = 0, gpio_mode = 0;
+
+	init_xr2280x_reg_map();
+	init_xr21b142x_reg_map();
+	init_xr21b1411_reg_map();
+	init_xr21v141x_reg_map();
+
+	if ((xrusb->DeviceProduct & 0xfff0) == 0x1400) {
+		memcpy(&(xrusb->reg_map), &xr2280x_reg_map,
+			sizeof(struct reg_addr_map));
+	} else if ((xrusb->DeviceProduct & 0xFFF0) == 0x1420) {
+		memcpy(&(xrusb->reg_map), &xr21b142x_reg_map,
+			sizeof(struct reg_addr_map));
+	} else if (xrusb->DeviceProduct == 0x1411) {
+		memcpy(&(xrusb->reg_map), &xr21b1411_reg_map,
+			sizeof(struct reg_addr_map));
+	} else if ((xrusb->DeviceProduct & 0xfff0) == 0x1410) {
+		memcpy(&(xrusb->reg_map), &xr21v141x_reg_map,
+			sizeof(struct reg_addr_map));
+	} else {
+		rv = -1;
+	}
+
+	if (xrusb->reg_map.uart_custom_driver)
+		xrusb_set_reg(xrusb, xrusb->reg_map.uart_custom_driver, 1);
+
+	// Enable TXT and RXT function for XR21B1420/22/24
+	if ((xrusb->DeviceProduct & 0xfff0) == 0x1420)
+		gpio_mode |= 0x300;
+
+	xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, gpio_mode);
+
+	// Enable RTS and DTR as outputs and high (de-asserted)
+	xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, 0x28);
+	xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set,
+		UART_GPIO_SET_DTR | UART_GPIO_SET_RTS);
+
+	return rv;
+}
+
+/*
+ * Write buffer management.
+ * All of these assume proper locks taken by the caller.
+ */
+
+static int xrusb_wb_alloc(struct xrusb *xrusb)
+{
+	int i, wbn;
+	struct xrusb_wb *wb;
+
+	wbn = 0;
+	i = 0;
+	for (;;) {
+		wb = &xrusb->wb[wbn];
+		if (!wb->use) {
+			wb->use = 1;
+			return wbn;
+		}
+		wbn = (wbn + 1) % XRUSB_NW;
+		if (++i >= XRUSB_NW)
+			return -1;
+	}
+}
+
+static int xrusb_wb_is_avail(struct xrusb *xrusb)
+{
+	int i, n;
+	unsigned long flags;
+
+	n = XRUSB_NW;
+	spin_lock_irqsave(&xrusb->write_lock, flags);
+	for (i = 0; i < XRUSB_NW; i++)
+		n -= xrusb->wb[i].use;
+	spin_unlock_irqrestore(&xrusb->write_lock, flags);
+	return n;
+}
+
+/*
+ * Finish write. Caller must hold xrusb->write_lock
+ */
+static void xrusb_write_done(struct xrusb *xrusb,
+		struct xrusb_wb *wb)
+{
+	wb->use = 0;
+	xrusb->transmitting--;
+	usb_autopm_put_interface_async(xrusb->control);
+}
+
+/*
+ * Poke write.
+ *
+ * the caller is responsible for locking
+ */
+
+static int xrusb_start_wb(struct xrusb *xrusb, struct xrusb_wb *wb)
+{
+	int rc;
+
+	xrusb->transmitting++;
+
+	wb->urb->transfer_buffer = wb->buf;
+	wb->urb->transfer_dma = wb->dmah;
+	wb->urb->transfer_buffer_length = wb->len;
+	wb->urb->dev = xrusb->dev;
+
+	rc = usb_submit_urb(wb->urb, GFP_ATOMIC);
+	if (rc < 0) {
+		dev_err(&xrusb->data->dev,
+			"%s - usb_submit_urb(write bulk) failed: %d\n",
+			__func__, rc);
+		xrusb_write_done(xrusb, wb);
+	}
+	return rc;
+}
+
+/*
+ * attributes exported through sysfs
+ */
+
+static ssize_t bmCapabilities_show
+(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct usb_interface *intf = to_usb_interface(dev);
+	struct xrusb *xrusb = usb_get_intfdata(intf);
+
+	return sprintf(buf, "%d", xrusb->ctrl_caps);
+}
+static DEVICE_ATTR_RO(bmCapabilities);
+
+/*
+ * Interrupt handlers for various XRUSB device responses
+ */
+
+/* control interface reports status changes with "interrupt" transfers */
+static void xrusb_ctrl_irq(struct urb *urb)
+{
+	struct xrusb *xrusb = urb->context;
+
+	int rv;
+	int status = urb->status;
+	unsigned char *p;
+
+	switch (status) {
+	case 0:
+		p = (unsigned char *)(urb->transfer_buffer);
+		/* success */
+		break;
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		/* this urb is terminated, clean up */
+
+		return;
+	default:
+		break;
+	}
+
+	rv = usb_submit_urb(urb, GFP_ATOMIC);
+	if (rv && rv != -EPERM && rv != -ENODEV) {
+		dev_err(&xrusb->control->dev,
+			"%s - usb_submit_urb failed: %d\n", __func__, rv);
+	}
+}
+
+static int xrusb_submit_read_urb(struct xrusb *xrusb, int index,
+	gfp_t mem_flags)
+{
+	int res;
+
+	if (!test_and_clear_bit(index, &xrusb->read_urbs_free))
+		return 0;
+
+	res = usb_submit_urb(xrusb->read_urbs[index], mem_flags);
+	if (res) {
+		if (res != -EPERM && res != -ENODEV) {
+			dev_err(&xrusb->data->dev, "%s - usb_submit_urb failed: %d\n",
+				__func__, res);
+		}
+		set_bit(index, &xrusb->read_urbs_free);
+		return res;
+	}
+
+	return 0;
+}
+
+static int xrusb_submit_read_urbs(struct xrusb *xrusb, gfp_t mem_flags)
+{
+	int res;
+	int i;
+
+	for (i = 0; i < xrusb->rx_buflimit; ++i) {
+		res = xrusb_submit_read_urb(xrusb, i, mem_flags);
+		if (res)
+			return res;
+	}
+
+	return 0;
+}
+
+static void xrusb_process_read_urb(struct xrusb *xrusb,	struct urb *urb)
+{
+	int wide_mode = xrusb->wide_mode;
+	int have_extra_byte;
+	int length;
+
+	if (xrusb->uart_type == 0)
+		return;
+	if (!urb->actual_length)
+		return;
+
+	if (wide_mode) {
+		char *dp = urb->transfer_buffer;
+		int i, ch, ch_flags;
+
+		length = urb->actual_length;
+		length = length + (xrusb->have_extra_byte ? 1 : 0);
+		have_extra_byte = (wide_mode && (length & 1));
+		length = (wide_mode) ? (length / 2) : length;
+		for (i = 0; i < length; ++i) {
+			char tty_flag;
+
+			if (i == 0)
+				if (xrusb->have_extra_byte)
+					ch = xrusb->extra_byte;
+				else
+					ch = *dp++;
+			else
+				ch = *dp++;
+
+			ch_flags = *dp++;
+			if (ch_flags & WIDE_MODE_PARITY)
+				tty_flag = TTY_PARITY;
+			else if (ch_flags & WIDE_MODE_BREAK)
+				tty_flag = TTY_BREAK;
+			else if (ch_flags & WIDE_MODE_FRAME)
+				tty_flag = TTY_FRAME;
+			else if (ch_flags & WIDE_MODE_OVERRUN)
+				tty_flag = TTY_OVERRUN;
+			else
+				tty_flag = TTY_NORMAL;
+
+			tty_insert_flip_char(&xrusb->port, ch, tty_flag);
+			tty_flip_buffer_push(&xrusb->port);
+		}
+	} else {
+		tty_insert_flip_string(&xrusb->port,
+			urb->transfer_buffer, urb->actual_length);
+		tty_flip_buffer_push(&xrusb->port);
+	}
+}
+
+static void xrusb_read_bulk_callback(struct urb *urb)
+{
+	struct xrusb_rb *rb = urb->context;
+	struct xrusb *xrusb = rb->instance;
+	unsigned long flags;
+
+	set_bit(rb->index, &xrusb->read_urbs_free);
+
+	if (!xrusb->dev) {
+		dev_dbg(&xrusb->data->dev, "%s - disconnected\n", __func__);
+		return;
+	}
+	usb_mark_last_busy(xrusb->dev);
+	xrusb_process_read_urb(xrusb, urb);
+
+	/* throttle device if requested by tty */
+	spin_lock_irqsave(&xrusb->read_lock, flags);
+	xrusb->throttled = xrusb->throttle_req;
+	if (!xrusb->throttled && !xrusb->susp_count) {
+		spin_unlock_irqrestore(&xrusb->read_lock, flags);
+		xrusb_submit_read_urb(xrusb,
+			rb->index, GFP_ATOMIC);
+	} else {
+		spin_unlock_irqrestore(&xrusb->read_lock, flags);
+	}
+}
+
+/* data interface wrote those outgoing bytes */
+static void xrusb_write_bulk(struct urb *urb)
+{
+	struct xrusb_wb *wb = urb->context;
+	struct xrusb *xrusb = wb->instance;
+	unsigned long flags;
+
+	spin_lock_irqsave(&xrusb->write_lock, flags);
+	xrusb_write_done(xrusb, wb);
+	spin_unlock_irqrestore(&xrusb->write_lock, flags);
+	schedule_work(&xrusb->work);
+}
+
+static void xrusb_softint(struct work_struct *work)
+{
+	struct xrusb *xrusb = container_of(work, struct xrusb, work);
+
+	tty_port_tty_wakeup(&xrusb->port);
+}
+
+/*
+ * TTY handlers
+ */
+
+static int xrusb_tty_install(struct tty_driver *driver,	struct tty_struct *tty)
+{
+	struct xrusb *xrusb;
+	int rv;
+
+	xrusb = xrusb_get_by_index(tty->index);
+	if (!xrusb)
+		return -ENODEV;
+
+	rv = tty_standard_install(driver, tty);
+	if (rv)
+		goto error_init_termios;
+
+	tty->driver_data = xrusb;
+
+	return 0;
+
+error_init_termios:
+	tty_port_put(&xrusb->port);
+	return rv;
+}
+
+static int xrusb_tty_open(struct tty_struct *tty, struct file *filp)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	int result;
+
+	result = xrusb_fifo_reset(xrusb);
+
+	return tty_port_open(&xrusb->port, tty, filp);
+}
+
+static int xrusb_port_activate(struct tty_port *port, struct tty_struct *tty)
+{
+	struct xrusb *xrusb = container_of(port, struct xrusb, port);
+	int rv = -ENODEV;
+
+	mutex_lock(&xrusb->mutex);
+	if (xrusb->disconnected)
+		goto disconnected;
+
+	rv = usb_autopm_get_interface(xrusb->control);
+	if (rv)
+		goto error_get_interface;
+
+	set_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
+	xrusb->control->needs_remote_wakeup = 1;
+
+	xrusb->ctrlurb->dev = xrusb->dev;
+	if (usb_submit_urb(xrusb->ctrlurb, GFP_KERNEL)) {
+		dev_err(&xrusb->control->dev,
+			"%s - usb_submit_urb(ctrl irq) failed\n", __func__);
+		goto error_submit_urb;
+	}
+
+	xrusb->ctrlout = XRUSB_CTRL_DTR | XRUSB_CTRL_RTS;
+	if (xrusb_set_control(xrusb, xrusb->ctrlout) < 0
+		&& (xrusb->ctrl_caps & USB_CDC_CAP_LINE)) {
+		goto error_set_control;
+	}
+
+	usb_autopm_put_interface(xrusb->control);
+
+	/*
+	 * Unthrottle device in case the TTY was closed while throttled.
+	 */
+	spin_lock_irq(&xrusb->read_lock);
+	xrusb->throttled = 0;
+	xrusb->throttle_req = 0;
+	spin_unlock_irq(&xrusb->read_lock);
+
+	if (xrusb_submit_read_urbs(xrusb, GFP_KERNEL))
+		goto error_submit_read_urbs;
+
+	mutex_unlock(&xrusb->mutex);
+
+	return 0;
+
+error_submit_read_urbs:
+	xrusb->ctrlout = 0;
+	xrusb_set_control(xrusb, xrusb->ctrlout);
+error_set_control:
+	usb_kill_urb(xrusb->ctrlurb);
+error_submit_urb:
+	usb_autopm_put_interface(xrusb->control);
+error_get_interface:
+disconnected:
+	mutex_unlock(&xrusb->mutex);
+	return rv;
+}
+
+static void xrusb_port_destruct(struct tty_port *port)
+{
+	struct xrusb *xrusb = container_of(port, struct xrusb, port);
+
+#ifdef CONFIG_GPIOLIB
+	if (xrusb->rv_gpio_created == 0)
+		gpiochip_remove(&xrusb->xr_gpio);
+#endif
+	xrusb_release_minor(xrusb);
+	usb_put_intf(xrusb->control);
+	kfree(xrusb);
+}
+
+static void xrusb_port_shutdown(struct tty_port *port)
+{
+	struct xrusb *xrusb = container_of(port, struct xrusb, port);
+	int i;
+
+	mutex_lock(&xrusb->mutex);
+	if (!xrusb->disconnected) {
+		usb_autopm_get_interface(xrusb->control);
+		xrusb_set_control(xrusb,
+			xrusb->ctrlout = 0);
+		usb_kill_urb(xrusb->ctrlurb);
+		for (i = 0; i < XRUSB_NW; i++)
+			usb_kill_urb(xrusb->wb[i].urb);
+		for (i = 0; i < xrusb->rx_buflimit; i++)
+			usb_kill_urb(xrusb->read_urbs[i]);
+		xrusb->control->needs_remote_wakeup = 0;
+		usb_autopm_put_interface(xrusb->control);
+	}
+	mutex_unlock(&xrusb->mutex);
+}
+
+static void xrusb_tty_cleanup(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+
+	tty_port_put(&xrusb->port);
+}
+
+static void xrusb_tty_hangup(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+
+	tty_port_hangup(&xrusb->port);
+}
+
+static void xrusb_tty_close(struct tty_struct *tty, struct file *filp)
+{
+	struct xrusb *xrusb = tty->driver_data;
+
+	tty_port_close(&xrusb->port, tty, filp);
+}
+
+static int xrusb_tty_write(struct tty_struct *tty,
+		const unsigned char *buf, int count)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	int stat;
+	unsigned long flags;
+	int wbn;
+	struct xrusb_wb *wb;
+
+	if (xrusb->uart_type == 0)
+		return -EIO;
+
+	if (!count)
+		return 0;
+
+	spin_lock_irqsave(&xrusb->write_lock, flags);
+	wbn = xrusb_wb_alloc(xrusb);
+	if (wbn < 0) {
+		spin_unlock_irqrestore(&xrusb->write_lock, flags);
+		return 0;
+	}
+	wb = &xrusb->wb[wbn];
+
+	if (!xrusb->dev) {
+		wb->use = 0;
+		spin_unlock_irqrestore(&xrusb->write_lock, flags);
+		return -ENODEV;
+	}
+
+	count = (count > xrusb->writesize) ? xrusb->writesize : count;
+	memcpy(wb->buf, buf, count);
+	wb->len = count;
+
+	usb_autopm_get_interface_async(xrusb->control);
+	if (xrusb->susp_count) {
+		if (!xrusb->delayed_wb)
+			xrusb->delayed_wb = wb;
+		else
+			usb_autopm_put_interface_async(xrusb->control);
+
+		spin_unlock_irqrestore(&xrusb->write_lock, flags);
+		return count;	/* A white lie */
+	}
+	usb_mark_last_busy(xrusb->dev);
+
+	stat = xrusb_start_wb(xrusb, wb);
+	spin_unlock_irqrestore(&xrusb->write_lock, flags);
+
+	if (stat < 0)
+		return stat;
+
+	return count;
+}
+
+static int xrusb_tty_write_room(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	/*
+	 * Do not let the line discipline to know that we have a reserve,
+	 * or it might get too enthusiastic.
+	 */
+	return xrusb_wb_is_avail(xrusb) ? xrusb->writesize : 0;
+}
+
+static int xrusb_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	/*
+	 * if the device was unplugged then any remaining characters fell out
+	 * of the connector ;)
+	 */
+	if (xrusb->disconnected)
+		return 0;
+	/*
+	 * This is inaccurate (overcounts), but it works.
+	 */
+	return (XRUSB_NW - xrusb_wb_is_avail(xrusb)) * xrusb->writesize;
+}
+
+static void xrusb_tty_throttle(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+
+	spin_lock_irq(&xrusb->read_lock);
+	xrusb->throttle_req = 1;
+	spin_unlock_irq(&xrusb->read_lock);
+}
+
+static void xrusb_tty_unthrottle(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	unsigned int was_throttled;
+
+	spin_lock_irq(&xrusb->read_lock);
+	was_throttled = xrusb->throttled;
+	xrusb->throttled = 0;
+	xrusb->throttle_req = 0;
+	spin_unlock_irq(&xrusb->read_lock);
+
+	if (was_throttled)
+		xrusb_submit_read_urbs(xrusb, GFP_KERNEL);
+}
+
+static int xrusb_tty_break_ctl(struct tty_struct *tty, int state)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	int rv;
+
+	rv = xrusb_send_break(xrusb, state ? 0xffff : 0);
+	if (rv < 0) {
+		dev_err(&xrusb->control->dev, "%s - send break failed\n",
+			__func__);
+	}
+	return rv;
+}
+
+static int xrusb_tty_tiocmget(struct tty_struct *tty)
+{
+	struct xrusb *xrusb = tty->driver_data;
+
+	return xrusb_tiocmget(xrusb);
+}
+
+static int xrusb_tty_tiocmset(struct tty_struct *tty,
+			    unsigned int set, unsigned int clear)
+{
+	struct xrusb *xrusb = tty->driver_data;
+
+	return xrusb_tiocmset(xrusb, set, clear);
+}
+
+static int get_serial_info(struct xrusb *xrusb,
+		struct serial_struct __user *info)
+{
+	struct serial_struct tmp;
+
+	if (!info)
+		return -EINVAL;
+
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.type = xrusb->uart_type;
+	tmp.flags = ASYNC_LOW_LATENCY;
+	tmp.xmit_fifo_size = xrusb->writesize;
+	tmp.baud_base = le32_to_cpu(xrusb->line.dwDTERate);
+	tmp.close_delay	= xrusb->port.close_delay / 10;
+	tmp.closing_wait = xrusb->port.closing_wait ==
+		ASYNC_CLOSING_WAIT_NONE ? ASYNC_CLOSING_WAIT_NONE :
+		xrusb->port.closing_wait / 10;
+
+	if (copy_to_user(info, &tmp, sizeof(tmp)))
+		return -EFAULT;
+	else
+		return 0;
+}
+
+static int set_serial_info(struct xrusb *xrusb,
+		struct serial_struct __user *newinfo)
+{
+	struct serial_struct new_serial;
+	unsigned int closing_wait, close_delay;
+	int rv = 0;
+
+	if (copy_from_user(&new_serial, newinfo, sizeof(new_serial)))
+		return -EFAULT;
+
+	close_delay = new_serial.close_delay * 10;
+	closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
+			ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10;
+
+	mutex_lock(&xrusb->port.mutex);
+
+	if (!capable(CAP_SYS_ADMIN)) {
+		if ((close_delay != xrusb->port.close_delay) ||
+		    (closing_wait != xrusb->port.closing_wait)) {
+			rv = -EPERM;
+		} else {
+			rv = -EOPNOTSUPP;
+		}
+	} else {
+		xrusb->port.close_delay  = close_delay;
+		xrusb->port.closing_wait = closing_wait;
+	}
+	xrusb->uart_type = new_serial.type;
+	mutex_unlock(&xrusb->port.mutex);
+	return rv;
+}
+
+/* Enable or disable the rs485 support */
+void xr_config_rs485(struct xrusb *xrusb, struct serial_rs485 *rs485conf)
+{
+	int rv;
+	short reg_value;
+
+	xrusb->rs485 = *rs485conf;
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, &reg_value);
+
+	if (rs485conf->flags & SER_RS485_ENABLED) {
+		if ((rs485conf->delay_rts_after_send) < 0xf) {
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_rs485_delay,
+				rs485conf->delay_rts_after_send);
+		} else {
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_rs485_delay,
+				0xf); // max value is 0xf
+		}
+
+		if (rs485conf->flags & SER_RS485_RTS_ON_SEND) {
+		/* Set logical level for RTS pin equal to 1 when sending: */
+			reg_value &= 0xfff0;
+			reg_value |= 0xb;
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_gpio_mode,
+				reg_value);
+		} else {
+		/*set logical level for RTS pin equal to 0 when sending: */
+			reg_value &= 0xfff0;
+			reg_value |= 0x3;
+			rv = xrusb_set_reg(xrusb,
+				xrusb->reg_map.uart_gpio_mode,
+				reg_value);
+		}
+
+	} else {
+		/* RS-485/422 RTS direction control output disabled */
+		rv = xrusb_set_reg(xrusb,
+			xrusb->reg_map.uart_gpio_mode,
+			reg_value & 0xfff0);
+	}
+
+}
+
+static int xrusb_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
+		unsigned long arg)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	int rv = -ENOIOCTLCMD;
+	struct serial_rs485 rs485conf;
+
+	switch (cmd) {
+	case TIOCGSERIAL: /* gets serial port data */
+		rv = get_serial_info(xrusb,
+			(struct serial_struct __user *) arg);
+		break;
+	case TIOCSSERIAL:
+		rv = set_serial_info(xrusb,
+			(struct serial_struct __user *) arg);
+		break;
+	case TIOCSRS485:
+		if (copy_from_user(&rs485conf, (struct serial_rs485 *) arg,
+					sizeof(rs485conf))) {
+			return -EFAULT;
+		}
+		xr_config_rs485(xrusb, &rs485conf);
+		rv = 0;
+		break;
+	case TIOCGRS485:
+		if (copy_to_user((struct serial_rs485 *) arg, &(xrusb->rs485),
+					sizeof(rs485conf))) {
+			return -EFAULT;
+		}
+		rv = 0;
+		break;
+	}
+	return rv;
+}
+
+static void xrusb_tty_set_termios(struct tty_struct *tty,
+		struct ktermios *termios_old)
+{
+	struct xrusb *xrusb = tty->driver_data;
+	struct ktermios *termios = &tty->termios;
+	unsigned int   cflag = termios->c_cflag;
+	struct usb_cdc_line_coding newline;
+	int newctrl = xrusb->ctrlout;
+
+	xrusb_disable(xrusb);
+	newline.dwDTERate = cpu_to_le32(tty_get_baud_rate(tty));
+	newline.bCharFormat = termios->c_cflag & CSTOPB ? 1 : 0;
+	newline.bParityType = termios->c_cflag & PARENB ?
+				(termios->c_cflag & PARODD ? 1 : 2) +
+				(termios->c_cflag & CMSPAR ? 2 : 0) : 0;
+	xrusb->wide_mode = 0;
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:/*using CS5 replace of the 9 bit data mode*/
+		newline.bDataBits = 9;
+		xrusb->wide_mode = 1;
+		break;
+	case CS6:
+		newline.bDataBits = 6;
+		break;
+	case CS7:
+		newline.bDataBits = 7;
+		break;
+	case CS8:
+	default:
+		newline.bDataBits = 8;
+		break;
+	}
+	/* FIXME: Needs to clear unsupported bits in the termios */
+	xrusb->clocal = ((termios->c_cflag & CLOCAL) != 0);
+
+	if (!newline.dwDTERate) {
+		newline.dwDTERate = xrusb->line.dwDTERate;
+		newctrl &= ~XRUSB_CTRL_DTR;
+	} else {
+		newctrl |=  XRUSB_CTRL_DTR;
+	}
+
+	if (newctrl != xrusb->ctrlout)
+		xrusb_set_control(xrusb, xrusb->ctrlout = newctrl);
+
+	xrusb_set_flow_mode(xrusb, tty, cflag);
+
+	if (xrusb->wide_mode)
+		xrusb_set_wide_mode(xrusb, 1);
+	else if (!xrusb->wide_mode)
+		xrusb_set_wide_mode(xrusb, 0);
+
+	if (memcmp(&xrusb->line, &newline, sizeof(newline))) {
+		memcpy(&xrusb->line, &newline, sizeof(newline));
+		xrusb_set_line(xrusb, &xrusb->line);
+	}
+	xrusb_enable(xrusb);
+}
+
+static const struct tty_port_operations xrusb_port_ops = {
+	.shutdown = xrusb_port_shutdown,
+	.activate = xrusb_port_activate,
+	.destruct = xrusb_port_destruct,
+};
+
+/*
+ * USB probe and disconnect routines.
+ */
+
+/* Little helpers: write/read buffers free */
+static void xrusb_write_buffers_free(struct xrusb *xrusb)
+{
+	int i;
+	struct xrusb_wb *wb;
+
+	for (wb = &xrusb->wb[0], i = 0; i < XRUSB_NW; i++, wb++)
+		usb_free_coherent(xrusb->dev,
+			xrusb->writesize, wb->buf, wb->dmah);
+}
+
+static void xrusb_read_buffers_free(struct xrusb *xrusb)
+{
+	int i;
+
+	for (i = 0; i < xrusb->rx_buflimit; i++)
+		usb_free_coherent(xrusb->dev, xrusb->readsize,
+			  xrusb->read_buffers[i].base,
+			  xrusb->read_buffers[i].dma);
+}
+
+/* Little helper: write buffers allocate */
+static int xrusb_write_buffers_alloc(struct xrusb *xrusb)
+{
+	int i;
+	struct xrusb_wb *wb;
+
+	for (wb = &xrusb->wb[0], i = 0; i < XRUSB_NW; i++, wb++) {
+		wb->buf = usb_alloc_coherent(xrusb->dev,
+			xrusb->writesize, GFP_KERNEL,
+			&wb->dmah);
+		if (!wb->buf) {
+			while (i != 0) {
+				--i;
+				--wb;
+				usb_free_coherent(xrusb->dev,
+					xrusb->writesize,
+					wb->buf, wb->dmah);
+			}
+			return -ENOMEM;
+		}
+	}
+	return 0;
+}
+
+#ifdef CONFIG_GPIOLIB
+static int xrusb_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+	struct xrusb *xrusb = container_of(chip, struct xrusb, xr_gpio);
+	int rv;
+	short gpio_status;
+
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_status,
+				&gpio_status);
+	if (gpio_status&(1 << offset))
+		return 1;
+	else
+		return 0;
+}
+
+static void xrusb_gpio_set(struct gpio_chip *chip, unsigned int offset, int val)
+{
+	struct xrusb *xrusb = container_of(chip, struct xrusb, xr_gpio);
+	int rv, tmp;
+
+	tmp = 1 << offset;
+	if (val)
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, tmp);
+	else
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr, tmp);
+}
+
+static int xrusb_gpio_dir_input(struct gpio_chip *chip, unsigned int offset)
+{
+	int rv;
+	short  dir_value;
+	struct xrusb *xrusb = container_of(chip, struct xrusb, xr_gpio);
+
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_dir, &dir_value);
+	dir_value &= ~(1 << offset);
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, (int)dir_value);
+	return 0;
+}
+
+static int xrusb_gpio_dir_output(struct gpio_chip *chip,
+						unsigned int offset, int val)
+{
+	int rv;
+	short tmp;
+	struct xrusb *xrusb = container_of(chip, struct xrusb, xr_gpio);
+
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_dir, &tmp);
+	tmp |= (1 << offset);
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, (int)tmp);
+
+	if (offset > 7) {
+		rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, &tmp);
+		tmp &= ~(1 << offset);
+		rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode,
+						(int)tmp);
+	}
+	return 0;
+}
+#endif
+
+static int xrusb_probe(struct usb_interface *intf,
+		     const struct usb_device_id *id)
+{
+	struct usb_cdc_union_desc *union_header = NULL;
+	unsigned char *buffer = intf->altsetting->extra;
+	int buflen = intf->altsetting->extralen;
+	struct usb_interface *control_interface;
+	struct usb_interface *data_interface;
+	struct usb_endpoint_descriptor *epctrl = NULL;
+	struct usb_endpoint_descriptor *epread = NULL;
+	struct usb_endpoint_descriptor *epwrite = NULL;
+	struct usb_device *usb_dev = interface_to_usbdev(intf);
+	struct xrusb *xrusb;
+	int minor;
+	int ctrlsize, readsize;
+	u8 *buf;
+	u8 ac_management_function = 0;
+	u8 call_management_function = 0;
+	int call_interface_num = -1;
+	int data_interface_num = -1;
+	unsigned long quirks;
+	int num_rx_buf;
+	int i;
+	int combined_interfaces = 0;
+	struct device *tty_dev;
+	int rv = -ENOMEM;
+#ifdef CONFIG_GPIOLIB
+	int gpiochip_base;
+#endif
+
+	/* normal quirks */
+	quirks = (unsigned long)id->driver_info;
+
+	if (quirks == IGNORE_DEVICE)
+		return -ENODEV;
+
+	num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : XRUSB_NR;
+
+	/* handle quirks deadly to normal probing*/
+	if (quirks == NO_UNION_NORMAL) {
+		data_interface = usb_ifnum_to_if(usb_dev, 1);
+		control_interface = usb_ifnum_to_if(usb_dev, 0);
+	}
+
+	/* normal probing*/
+	if (!buffer) {
+		dev_err(&intf->dev, "Weird descriptor references\n");
+		return -EINVAL;
+	}
+
+	if (!buflen) {
+		if (intf->cur_altsetting->endpoint &&
+				intf->cur_altsetting->endpoint->extralen &&
+				intf->cur_altsetting->endpoint->extra) {
+			dev_dbg(&intf->dev,
+				"Seeking extra descriptors on endpoint\n");
+			buflen = intf->cur_altsetting->endpoint->extralen;
+			buffer = intf->cur_altsetting->endpoint->extra;
+		} else {
+			dev_err(&intf->dev,
+				"Zero length descriptor references\n");
+			return -EINVAL;
+		}
+	}
+
+	while (buflen > 0) {
+		if (buffer[1] != USB_DT_CS_INTERFACE) {
+			dev_err(&intf->dev, "skipping garbage\n");
+			goto next_desc;
+		}
+
+		switch (buffer[2]) {
+		case USB_CDC_UNION_TYPE: /* we've found it */
+			if (union_header) {
+				dev_err(&intf->dev, "> 1  union descriptor");
+				dev_err(&intf->dev, "skipping ...\n");
+				goto next_desc;
+			}
+			union_header = (struct usb_cdc_union_desc *)buffer;
+			break;
+		case USB_CDC_HEADER_TYPE: /* maybe check version */
+			break; /* for now we ignore it */
+		case USB_CDC_ACM_TYPE:
+			ac_management_function = buffer[3];
+			break;
+		case USB_CDC_CALL_MANAGEMENT_TYPE:
+			call_management_function = buffer[3];
+			call_interface_num = buffer[4];
+			//if ((quirks & NOT_A_MODEM) == 0 &&
+			//	(call_management_function & 3) != 3)
+			//	dev_err(&intf->dev, "This device cannot
+			//		do calls on its own. It is
+			//		not a modem.\n");
+			break;
+		default:
+			/* there are LOTS more CDC descriptors that
+			 * could legitimately be found here.
+			 */
+			dev_dbg(&intf->dev, "Ignoring descriptor: ");
+			dev_dbg(&intf->dev, "type %02x, length %d\n",
+				buffer[2], buffer[0]);
+			break;
+		}
+next_desc:
+		buflen -= buffer[0];
+		buffer += buffer[0];
+	}
+
+	control_interface = usb_ifnum_to_if(usb_dev,
+		union_header->bMasterInterface0);
+	data_interface = usb_ifnum_to_if(usb_dev,
+		(data_interface_num = union_header->bSlaveInterface0));
+
+	if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 ||
+	    control_interface->cur_altsetting->desc.bNumEndpoints == 0) {
+		return -EINVAL;
+	}
+
+	epctrl = &control_interface->cur_altsetting->endpoint[0].desc;
+	epread = &data_interface->cur_altsetting->endpoint[0].desc;
+	epwrite = &data_interface->cur_altsetting->endpoint[1].desc;
+
+
+	/* workaround for switched endpoints */
+	if (!usb_endpoint_dir_in(epread)) {
+		/* descriptors are swapped */
+		struct usb_endpoint_descriptor *t;
+
+		t = epread;
+		epread = epwrite;
+		epwrite = t;
+	}
+
+	xrusb = kzalloc(sizeof(struct xrusb), GFP_KERNEL);
+	if (xrusb == NULL)
+		goto alloc_fail;
+
+	minor = xrusb_alloc_minor(xrusb);
+	if (minor == XRUSB_TTY_MINORS) {
+		dev_err(&intf->dev, "no more free xrusb devices\n");
+		kfree(xrusb);
+		return -ENODEV;
+	}
+
+	ctrlsize = usb_endpoint_maxp(epctrl);
+	readsize = usb_endpoint_maxp(epread) *
+				(quirks == SINGLE_RX_URB ? 1 : 2);
+	xrusb->combined_interfaces = combined_interfaces;
+	xrusb->writesize = usb_endpoint_maxp(epwrite) * 20;
+	xrusb->control = control_interface;
+	xrusb->data = data_interface;
+	xrusb->minor = minor;
+	xrusb->dev = usb_dev;
+	xrusb->ctrl_caps = ac_management_function;
+	if (quirks & NO_CAP_LINE)
+		xrusb->ctrl_caps &= ~USB_CDC_CAP_LINE;
+	xrusb->ctrlsize = ctrlsize;
+	xrusb->readsize = readsize;
+	xrusb->rx_buflimit = num_rx_buf;
+	INIT_WORK(&xrusb->work, xrusb_softint);
+	spin_lock_init(&xrusb->write_lock);
+	spin_lock_init(&xrusb->read_lock);
+	mutex_init(&xrusb->mutex);
+	xrusb->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress);
+	xrusb->is_int_ep = usb_endpoint_xfer_int(epread);
+	if (xrusb->is_int_ep)
+		xrusb->bInterval = epread->bInterval;
+	tty_port_init(&xrusb->port);
+	xrusb->port.ops = &xrusb_port_ops;
+	xrusb->DeviceVendor = id->idVendor;
+	xrusb->DeviceProduct = id->idProduct;
+	xrusb->uart_type = 4; /*#define PORT_16550A	4*/
+	xrusb->channel = epwrite->bEndpointAddress;
+
+	buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL,
+		&xrusb->ctrl_dma);
+	if (!buf) {
+		dev_err(&intf->dev, "out of memory (ctrl buffer alloc)\n");
+		goto alloc_fail2;
+	}
+	xrusb->ctrl_buffer = buf;
+
+	if (xrusb_write_buffers_alloc(xrusb) < 0) {
+		dev_err(&intf->dev, "out of memory (write buffer alloc)\n");
+		goto alloc_fail4;
+	}
+
+	xrusb->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!xrusb->ctrlurb) {
+		dev_err(&intf->dev, "out of memory (ctrlurb kmalloc)\n");
+		goto alloc_fail5;
+	}
+	for (i = 0; i < num_rx_buf; i++) {
+		struct xrusb_rb *rb = &(xrusb->read_buffers[i]);
+		struct urb *urb;
+
+		rb->base = usb_alloc_coherent(xrusb->dev, readsize, GFP_KERNEL,
+								&rb->dma);
+		if (!rb->base) {
+			dev_err(&intf->dev, "out of memory ");
+			dev_err(&intf->dev, "(read bufs usb_alloc_coherent)\n");
+			goto alloc_fail6;
+		}
+		rb->index = i;
+		rb->instance = xrusb;
+
+		urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!urb) {
+			dev_err(&intf->dev, "out of memory ");
+			dev_err(&intf->dev, "(read bufs usb_alloc_urb)\n");
+			goto alloc_fail6;
+		}
+		urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+		urb->transfer_dma = rb->dma;
+		if (xrusb->is_int_ep) {
+			usb_fill_int_urb(urb, xrusb->dev,
+					 xrusb->rx_endpoint,
+					 rb->base,
+					 xrusb->readsize,
+					 xrusb_read_bulk_callback, rb,
+					 xrusb->bInterval);
+		} else {
+			usb_fill_bulk_urb(urb, xrusb->dev,
+					  xrusb->rx_endpoint,
+					  rb->base,
+					  xrusb->readsize,
+					  xrusb_read_bulk_callback, rb);
+		}
+
+		xrusb->read_urbs[i] = urb;
+		__set_bit(i, &xrusb->read_urbs_free);
+	}
+	for (i = 0; i < XRUSB_NW; i++) {
+		struct xrusb_wb *snd = &(xrusb->wb[i]);
+
+		snd->urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (snd->urb == NULL) {
+			dev_err(&intf->dev, "out of memory ");
+			dev_err(&intf->dev, "(write urbs usb_alloc_urb)\n");
+			goto alloc_fail7;
+		}
+
+		if (usb_endpoint_xfer_int(epwrite)) {
+			usb_fill_int_urb(snd->urb,
+				usb_dev,
+				usb_sndintpipe(usb_dev,
+					epwrite->bEndpointAddress),
+				NULL,
+				xrusb->writesize,
+				xrusb_write_bulk,
+				snd,
+				epwrite->bInterval);
+		} else {
+			usb_fill_bulk_urb(snd->urb,
+				usb_dev,
+				usb_sndbulkpipe(usb_dev,
+					epwrite->bEndpointAddress),
+				NULL,
+				xrusb->writesize,
+				xrusb_write_bulk,
+				snd);
+		}
+		snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+		snd->instance = xrusb;
+	}
+
+	usb_set_intfdata(intf, xrusb);
+
+	i = device_create_file(&intf->dev, &dev_attr_bmCapabilities);
+	if (i < 0)
+		goto alloc_fail7;
+
+	usb_fill_int_urb(xrusb->ctrlurb, usb_dev,
+			 usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress),
+			 xrusb->ctrl_buffer, ctrlsize, xrusb_ctrl_irq, xrusb,
+			 /* works around buggy devices */
+			 epctrl->bInterval ? epctrl->bInterval : 0xff);
+	xrusb->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+	xrusb->ctrlurb->transfer_dma = xrusb->ctrl_dma;
+
+	xrusb_reg_init(xrusb);
+	xrusb_set_control(xrusb, xrusb->ctrlout);
+
+	xrusb->line.dwDTERate = cpu_to_le32(9600);
+	xrusb->line.bDataBits = 8;
+	xrusb_set_line(xrusb, &xrusb->line);
+
+	usb_driver_claim_interface(&xrusb_driver, data_interface, xrusb);
+	usb_set_intfdata(data_interface, xrusb);
+
+	usb_get_intf(control_interface);
+
+	tty_dev = tty_port_register_device(&xrusb->port,
+		xrusb_tty_driver, minor, &control_interface->dev);
+
+	if (IS_ERR(tty_dev)) {
+		rv = PTR_ERR(tty_dev);
+		goto alloc_fail8;
+	}
+#ifdef CONFIG_GPIOLIB
+	/* Setup GPIO cotroller */
+	gpiochip_base = xrusb->dev->portnum*100 + xrusb->channel*10;
+	if (gpiochip_base > 502) {
+		// base must be 502 or less
+		gpiochip_base = 0;
+	}
+
+	xrusb->xr_gpio.owner		= THIS_MODULE;
+	xrusb->xr_gpio.label		= dev_name(&control_interface->dev);
+	xrusb->xr_gpio.direction_input	= xrusb_gpio_dir_input;
+	xrusb->xr_gpio.get			= xrusb_gpio_get;
+	xrusb->xr_gpio.direction_output	= xrusb_gpio_dir_output;
+	xrusb->xr_gpio.set			= xrusb_gpio_set;
+	xrusb->xr_gpio.base			= gpiochip_base;
+	xrusb->xr_gpio.ngpio		= 10;
+	xrusb->xr_gpio.can_sleep	= 1;
+
+	rv = gpiochip_add(&xrusb->xr_gpio);
+
+	if (rv != 0) {
+		// gpiochip numbers not available, start from 0
+		xrusb->xr_gpio.base = 0;
+	}
+
+	while (rv != 0) {
+		xrusb->xr_gpio.base += 10;
+
+		if (xrusb->xr_gpio.base > 502) {
+		// max gpio number = 512
+		// we ran out of gpios??
+			break;
+		}
+		rv = gpiochip_add(&xrusb->xr_gpio);
+	}
+	xrusb->rv_gpio_created = rv;
+	if (rv == 0) {
+		dev_dbg(&xrusb->control->dev, "gpiochip%d added",
+			xrusb->xr_gpio.base);
+	} else {
+		dev_dbg(&xrusb->control->dev, "failed to add gpiochip\n");
+	}
+
+#endif
+
+	return 0;
+
+alloc_fail8:
+	device_remove_file(&xrusb->control->dev, &dev_attr_bmCapabilities);
+alloc_fail7:
+	usb_set_intfdata(intf, NULL);
+	for (i = 0; i < XRUSB_NW; i++)
+		usb_free_urb(xrusb->wb[i].urb);
+alloc_fail6:
+	for (i = 0; i < num_rx_buf; i++)
+		usb_free_urb(xrusb->read_urbs[i]);
+	xrusb_read_buffers_free(xrusb);
+	usb_free_urb(xrusb->ctrlurb);
+alloc_fail5:
+	xrusb_write_buffers_free(xrusb);
+alloc_fail4:
+	usb_free_coherent(usb_dev, ctrlsize,
+		xrusb->ctrl_buffer, xrusb->ctrl_dma);
+alloc_fail2:
+	xrusb_release_minor(xrusb);
+	kfree(xrusb);
+alloc_fail:
+	return rv;
+}
+
+static void stop_data_traffic(struct xrusb *xrusb)
+{
+	int i;
+
+	//dev_dbg(&xrusb->control->dev, "%s\n", __func__);
+
+	usb_kill_urb(xrusb->ctrlurb);
+	for (i = 0; i < XRUSB_NW; i++)
+		usb_kill_urb(xrusb->wb[i].urb);
+	for (i = 0; i < xrusb->rx_buflimit; i++)
+		usb_kill_urb(xrusb->read_urbs[i]);
+
+	cancel_work_sync(&xrusb->work);
+}
+static void xrusb_disconnect(struct usb_interface *intf)
+{
+	struct xrusb *xrusb = usb_get_intfdata(intf);
+	struct tty_struct *tty;
+	/* sibling interface is already cleaning up */
+	if (!xrusb)
+		return;
+
+	mutex_lock(&xrusb->mutex);
+	xrusb->disconnected = true;
+
+	device_remove_file(&xrusb->control->dev,
+		&dev_attr_bmCapabilities);
+	usb_set_intfdata(xrusb->control, NULL);
+	usb_set_intfdata(xrusb->data, NULL);
+	mutex_unlock(&xrusb->mutex);
+
+	tty = tty_port_tty_get(&xrusb->port);
+	if (tty) {
+		tty_vhangup(tty);
+		tty_kref_put(tty);
+	}
+
+	stop_data_traffic(xrusb);
+
+	tty_unregister_device(xrusb_tty_driver, xrusb->minor);
+
+	xrusb_write_buffers_free(xrusb);
+	usb_free_coherent(xrusb->dev, xrusb->ctrlsize,
+		xrusb->ctrl_buffer, xrusb->ctrl_dma);
+	xrusb_read_buffers_free(xrusb);
+
+	if (!xrusb->combined_interfaces) {
+		usb_driver_release_interface(&xrusb_driver,
+			intf == xrusb->control ?
+			xrusb->data : xrusb->control);
+	}
+
+	tty_port_put(&xrusb->port);
+}
+#ifdef CONFIG_PM
+static int xrusb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct xrusb *xrusb = usb_get_intfdata(intf);
+	int cnt;
+	unsigned short dir_tmp, mode_tmp, state_tmp;
+	int rv;
+
+	if (PMSG_IS_AUTO(message)) {
+		int b;
+
+		spin_lock_irq(&xrusb->write_lock);
+		b = xrusb->transmitting;
+		spin_unlock_irq(&xrusb->write_lock);
+		if (b)
+			return -EBUSY;
+	}
+
+	spin_lock_irq(&xrusb->read_lock);
+	spin_lock(&xrusb->write_lock);
+
+	//Save the register values before suspend
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_dir, &dir_tmp);
+	xrusb->gpio_dir_saved = dir_tmp;
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_status, &state_tmp);
+	xrusb->gpio_state_saved = state_tmp;
+	rv = xrusb_get_reg(xrusb, xrusb->reg_map.uart_gpio_mode, &mode_tmp);
+	xrusb->gpio_mode_saved = mode_tmp;
+	cnt = xrusb->susp_count++;
+	spin_unlock(&xrusb->write_lock);
+	spin_unlock_irq(&xrusb->read_lock);
+
+	if (cnt)
+		return 0;
+
+	if (test_bit(ASYNCB_INITIALIZED, &xrusb->port.flags))
+		stop_data_traffic(xrusb);
+
+	return 0;
+}
+
+static int xrusb_resume(struct usb_interface *intf)
+{
+	struct xrusb *xrusb = usb_get_intfdata(intf);
+	struct xrusb_wb *wb;
+	int rv = 0;
+	int cnt;
+	unsigned short gpio_state, gpio_dir, gpio_mode;
+
+	xrusb_reg_init(xrusb);
+
+	spin_lock_irq(&xrusb->read_lock);
+	xrusb->susp_count -= 1;
+	cnt = xrusb->susp_count;
+	spin_unlock_irq(&xrusb->read_lock);
+
+	if (cnt)
+		return 0;
+
+	if (test_bit(ASYNCB_INITIALIZED, &xrusb->port.flags)) {
+		rv = usb_submit_urb(xrusb->ctrlurb, GFP_NOIO);
+
+		spin_lock_irq(&xrusb->write_lock);
+		if (xrusb->delayed_wb) {
+			wb = xrusb->delayed_wb;
+			xrusb->delayed_wb = NULL;
+			spin_unlock_irq(&xrusb->write_lock);
+			xrusb_start_wb(xrusb, wb);
+		} else {
+			spin_unlock_irq(&xrusb->write_lock);
+		}
+
+		/*
+		 * delayed error checking because we must
+		 * do the write path at all cost
+		 */
+		if (rv < 0)
+			goto err_out;
+
+
+		rv = xrusb_submit_read_urbs(xrusb, GFP_NOIO);
+	}
+	gpio_dir = xrusb->gpio_dir_saved;
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_dir, gpio_dir);
+
+	gpio_mode = xrusb->gpio_mode_saved;
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_mode, gpio_mode);
+
+	gpio_state = xrusb->gpio_state_saved;
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_set, gpio_state);
+	rv = xrusb_set_reg(xrusb, xrusb->reg_map.uart_gpio_clr, ~gpio_state);
+err_out:
+	return rv;
+}
+
+static int xrusb_reset_resume(struct usb_interface *intf)
+{
+	struct xrusb *xrusb = usb_get_intfdata(intf);
+
+	if (test_bit(ASYNCB_INITIALIZED, &xrusb->port.flags))
+		tty_port_tty_hangup(&xrusb->port, false);
+	return xrusb_resume(intf);
+}
+
+#endif /* CONFIG_PM */
+
+/*
+ * USB driver structure.
+ */
+static const struct usb_device_id xrusb_ids[] = {
+	{ USB_DEVICE(0x04e2, 0x1410)},
+	{ USB_DEVICE(0x04e2, 0x1411)},
+	{ USB_DEVICE(0x04e2, 0x1412)},
+	{ USB_DEVICE(0x04e2, 0x1414)},
+	{ USB_DEVICE(0x04e2, 0x1420)},
+	{ USB_DEVICE(0x04e2, 0x1422)},
+	{ USB_DEVICE(0x04e2, 0x1424)},
+	{ USB_DEVICE(0x04e2, 0x1400)}, // XR2280x ch A
+	{ USB_DEVICE(0x04e2, 0x1401)}, // XR2280x ch B
+	{ USB_DEVICE(0x04e2, 0x1402)}, // XR2280x ch C
+	{ USB_DEVICE(0x04e2, 0x1403)}, // XR2280x ch D
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, xrusb_ids);
+
+static struct usb_driver xrusb_driver = {
+	.name =				"xrusb_serial",
+	.probe =			xrusb_probe,
+	.disconnect =		xrusb_disconnect,
+#ifdef CONFIG_PM
+	.suspend =			xrusb_suspend,
+	.resume =			xrusb_resume,
+	.reset_resume =		xrusb_reset_resume,
+#endif
+	.id_table =			xrusb_ids,
+#ifdef CONFIG_PM
+	.supports_autosuspend = 1,
+#endif
+	.disable_hub_initiated_lpm = 1,
+};
+
+/*
+ * TTY driver structures.
+ */
+
+static const struct tty_operations xrusb_ops = {
+	.install =			xrusb_tty_install,
+	.open =				xrusb_tty_open,
+	.close =			xrusb_tty_close,
+	.cleanup =			xrusb_tty_cleanup,
+	.hangup =			xrusb_tty_hangup,
+	.write =			xrusb_tty_write,
+	.write_room =		xrusb_tty_write_room,
+	.ioctl =			xrusb_tty_ioctl,
+	.throttle =			xrusb_tty_throttle,
+	.unthrottle =		xrusb_tty_unthrottle,
+	.chars_in_buffer =	xrusb_tty_chars_in_buffer,
+	.break_ctl =		xrusb_tty_break_ctl,
+	.set_termios =		xrusb_tty_set_termios,
+	.tiocmget =			xrusb_tty_tiocmget,
+	.tiocmset =			xrusb_tty_tiocmset,
+};
+
+/*
+ * Init / exit.
+ */
+
+static int __init xrusb_init(void)
+{
+	int rv;
+
+	xrusb_tty_driver = alloc_tty_driver(XRUSB_TTY_MINORS);
+	if (!xrusb_tty_driver)
+		return -ENOMEM;
+
+	xrusb_tty_driver->driver_name = "xrusb",
+	xrusb_tty_driver->name = "ttyXRUSB",
+	xrusb_tty_driver->major = XRUSB_TTY_MAJOR,
+	xrusb_tty_driver->minor_start = 0,
+	xrusb_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
+	xrusb_tty_driver->subtype = SERIAL_TYPE_NORMAL,
+	xrusb_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	xrusb_tty_driver->init_termios = tty_std_termios;
+	xrusb_tty_driver->init_termios.c_cflag = B9600 | CS8 |
+		CREAD | HUPCL | CLOCAL;
+	tty_set_operations(xrusb_tty_driver, &xrusb_ops);
+
+	rv = tty_register_driver(xrusb_tty_driver);
+	if (rv) {
+		put_tty_driver(xrusb_tty_driver);
+		return rv;
+	}
+
+	rv = usb_register(&xrusb_driver);
+	if (rv) {
+		tty_unregister_driver(xrusb_tty_driver);
+		put_tty_driver(xrusb_tty_driver);
+		return rv;
+	}
+
+	//printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n");
+	pr_info(KBUILD_MODNAME ": " DRIVER_DESC "\n");
+
+	return 0;
+}
+
+static void __exit xrusb_exit(void)
+{
+	usb_deregister(&xrusb_driver);
+	tty_unregister_driver(xrusb_tty_driver);
+	put_tty_driver(xrusb_tty_driver);
+}
+
+module_init(xrusb_init);
+module_exit(xrusb_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_CHARDEV_MAJOR(XRUSB_TTY_MAJOR);
diff --git a/drivers/usb/serial/xrusb_serial.h b/drivers/usb/serial/xrusb_serial.h
new file mode 100644
index 000000000000..7b743d3da68a
--- /dev/null
+++ b/drivers/usb/serial/xrusb_serial.h
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Includes for xrusb_serial.c
+ */
+
+/*
+ * CMSPAR, some architectures can't have space and mark parity.
+ */
+
+#ifndef CMSPAR
+#define CMSPAR		0
+#endif
+
+/*
+ * Major and minor numbers.
+ */
+
+#define XRUSB_TTY_MAJOR		266
+#define XRUSB_TTY_MINORS	256
+
+/*
+ * Requests.
+ */
+
+#define USB_RT_XRUSB		(USB_TYPE_CLASS | USB_RECIP_INTERFACE)
+
+/*
+ * Output control lines.
+ */
+
+#define XRUSB_CTRL_DTR		0x01
+#define XRUSB_CTRL_RTS		0x02
+
+/*
+ * Input control lines and line errors.
+ */
+
+#define XRUSB_CTRL_DCD		0x01
+#define XRUSB_CTRL_DSR		0x02
+#define XRUSB_CTRL_BRK		0x04
+#define XRUSB_CTRL_RI		0x08
+
+#define XRUSB_CTRL_FRAMING	0x10
+#define XRUSB_CTRL_PARITY	0x20
+#define XRUSB_CTRL_OVERRUN	0x40
+
+/*
+ * Internal driver structures.
+ */
+
+/*
+ * The only reason to have several buffers is to accommodate assumptions
+ * in line disciplines. They ask for empty space amount, receive our URB size,
+ * and proceed to issue several 1-character writes, assuming they will fit.
+ * The very first write takes a complete URB. Fortunately, this only happens
+ * when processing onlcr, so we only need 2 buffers. These values must be
+ * powers of 2.
+ */
+
+#define XRUSB_NW  16
+#define XRUSB_NR  16
+
+#define WIDE_MODE_PARITY	0x1
+#define WIDE_MODE_BREAK		0x2
+#define WIDE_MODE_FRAME		0x4
+#define WIDE_MODE_OVERRUN	0x8
+
+struct xrusb_wb {
+	unsigned char *buf;
+	dma_addr_t dmah;
+	int len;
+	int use;
+	struct urb	*urb;
+	struct xrusb	*instance;
+};
+
+struct xrusb_rb {
+	int	size;
+	unsigned char	*base;
+	dma_addr_t	dma;
+	int	index;
+	struct xrusb	*instance;
+};
+
+struct reg_addr_map {
+	unsigned int uart_enable;
+	unsigned int uart_format;
+	unsigned int uart_flow;
+	unsigned int uart_xon_char;
+	unsigned int uart_xoff_char;
+	unsigned int uart_tx_break;
+	unsigned int uart_rs485_delay;
+	unsigned int uart_gpio_mode;
+	unsigned int uart_gpio_dir;
+	unsigned int uart_gpio_set;
+	unsigned int uart_gpio_clr;
+	unsigned int uart_gpio_status;
+	unsigned int uart_gpio_int_mask;
+	unsigned int uart_customized_int;
+	unsigned int uart_gpio_open_drain;
+	unsigned int uart_gpio_pull_up_enable;
+	unsigned int uart_gpio_pull_down_enable;
+	unsigned int uart_loopback;
+	unsigned int uart_low_latency;
+	unsigned int uart_custom_driver;
+};
+
+struct xrusb {
+	struct usb_device *dev;				/* the corresponding usb device */
+	struct usb_interface *control;		/* control interface */
+	struct usb_interface *data;			/* data interface */
+	struct tty_port port;				/* our tty port data */
+	struct urb *ctrlurb;				/* urbs */
+	u8 *ctrl_buffer;					/* buffers of urbs */
+	dma_addr_t ctrl_dma;				/* dma handles of buffers */
+	struct xrusb_wb wb[XRUSB_NW];
+	unsigned long read_urbs_free;
+	struct urb *read_urbs[XRUSB_NR];
+	struct xrusb_rb read_buffers[XRUSB_NR];
+	struct xrusb_wb *putbuffer;
+	int rx_buflimit;
+	int rx_endpoint;
+	spinlock_t read_lock;
+	int transmitting;
+	spinlock_t write_lock;
+	struct mutex mutex;
+	bool disconnected;
+	struct usb_cdc_line_coding line;	/* bits, stop, parity */
+	struct work_struct work;			/* work queue entry for line discipline waking up */
+	unsigned int ctrlin;				/* input control lines (DCD, DSR, RI, break, overruns) */
+	unsigned int ctrlout;				/* output control lines (DTR, RTS) */
+	unsigned int writesize;				/* max packet size for the output bulk endpoint */
+	unsigned int readsize, ctrlsize;	/* buffer sizes for freeing */
+	unsigned int minor;					/* xrusb minor number */
+	unsigned char clocal;				/* termios CLOCAL */
+	unsigned int ctrl_caps;				/* control capabilities from the class specific header */
+	unsigned int susp_count;			/* number of suspended interfaces */
+	unsigned int combined_interfaces:1;	/* control and data collapsed */
+	unsigned int is_int_ep:1;			/* interrupt endpoints contrary to spec used */
+	unsigned int throttled:1;			/* actually throttled */
+	unsigned int throttle_req:1;		/* throttle requested */
+	u8 bInterval;
+
+	struct xrusb_wb *delayed_wb;		/* write queued for a device about to be woken */
+	unsigned int channel;
+	int wide_mode;						/* USB: wide mode, TTY: flags per character */
+	int have_extra_byte;
+	int extra_byte;
+	int uart_type;
+	unsigned short DeviceVendor;
+	unsigned short DeviceProduct;
+	struct serial_rs485 rs485;			/* rs485 settings */
+	int found_smbios_caracalla_config;	/* Modes pre-programmed in BIOS for Caracalla board */
+
+	struct reg_addr_map reg_map;
+	#ifdef CONFIG_GPIOLIB
+	struct gpio_chip xr_gpio;
+	int rv_gpio_created;
+	#endif
+	unsigned short gpio_dir_saved;
+	unsigned short gpio_state_saved;
+	unsigned short gpio_mode_saved;
+};
+
+#define CDC_DATA_INTERFACE_TYPE	0x0a
+
+/* constants describing various quirks and errors */
+#define NO_UNION_NORMAL			1
+#define SINGLE_RX_URB			2
+#define NO_CAP_LINE				4
+#define NOT_A_MODEM				8
+#define NO_DATA_INTERFACE		16
+#define IGNORE_DEVICE			32
+
+/* USB Requests */
+#define XRUSB_GET_CHIP_ID		0xFF
+#define XRUSB_SET_XR2280X		5
+#define XRUSB_GET_XR2280X		5
+#define XRUSB_SET_XR21B142X		0
+#define XRUSB_GET_XR21B142X		0
+#define XRUSB_SET_XR21V141X		0
+#define XRUSB_GET_XR21V141X		1
+#define XRUSB_SET_XR21B1411		0
+#define XRUSB_GET_XR21B1411		1
+
+#define XR21V141X_CLOCK_DIVISOR_0	0x004
+#define XR21V141X_CLOCK_DIVISOR_1	0x005
+#define XR21V141X_CLOCK_DIVISOR_2	0x006
+#define XR21V141X_TX_CLOCK_MASK_0	0x007
+#define XR21V141X_TX_CLOCK_MASK_1	0x008
+#define XR21V141X_RX_CLOCK_MASK_0	0x009
+#define XR21V141X_RX_CLOCK_MASK_1	0x00a
+
+/* XR21V141x UART Manager Registers */
+#define XR21V141x_URM_REG_BLOCK		4
+#define XR21V141x_URM_FIFO_ENABLE_REG	0x10
+#define XR21V141x_URM_ENABLE_TX_FIFO	0x1
+#define XR21V141x_URM_ENABLE_RX_FIFO	0x2
+
+#define XR21V141x_URM_RX_FIFO_RESET	0x18
+#define XR21V141x_URM_TX_FIFO_RESET	0x1C
+
+#define XR21V141x_UART_CUSTOM_BLOCK	0x66
+
+#define UART_ENABLE_TX			1
+#define UART_ENABLE_RX			2
+
+#define UART_GPIO_CLR_DTR		0x8
+#define UART_GPIO_SET_DTR		0x8
+#define UART_GPIO_CLR_RTS		0x20
+#define UART_GPIO_SET_RTS		0x20
+
+#define LOOPBACK_ENABLE_TX_RX		1
+#define LOOPBACK_ENABLE_RTS_CTS		2
+#define LOOPBACK_ENABLE_DTR_DSR		4
+
+#define UART_FLOW_MODE_NONE		0x0
+#define UART_FLOW_MODE_HW		0x1
+#define UART_FLOW_MODE_SW		0x2
+
+#define UART_GPIO_MODE_SEL_GPIO		0x0
+#define UART_GPIO_MODE_SEL_RTS_CTS	0x1
+
+#define XR21V141X_WIDE_MODE_REG         3
+
+#define XR21B1411_UART_ENABLE		0xC00
+#define XR21B1411_WIDE_MODE_REG		0xD02
+
+#define XR21B142x_UART_ENABLE		0x00
+#define XR21B142X_TX_WIDE_MODE_REG	0x42
+#define XR21B142X_RX_WIDE_MODE_REG	0x45
+#define XR2280X_TX_WIDE_MODE_REG	0x62
+#define XR2280X_RX_WIDE_MODE_REG	0x65
+#define XR2280x_FUNC_MGR_OFFSET		0x40

^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial adapters.
@ 2018-04-06 14:45 Greg Kroah-Hartman
  0 siblings, 0 replies; 15+ messages in thread
From: Greg Kroah-Hartman @ 2018-04-06 14:45 UTC (permalink / raw)
  To: Patong Yang; +Cc: johan, pyang, linux-usb

On Thu, Apr 05, 2018 at 08:42:57PM -0700, Patong Yang wrote:
> On Wed, Apr 4, 2018 at 11:26 PM, Greg KH <gregkh@linuxfoundation.org> wrote:
> > > This same customer is designing a new board to support the same
> transceiver
> > > configurations.  Instead of using the BIOS settings, they would like to
> use
> > > a user-space application to configure the additional GPIOs in a newer
> USB
> > > UART to set the transceiver modes, however, there are no standard APIs
> to
> > > support setting different modes, hence the custom IOCTLs.
> >
> > What is wrong with the current GPIO Linux api?  Doesn't that support
> > everything you need here?
> 
> Sorry, I am a kernel newbie and was not aware of the GPIO support.  I found
> and read through the documentation.  It's unclear to me how to map the
> GPIOs of the USB UART so that I can access them using the APIs.

That is up to you to figure out how that works for your device.

> Can someone point me to an example of how that can be done?  Would the
> support be added in the xrusb_serial driver or in a separate driver?

Probably in this driver, but look around at the current examples in the
kernel for other ideas.

> I also went through the documentation for the standard API for RS485
> support.  I can use the standard IOCTLs for enabling RS485 half-duplex
> control with the RTS pin.  However, is there a way to specify a
> different pin?  Some of the Exar USB UARTs can use a different pin for
> that function.

Why would they want to use a different pin?  Anyway, propose a standard
tty ioctl for that and we can take it from there.

> There is a custom IOCTL for enabling the "wide mode" feature in the Exar
> USB UARTs that inserts an additional byte in the RX FIFO that reports
> the parity, framing, parity and break error status for every data byte
> received.  This is the equivalent of reading LSR and RHR in 16550
> UARTs, and is useful for multidrop/9-bit mode applications.  I don't
> think any other USB UART has this function so there's no standard
> IOCTL for it.  Can I use a custom IOCTL for this or is there a better
> approach?

Isn't there already an option for that for other UARTs that do this?

> Also, as I mentioned in my original patch submission, the driver checks for
> a port_config file at startup and loads the appropriate settings.  The
> driver was creating the port_config file in the /etc/ directory when
> some of the custom IOCTLs were called to store the settings for the
> next time that the driver was loaded.  Without the custom IOCTLs, I
> was thinking that the user would just create those files in the /etc/
> directory if they wanted the driver to load/store the port
> configurations.  Do you see any issues with doing that?

No Linux kernel driver can ever read a file from a disk.  That's not how
Linux works at all, sorry.  Configuration comes from userspace programs
when they want to configure things, not when the kernel finds a device.

The kernel does notify userspace when devices are found, so you can run
your program at that point in time.

Hope this helps,

greg k-h
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" 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] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial adapters.
@ 2018-04-05  7:38 Oliver Neukum
  0 siblings, 0 replies; 15+ messages in thread
From: Oliver Neukum @ 2018-04-05  7:38 UTC (permalink / raw)
  To: Greg KH, Patong Yang; +Cc: johan, pyang, linux-usb

Am Donnerstag, den 05.04.2018, 08:26 +0200 schrieb Greg KH:
> > The USB device can describe itself properly.  The SMBIOS function is a
> > requirement from our customer who designed a board using our device where
> > their CPU reads from a specific BIOS location and initializes GPIOs based
> > on the settings.  These GPIOs set the mode of the transceiver (LOOPBACK,
> > RS232, RS485, or RS422).  Therefore, the driver also reads the same
> > settings from the BIOS, so that it can enable the appropriate mode.
> 
> That logic can be done in userspace, no need to do that within the
> kernel, right?

No, not if you want to do full RS485 and similar stuff.

Yet ACM is not a full serial interface. It is for modems.
That raises two questions

a) why not add this to cdc-acm?
b) why custom ioctls? The kernel can do those serial protocols at upper
levels.

	Regards
		Oliver
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" 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] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial adapters.
@ 2018-04-05  6:26 Greg Kroah-Hartman
  0 siblings, 0 replies; 15+ messages in thread
From: Greg Kroah-Hartman @ 2018-04-05  6:26 UTC (permalink / raw)
  To: Patong Yang; +Cc: johan, pyang, linux-usb

On Wed, Apr 04, 2018 at 12:03:51PM -0700, Patong Yang wrote:
> Thanks for the quick reply and feedback.  This needs to be a different
> driver because although the USB UARTs can work with the CDC-ACM driver,
> there are limitations in the CDC-ACM spec and therefore the driver that
> prevents it from fully functioning as a standard serial port (ie. status of
> CTS pin, ability to enable/disable flow control).  Therefore, the USB UARTs
> also support custom USB Vendor requests so that it can behave as a standard
> serial port and support other device features such as GPIO control and the
> automatic direction control feature typically used in RS-485 and RS-422
> designs.

The GPIO stuff needs to be a separate interface, using the kernel's GPIO
api, not custom ioctls.

> The USB device can describe itself properly.  The SMBIOS function is a
> requirement from our customer who designed a board using our device where
> their CPU reads from a specific BIOS location and initializes GPIOs based
> on the settings.  These GPIOs set the mode of the transceiver (LOOPBACK,
> RS232, RS485, or RS422).  Therefore, the driver also reads the same
> settings from the BIOS, so that it can enable the appropriate mode.

That logic can be done in userspace, no need to do that within the
kernel, right?

> This same customer is designing a new board to support the same transceiver
> configurations.  Instead of using the BIOS settings, they would like to use
> a user-space application to configure the additional GPIOs in a newer USB
> UART to set the transceiver modes, however, there are no standard APIs to
> support setting different modes, hence the custom IOCTLs.

What is wrong with the current GPIO Linux api?  Doesn't that support
everything you need here?

thanks,

greg k-h
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" 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] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial adapters.
@ 2018-04-04  8:00 Greg Kroah-Hartman
  0 siblings, 0 replies; 15+ messages in thread
From: Greg Kroah-Hartman @ 2018-04-04  8:00 UTC (permalink / raw)
  To: Patong Yang; +Cc: johan, pyang, linux-usb

On Wed, Apr 04, 2018 at 12:06:34AM -0700, Patong Yang wrote:
> - When specific IOCTLs are called by a user-space application, a 
>   port_config file is created for the /dev/ttyXRUSB device at a
>   specific USB tree location, and some configuration data is stored. 
>   The driver checks for the port_config file when the driver is loaded
>   for each port and loads the configuration settings if there is a
>   port_config file for the USB tree location.

Custom IOCTLS on a USB serial driver are not ok, sorry.  Please use the
standard ioctls and userspace apis for setting up and handling
configuration of your device.

thanks,

greg k-h
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" 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] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial adapters.
@ 2018-04-04  7:59 Greg Kroah-Hartman
  0 siblings, 0 replies; 15+ messages in thread
From: Greg Kroah-Hartman @ 2018-04-04  7:59 UTC (permalink / raw)
  To: Patong Yang; +Cc: johan, pyang, linux-usb

On Wed, Apr 04, 2018 at 12:06:34AM -0700, Patong Yang wrote:
> The driver is based on the CDC-ACM driver. In addition to supporting 
> the features of the MaxLinear/Exar USB UART devices, the driver also 
> has support for 2 other functions per customer requirements:
> 
> - Specific entries are checked in the BIOS to detect if the board is a
>   "Caracalla" board before enabling specific modes in the MaxLinear/Exar
>   USB UARTs.  The smbios code is based on the example at:
>   https://sourceforge.net/projects/smbios/
> 
> - When specific IOCTLs are called by a user-space application, a 
>   port_config file is created for the /dev/ttyXRUSB device at a
>   specific USB tree location, and some configuration data is stored. 
>   The driver checks for the port_config file when the driver is loaded
>   for each port and loads the configuration settings if there is a
>   port_config file for the USB tree location.
> 
> Signed-off-by: Patong Yang <patong.mxl@gmail.com>
> ---
>  drivers/usb/serial/xrusb_serial.c | 3285 +++++++++++++++++++++++++++++++++++++
>  drivers/usb/serial/xrusb_serial.h |  448 +++++
>  2 files changed, 3733 insertions(+)
>  create mode 100644 drivers/usb/serial/xrusb_serial.c
>  create mode 100644 drivers/usb/serial/xrusb_serial.h
> 
> diff --git a/drivers/usb/serial/xrusb_serial.c b/drivers/usb/serial/xrusb_serial.c
> new file mode 100644
> index 000000000000..16a5bcff9103
> --- /dev/null
> +++ b/drivers/usb/serial/xrusb_serial.c
> @@ -0,0 +1,3285 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * xrusb_serial.c
> + *
> + * Copyright (c) 2018 Patong Yang <patong.mxl@gmail.com>
> + *
> + * USB Serial Driver based on the cdc-acm.c driver for the
> + * MaxLinear/Exar USB UARTs/Serial adapters
> + */
> +
> +
> +#undef DEBUG
> +#undef VERBOSE_DEBUG

No need for these #undef in the driver.

And as Oliver points out, why does this have to be a totally different
driver?  And putting SMBIOS calls in a USB driver is very strange, can't
the USB devices describe themselves properly?

thanks,

greg k-h
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" 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] 15+ messages in thread

* Driver for MaxLinear/Exar USB (UART) Serial adapters.
@ 2018-04-04  7:38 Oliver Neukum
  0 siblings, 0 replies; 15+ messages in thread
From: Oliver Neukum @ 2018-04-04  7:38 UTC (permalink / raw)
  To: Patong Yang, johan; +Cc: gregkh, pyang, linux-usb

Am Mittwoch, den 04.04.2018, 00:06 -0700 schrieb Patong Yang:
> The driver is based on the CDC-ACM driver. In addition to supporting 
> the features of the MaxLinear/Exar USB UART devices, the driver also 

Hi,

it is rather drastic a measure to duplicate a driver just for a low
number of devices. It makes fixing bugs double the work. At a minimum,
before we look at the code itself, could you please explain why

a) the code cannot be added to cdc-acm?
b) the check for SMBIOS is needed?

	Regards
		Oliver
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" 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] 15+ messages in thread

end of thread, other threads:[~2018-08-16 10:05 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-04-04  7:06 Driver for MaxLinear/Exar USB (UART) Serial adapters Patong Yang
2018-04-04  7:38 Oliver Neukum
2018-04-04  7:59 Greg Kroah-Hartman
2018-04-04  8:00 Greg Kroah-Hartman
2018-04-05  6:26 Greg Kroah-Hartman
2018-04-05  7:38 Oliver Neukum
2018-04-06 14:45 Greg Kroah-Hartman
2018-07-24 22:36 Driver for MaxLinear/Exar USB (UART) Serial Adapters Patong Yang
2018-07-25  7:38 Oliver Neukum
2018-07-26 10:57 Greg Kroah-Hartman
2018-08-16  5:56 Patong Yang
2018-08-16  6:34 Greg Kroah-Hartman
2018-08-16  8:26 Oliver Neukum
2018-08-16  8:28 Patong Yang
2018-08-16 10:05 Greg Kroah-Hartman

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).