diff mbox series

Driver for MaxLinear/Exar USB (UART) Serial Adapters

Message ID 20180724223636.GA17270@linux1804-desktop (mailing list archive)
State New, archived
Headers show
Series Driver for MaxLinear/Exar USB (UART) Serial Adapters | expand

Commit Message

Patong Yang July 24, 2018, 10:36 p.m. UTC
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

Comments

Oliver Neukum July 25, 2018, 7:38 a.m. UTC | #1
On Di, 2018-07-24 at 15:36 -0700, Patong Yang wrote:
> +static int xrusb_ctrl_msg(struct xrusb *xrusb,
> +               int request, int value, void *buf, int len)
> +{
> +       int rv = usb_control_msg(xrusb->dev,
> +               usb_sndctrlpipe(xrusb->dev, 0),
> +               request,
> +               USB_RT_XRUSB,
> +               value,
> +               xrusb->control->altsetting[0].desc.bInterfaceNumber,
> +               buf,
> +               len,
> +               5000);

Please use the symbolic constant.

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

	Regards
		Oliver

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Greg Kroah-Hartman July 26, 2018, 10:57 a.m. UTC | #2
On Tue, Jul 24, 2018 at 03:36:36PM -0700, Patong Yang wrote:
> The original driver/patch was submitted on April 4, 2018.  This is the
> second version based on the feedback received on the original patch.
> 
> v2: Removed custom IOCTLs, as suggested by Greg KH
>     Using standard Linux GPIO APIs, as suggested by Greg KH
>     Removed file reads/writes as suggested by Greg KH
> 
> Signed-off-by: Patong Yang <patong.mxl@gmail.com>
> ---
>  drivers/usb/serial/xrusb_serial.c | 2380 +++++++++++++++++++++++++++++
>  drivers/usb/serial/xrusb_serial.h |  234 +++

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

But there is a bigger problem here:

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

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

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

thanks,

greg k-h
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Patong Yang Aug. 16, 2018, 5:56 a.m. UTC | #3
Greg,

Please see my response inline below.

Patong

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

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

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

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

thanks,

greg k-h
Oliver Neukum Aug. 16, 2018, 8:26 a.m. UTC | #5
On Do, 2018-08-16 at 01:28 -0700, Patong Yang wrote:
> Features not supported by the cdc-acm driver are 
> enabling/disabling flow control, enabling/disabling RS-485 mode, 
> GPIOs and GPIO modes, etc. Support for these features had to be added in this
> new driver.

Hi,

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

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

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

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

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

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

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

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

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

thanks,

greg k-h
diff mbox series

Patch

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