linux-usb.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Patong Yang <patong.mxl@gmail.com>
To: linux-usb@vger.kernel.org
Cc: johan@kernel.org, pyang@maxlinear.com, gregkh@linuxfoundation.org
Subject: Driver for MaxLinear/Exar USB (UART) Serial Adapters
Date: Tue, 24 Jul 2018 15:36:36 -0700	[thread overview]
Message-ID: <20180724223636.GA17270@linux1804-desktop> (raw)

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

             reply	other threads:[~2018-07-24 22:36 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-07-24 22:36 Patong Yang [this message]
  -- strict thread matches above, loose matches on Subject: below --
2018-08-16 10:05 Driver for MaxLinear/Exar USB (UART) Serial Adapters Greg Kroah-Hartman
2018-08-16  8:28 Patong Yang
2018-08-16  8:26 Oliver Neukum
2018-08-16  6:34 Greg Kroah-Hartman
2018-08-16  5:56 Patong Yang
2018-07-26 10:57 Greg Kroah-Hartman
2018-07-25  7:38 Oliver Neukum
2018-04-06 14:45 Driver for MaxLinear/Exar USB (UART) Serial adapters Greg Kroah-Hartman
2018-04-05  7:38 Oliver Neukum
2018-04-05  6:26 Greg Kroah-Hartman
2018-04-04  8:00 Greg Kroah-Hartman
2018-04-04  7:59 Greg Kroah-Hartman
2018-04-04  7:38 Oliver Neukum
2018-04-04  7:06 Patong Yang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180724223636.GA17270@linux1804-desktop \
    --to=patong.mxl@gmail.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=johan@kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=pyang@maxlinear.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).