All of lore.kernel.org
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.