v4.19.13 snapshot.
diff --git a/drivers/usb/atm/Kconfig b/drivers/usb/atm/Kconfig
new file mode 100644
index 0000000..0f92294
--- /dev/null
+++ b/drivers/usb/atm/Kconfig
@@ -0,0 +1,68 @@
+#
+# USB/ATM DSL configuration
+#
+
+menuconfig USB_ATM
+	tristate "USB DSL modem support"
+	depends on ATM
+	select CRC32
+	default n
+	help
+	  Say Y here if you want to connect a USB Digital Subscriber Line (DSL)
+	  modem to your computer's USB port.  You will then need to choose your
+	  modem from the list below.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called usbatm.
+
+if USB_ATM
+
+config USB_SPEEDTOUCH
+	tristate "Speedtouch USB support"
+	select FW_LOADER
+	help
+	  Say Y here if you have an SpeedTouch USB or SpeedTouch 330
+	  modem.  In order to use your modem you will need to install the 
+	  two parts of the firmware, extracted by the user space tools; see
+	  <http://www.linux-usb.org/SpeedTouch/> for details.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called speedtch.
+
+config USB_CXACRU
+	tristate "Conexant AccessRunner USB support"
+	select FW_LOADER
+	help
+	  Say Y here if you have an ADSL USB modem based on the Conexant
+	  AccessRunner chipset.  In order to use your modem you will need to
+	  install the firmware, extracted by the user space tools; see
+	  <http://accessrunner.sourceforge.net/> for details.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called cxacru.
+
+config USB_UEAGLEATM
+	tristate "ADI 930 and eagle USB DSL modem"
+	select FW_LOADER
+	help
+	  Say Y here if you have an ADSL USB modem based on the ADI 930
+	  or eagle chipset. In order to use your modem you will need to
+	  install firmwares and CMV (Command Management Variables); see
+	  <https://gna.org/projects/ueagleatm/> for details.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ueagle-atm.
+
+config USB_XUSBATM
+	tristate "Other USB DSL modem support"
+	help
+	  Say Y here if you have a DSL USB modem not explicitly supported by
+	  another USB DSL drivers.  In order to use your modem you will need to
+	  pass the vendor ID, product ID, and endpoint numbers for transmission
+	  and reception as module parameters.  You may need to initialize
+	  the modem using a user space utility (a firmware loader for example).
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called xusbatm.
+
+endif # USB_ATM
diff --git a/drivers/usb/atm/Makefile b/drivers/usb/atm/Makefile
new file mode 100644
index 0000000..7ac65ce
--- /dev/null
+++ b/drivers/usb/atm/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for USB ATM/xDSL drivers
+#
+obj-$(CONFIG_USB_CXACRU)	+= cxacru.o
+obj-$(CONFIG_USB_SPEEDTOUCH)	+= speedtch.o
+obj-$(CONFIG_USB_UEAGLEATM)	+= ueagle-atm.o
+obj-$(CONFIG_USB_ATM)		+= usbatm.o
+obj-$(CONFIG_USB_XUSBATM)	+= xusbatm.o
diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c
new file mode 100644
index 0000000..e57a2be
--- /dev/null
+++ b/drivers/usb/atm/cxacru.c
@@ -0,0 +1,1374 @@
+// SPDX-License-Identifier: GPL-2.0+
+/******************************************************************************
+ *  cxacru.c  -  driver for USB ADSL modems based on
+ *               Conexant AccessRunner chipset
+ *
+ *  Copyright (C) 2004 David Woodhouse, Duncan Sands, Roman Kagan
+ *  Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru)
+ *  Copyright (C) 2007 Simon Arlott
+ *  Copyright (C) 2009 Simon Arlott
+ ******************************************************************************/
+
+/*
+ *  Credit is due for Josep Comas, who created the original patch to speedtch.c
+ *  to support the different padding used by the AccessRunner (now generalized
+ *  into usbatm), and the userspace firmware loading utility.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/mutex.h>
+#include <asm/unaligned.h>
+
+#include "usbatm.h"
+
+#define DRIVER_AUTHOR	"Roman Kagan, David Woodhouse, Duncan Sands, Simon Arlott"
+#define DRIVER_DESC	"Conexant AccessRunner ADSL USB modem driver"
+
+static const char cxacru_driver_name[] = "cxacru";
+
+#define CXACRU_EP_CMD		0x01	/* Bulk/interrupt in/out */
+#define CXACRU_EP_DATA		0x02	/* Bulk in/out */
+
+#define CMD_PACKET_SIZE		64	/* Should be maxpacket(ep)? */
+#define CMD_MAX_CONFIG		((CMD_PACKET_SIZE / 4 - 1) / 2)
+
+/* Addresses */
+#define PLLFCLK_ADDR	0x00350068
+#define PLLBCLK_ADDR	0x0035006c
+#define SDRAMEN_ADDR	0x00350010
+#define FW_ADDR		0x00801000
+#define BR_ADDR		0x00180600
+#define SIG_ADDR	0x00180500
+#define BR_STACK_ADDR	0x00187f10
+
+/* Values */
+#define SDRAM_ENA	0x1
+
+#define CMD_TIMEOUT	2000	/* msecs */
+#define POLL_INTERVAL	1	/* secs */
+
+/* commands for interaction with the modem through the control channel before
+ * firmware is loaded  */
+enum cxacru_fw_request {
+	FW_CMD_ERR,
+	FW_GET_VER,
+	FW_READ_MEM,
+	FW_WRITE_MEM,
+	FW_RMW_MEM,
+	FW_CHECKSUM_MEM,
+	FW_GOTO_MEM,
+};
+
+/* commands for interaction with the modem through the control channel once
+ * firmware is loaded  */
+enum cxacru_cm_request {
+	CM_REQUEST_UNDEFINED = 0x80,
+	CM_REQUEST_TEST,
+	CM_REQUEST_CHIP_GET_MAC_ADDRESS,
+	CM_REQUEST_CHIP_GET_DP_VERSIONS,
+	CM_REQUEST_CHIP_ADSL_LINE_START,
+	CM_REQUEST_CHIP_ADSL_LINE_STOP,
+	CM_REQUEST_CHIP_ADSL_LINE_GET_STATUS,
+	CM_REQUEST_CHIP_ADSL_LINE_GET_SPEED,
+	CM_REQUEST_CARD_INFO_GET,
+	CM_REQUEST_CARD_DATA_GET,
+	CM_REQUEST_CARD_DATA_SET,
+	CM_REQUEST_COMMAND_HW_IO,
+	CM_REQUEST_INTERFACE_HW_IO,
+	CM_REQUEST_CARD_SERIAL_DATA_PATH_GET,
+	CM_REQUEST_CARD_SERIAL_DATA_PATH_SET,
+	CM_REQUEST_CARD_CONTROLLER_VERSION_GET,
+	CM_REQUEST_CARD_GET_STATUS,
+	CM_REQUEST_CARD_GET_MAC_ADDRESS,
+	CM_REQUEST_CARD_GET_DATA_LINK_STATUS,
+	CM_REQUEST_MAX,
+};
+
+/* commands for interaction with the flash memory
+ *
+ * read:  response is the contents of the first 60 bytes of flash memory
+ * write: request contains the 60 bytes of data to write to flash memory
+ *        response is the contents of the first 60 bytes of flash memory
+ *
+ * layout: PP PP VV VV  MM MM MM MM  MM MM ?? ??  SS SS SS SS  SS SS SS SS
+ *         SS SS SS SS  SS SS SS SS  00 00 00 00  00 00 00 00  00 00 00 00
+ *         00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00
+ *
+ *   P: le16  USB Product ID
+ *   V: le16  USB Vendor ID
+ *   M: be48  MAC Address
+ *   S: le16  ASCII Serial Number
+ */
+enum cxacru_cm_flash {
+	CM_FLASH_READ = 0xa1,
+	CM_FLASH_WRITE = 0xa2
+};
+
+/* reply codes to the commands above */
+enum cxacru_cm_status {
+	CM_STATUS_UNDEFINED,
+	CM_STATUS_SUCCESS,
+	CM_STATUS_ERROR,
+	CM_STATUS_UNSUPPORTED,
+	CM_STATUS_UNIMPLEMENTED,
+	CM_STATUS_PARAMETER_ERROR,
+	CM_STATUS_DBG_LOOPBACK,
+	CM_STATUS_MAX,
+};
+
+/* indices into CARD_INFO_GET return array */
+enum cxacru_info_idx {
+	CXINF_DOWNSTREAM_RATE,
+	CXINF_UPSTREAM_RATE,
+	CXINF_LINK_STATUS,
+	CXINF_LINE_STATUS,
+	CXINF_MAC_ADDRESS_HIGH,
+	CXINF_MAC_ADDRESS_LOW,
+	CXINF_UPSTREAM_SNR_MARGIN,
+	CXINF_DOWNSTREAM_SNR_MARGIN,
+	CXINF_UPSTREAM_ATTENUATION,
+	CXINF_DOWNSTREAM_ATTENUATION,
+	CXINF_TRANSMITTER_POWER,
+	CXINF_UPSTREAM_BITS_PER_FRAME,
+	CXINF_DOWNSTREAM_BITS_PER_FRAME,
+	CXINF_STARTUP_ATTEMPTS,
+	CXINF_UPSTREAM_CRC_ERRORS,
+	CXINF_DOWNSTREAM_CRC_ERRORS,
+	CXINF_UPSTREAM_FEC_ERRORS,
+	CXINF_DOWNSTREAM_FEC_ERRORS,
+	CXINF_UPSTREAM_HEC_ERRORS,
+	CXINF_DOWNSTREAM_HEC_ERRORS,
+	CXINF_LINE_STARTABLE,
+	CXINF_MODULATION,
+	CXINF_ADSL_HEADEND,
+	CXINF_ADSL_HEADEND_ENVIRONMENT,
+	CXINF_CONTROLLER_VERSION,
+	/* dunno what the missing two mean */
+	CXINF_MAX = 0x1c,
+};
+
+enum cxacru_poll_state {
+	CXPOLL_STOPPING,
+	CXPOLL_STOPPED,
+	CXPOLL_POLLING,
+	CXPOLL_SHUTDOWN
+};
+
+struct cxacru_modem_type {
+	u32 pll_f_clk;
+	u32 pll_b_clk;
+	int boot_rom_patch;
+};
+
+struct cxacru_data {
+	struct usbatm_data *usbatm;
+
+	const struct cxacru_modem_type *modem_type;
+
+	int line_status;
+	struct mutex adsl_state_serialize;
+	int adsl_status;
+	struct delayed_work poll_work;
+	u32 card_info[CXINF_MAX];
+	struct mutex poll_state_serialize;
+	enum cxacru_poll_state poll_state;
+
+	/* contol handles */
+	struct mutex cm_serialize;
+	u8 *rcv_buf;
+	u8 *snd_buf;
+	struct urb *rcv_urb;
+	struct urb *snd_urb;
+	struct completion rcv_done;
+	struct completion snd_done;
+};
+
+static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm,
+	u8 *wdata, int wsize, u8 *rdata, int rsize);
+static void cxacru_poll_status(struct work_struct *work);
+
+/* Card info exported through sysfs */
+#define CXACRU__ATTR_INIT(_name) \
+static DEVICE_ATTR_RO(_name)
+
+#define CXACRU_CMD_INIT(_name) \
+static DEVICE_ATTR_RW(_name)
+
+#define CXACRU_SET_INIT(_name) \
+static DEVICE_ATTR_WO(_name)
+
+#define CXACRU_ATTR_INIT(_value, _type, _name) \
+static ssize_t _name##_show(struct device *dev, \
+	struct device_attribute *attr, char *buf) \
+{ \
+	struct cxacru_data *instance = to_usbatm_driver_data(\
+		to_usb_interface(dev)); \
+\
+	if (instance == NULL) \
+		return -ENODEV; \
+\
+	return cxacru_sysfs_showattr_##_type(instance->card_info[_value], buf); \
+} \
+CXACRU__ATTR_INIT(_name)
+
+#define CXACRU_ATTR_CREATE(_v, _t, _name) CXACRU_DEVICE_CREATE_FILE(_name)
+#define CXACRU_CMD_CREATE(_name)          CXACRU_DEVICE_CREATE_FILE(_name)
+#define CXACRU_SET_CREATE(_name)          CXACRU_DEVICE_CREATE_FILE(_name)
+#define CXACRU__ATTR_CREATE(_name)        CXACRU_DEVICE_CREATE_FILE(_name)
+
+#define CXACRU_ATTR_REMOVE(_v, _t, _name) CXACRU_DEVICE_REMOVE_FILE(_name)
+#define CXACRU_CMD_REMOVE(_name)          CXACRU_DEVICE_REMOVE_FILE(_name)
+#define CXACRU_SET_REMOVE(_name)          CXACRU_DEVICE_REMOVE_FILE(_name)
+#define CXACRU__ATTR_REMOVE(_name)        CXACRU_DEVICE_REMOVE_FILE(_name)
+
+static ssize_t cxacru_sysfs_showattr_u32(u32 value, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%u\n", value);
+}
+
+static ssize_t cxacru_sysfs_showattr_s8(s8 value, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%d\n", value);
+}
+
+static ssize_t cxacru_sysfs_showattr_dB(s16 value, char *buf)
+{
+	if (likely(value >= 0)) {
+		return snprintf(buf, PAGE_SIZE, "%u.%02u\n",
+					value / 100, value % 100);
+	} else {
+		value = -value;
+		return snprintf(buf, PAGE_SIZE, "-%u.%02u\n",
+					value / 100, value % 100);
+	}
+}
+
+static ssize_t cxacru_sysfs_showattr_bool(u32 value, char *buf)
+{
+	static char *str[] = { "no", "yes" };
+
+	if (unlikely(value >= ARRAY_SIZE(str)))
+		return snprintf(buf, PAGE_SIZE, "%u\n", value);
+	return snprintf(buf, PAGE_SIZE, "%s\n", str[value]);
+}
+
+static ssize_t cxacru_sysfs_showattr_LINK(u32 value, char *buf)
+{
+	static char *str[] = { NULL, "not connected", "connected", "lost" };
+
+	if (unlikely(value >= ARRAY_SIZE(str) || str[value] == NULL))
+		return snprintf(buf, PAGE_SIZE, "%u\n", value);
+	return snprintf(buf, PAGE_SIZE, "%s\n", str[value]);
+}
+
+static ssize_t cxacru_sysfs_showattr_LINE(u32 value, char *buf)
+{
+	static char *str[] = { "down", "attempting to activate",
+		"training", "channel analysis", "exchange", "up",
+		"waiting", "initialising"
+	};
+	if (unlikely(value >= ARRAY_SIZE(str)))
+		return snprintf(buf, PAGE_SIZE, "%u\n", value);
+	return snprintf(buf, PAGE_SIZE, "%s\n", str[value]);
+}
+
+static ssize_t cxacru_sysfs_showattr_MODU(u32 value, char *buf)
+{
+	static char *str[] = {
+			"",
+			"ANSI T1.413",
+			"ITU-T G.992.1 (G.DMT)",
+			"ITU-T G.992.2 (G.LITE)"
+	};
+	if (unlikely(value >= ARRAY_SIZE(str)))
+		return snprintf(buf, PAGE_SIZE, "%u\n", value);
+	return snprintf(buf, PAGE_SIZE, "%s\n", str[value]);
+}
+
+/*
+ * This could use MAC_ADDRESS_HIGH and MAC_ADDRESS_LOW, but since
+ * this data is already in atm_dev there's no point.
+ *
+ * MAC_ADDRESS_HIGH = 0x????5544
+ * MAC_ADDRESS_LOW  = 0x33221100
+ * Where 00-55 are bytes 0-5 of the MAC.
+ */
+static ssize_t mac_address_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct cxacru_data *instance = to_usbatm_driver_data(
+			to_usb_interface(dev));
+
+	if (instance == NULL || instance->usbatm->atm_dev == NULL)
+		return -ENODEV;
+
+	return snprintf(buf, PAGE_SIZE, "%pM\n",
+		instance->usbatm->atm_dev->esi);
+}
+
+static ssize_t adsl_state_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	static char *str[] = { "running", "stopped" };
+	struct cxacru_data *instance = to_usbatm_driver_data(
+			to_usb_interface(dev));
+	u32 value;
+
+	if (instance == NULL)
+		return -ENODEV;
+
+	value = instance->card_info[CXINF_LINE_STARTABLE];
+	if (unlikely(value >= ARRAY_SIZE(str)))
+		return snprintf(buf, PAGE_SIZE, "%u\n", value);
+	return snprintf(buf, PAGE_SIZE, "%s\n", str[value]);
+}
+
+static ssize_t adsl_state_store(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct cxacru_data *instance = to_usbatm_driver_data(
+			to_usb_interface(dev));
+	int ret;
+	int poll = -1;
+	char str_cmd[8];
+	int len = strlen(buf);
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EACCES;
+
+	ret = sscanf(buf, "%7s", str_cmd);
+	if (ret != 1)
+		return -EINVAL;
+	ret = 0;
+
+	if (instance == NULL)
+		return -ENODEV;
+
+	if (mutex_lock_interruptible(&instance->adsl_state_serialize))
+		return -ERESTARTSYS;
+
+	if (!strcmp(str_cmd, "stop") || !strcmp(str_cmd, "restart")) {
+		ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_STOP, NULL, 0, NULL, 0);
+		if (ret < 0) {
+			atm_err(instance->usbatm, "change adsl state:"
+				" CHIP_ADSL_LINE_STOP returned %d\n", ret);
+
+			ret = -EIO;
+		} else {
+			ret = len;
+			poll = CXPOLL_STOPPED;
+		}
+	}
+
+	/* Line status is only updated every second
+	 * and the device appears to only react to
+	 * START/STOP every second too. Wait 1.5s to
+	 * be sure that restart will have an effect. */
+	if (!strcmp(str_cmd, "restart"))
+		msleep(1500);
+
+	if (!strcmp(str_cmd, "start") || !strcmp(str_cmd, "restart")) {
+		ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0);
+		if (ret < 0) {
+			atm_err(instance->usbatm, "change adsl state:"
+				" CHIP_ADSL_LINE_START returned %d\n", ret);
+
+			ret = -EIO;
+		} else {
+			ret = len;
+			poll = CXPOLL_POLLING;
+		}
+	}
+
+	if (!strcmp(str_cmd, "poll")) {
+		ret = len;
+		poll = CXPOLL_POLLING;
+	}
+
+	if (ret == 0) {
+		ret = -EINVAL;
+		poll = -1;
+	}
+
+	if (poll == CXPOLL_POLLING) {
+		mutex_lock(&instance->poll_state_serialize);
+		switch (instance->poll_state) {
+		case CXPOLL_STOPPED:
+			/* start polling */
+			instance->poll_state = CXPOLL_POLLING;
+			break;
+
+		case CXPOLL_STOPPING:
+			/* abort stop request */
+			instance->poll_state = CXPOLL_POLLING;
+			/* fall through */
+		case CXPOLL_POLLING:
+		case CXPOLL_SHUTDOWN:
+			/* don't start polling */
+			poll = -1;
+		}
+		mutex_unlock(&instance->poll_state_serialize);
+	} else if (poll == CXPOLL_STOPPED) {
+		mutex_lock(&instance->poll_state_serialize);
+		/* request stop */
+		if (instance->poll_state == CXPOLL_POLLING)
+			instance->poll_state = CXPOLL_STOPPING;
+		mutex_unlock(&instance->poll_state_serialize);
+	}
+
+	mutex_unlock(&instance->adsl_state_serialize);
+
+	if (poll == CXPOLL_POLLING)
+		cxacru_poll_status(&instance->poll_work.work);
+
+	return ret;
+}
+
+/* CM_REQUEST_CARD_DATA_GET times out, so no show attribute */
+
+static ssize_t adsl_config_store(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct cxacru_data *instance = to_usbatm_driver_data(
+			to_usb_interface(dev));
+	int len = strlen(buf);
+	int ret, pos, num;
+	__le32 data[CMD_PACKET_SIZE / 4];
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EACCES;
+
+	if (instance == NULL)
+		return -ENODEV;
+
+	pos = 0;
+	num = 0;
+	while (pos < len) {
+		int tmp;
+		u32 index;
+		u32 value;
+
+		ret = sscanf(buf + pos, "%x=%x%n", &index, &value, &tmp);
+		if (ret < 2)
+			return -EINVAL;
+		if (index > 0x7f)
+			return -EINVAL;
+		if (tmp < 0 || tmp > len - pos)
+			return -EINVAL;
+		pos += tmp;
+
+		/* skip trailing newline */
+		if (buf[pos] == '\n' && pos == len-1)
+			pos++;
+
+		data[num * 2 + 1] = cpu_to_le32(index);
+		data[num * 2 + 2] = cpu_to_le32(value);
+		num++;
+
+		/* send config values when data buffer is full
+		 * or no more data
+		 */
+		if (pos >= len || num >= CMD_MAX_CONFIG) {
+			char log[CMD_MAX_CONFIG * 12 + 1]; /* %02x=%08x */
+
+			data[0] = cpu_to_le32(num);
+			ret = cxacru_cm(instance, CM_REQUEST_CARD_DATA_SET,
+				(u8 *) data, 4 + num * 8, NULL, 0);
+			if (ret < 0) {
+				atm_err(instance->usbatm,
+					"set card data returned %d\n", ret);
+				return -EIO;
+			}
+
+			for (tmp = 0; tmp < num; tmp++)
+				snprintf(log + tmp*12, 13, " %02x=%08x",
+					le32_to_cpu(data[tmp * 2 + 1]),
+					le32_to_cpu(data[tmp * 2 + 2]));
+			atm_info(instance->usbatm, "config%s\n", log);
+			num = 0;
+		}
+	}
+
+	return len;
+}
+
+/*
+ * All device attributes are included in CXACRU_ALL_FILES
+ * so that the same list can be used multiple times:
+ *     INIT   (define the device attributes)
+ *     CREATE (create all the device files)
+ *     REMOVE (remove all the device files)
+ *
+ * With the last two being defined as needed in the functions
+ * they are used in before calling CXACRU_ALL_FILES()
+ */
+#define CXACRU_ALL_FILES(_action) \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_RATE,           u32,  downstream_rate); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_RATE,             u32,  upstream_rate); \
+CXACRU_ATTR_##_action(CXINF_LINK_STATUS,               LINK, link_status); \
+CXACRU_ATTR_##_action(CXINF_LINE_STATUS,               LINE, line_status); \
+CXACRU__ATTR_##_action(                                      mac_address); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_SNR_MARGIN,       dB,   upstream_snr_margin); \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_SNR_MARGIN,     dB,   downstream_snr_margin); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_ATTENUATION,      dB,   upstream_attenuation); \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_ATTENUATION,    dB,   downstream_attenuation); \
+CXACRU_ATTR_##_action(CXINF_TRANSMITTER_POWER,         s8,   transmitter_power); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_BITS_PER_FRAME,   u32,  upstream_bits_per_frame); \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_BITS_PER_FRAME, u32,  downstream_bits_per_frame); \
+CXACRU_ATTR_##_action(CXINF_STARTUP_ATTEMPTS,          u32,  startup_attempts); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_CRC_ERRORS,       u32,  upstream_crc_errors); \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_CRC_ERRORS,     u32,  downstream_crc_errors); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_FEC_ERRORS,       u32,  upstream_fec_errors); \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_FEC_ERRORS,     u32,  downstream_fec_errors); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_HEC_ERRORS,       u32,  upstream_hec_errors); \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_HEC_ERRORS,     u32,  downstream_hec_errors); \
+CXACRU_ATTR_##_action(CXINF_LINE_STARTABLE,            bool, line_startable); \
+CXACRU_ATTR_##_action(CXINF_MODULATION,                MODU, modulation); \
+CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND,              u32,  adsl_headend); \
+CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND_ENVIRONMENT,  u32,  adsl_headend_environment); \
+CXACRU_ATTR_##_action(CXINF_CONTROLLER_VERSION,        u32,  adsl_controller_version); \
+CXACRU_CMD_##_action(                                        adsl_state); \
+CXACRU_SET_##_action(                                        adsl_config);
+
+CXACRU_ALL_FILES(INIT);
+
+/* the following three functions are stolen from drivers/usb/core/message.c */
+static void cxacru_blocking_completion(struct urb *urb)
+{
+	complete(urb->context);
+}
+
+struct cxacru_timer {
+	struct timer_list timer;
+	struct urb *urb;
+};
+
+static void cxacru_timeout_kill(struct timer_list *t)
+{
+	struct cxacru_timer *timer = from_timer(timer, t, timer);
+
+	usb_unlink_urb(timer->urb);
+}
+
+static int cxacru_start_wait_urb(struct urb *urb, struct completion *done,
+				 int *actual_length)
+{
+	struct cxacru_timer timer = {
+		.urb = urb,
+	};
+
+	timer_setup_on_stack(&timer.timer, cxacru_timeout_kill, 0);
+	mod_timer(&timer.timer, jiffies + msecs_to_jiffies(CMD_TIMEOUT));
+	wait_for_completion(done);
+	del_timer_sync(&timer.timer);
+	destroy_timer_on_stack(&timer.timer);
+
+	if (actual_length)
+		*actual_length = urb->actual_length;
+	return urb->status; /* must read status after completion */
+}
+
+static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm,
+		     u8 *wdata, int wsize, u8 *rdata, int rsize)
+{
+	int ret, actlen;
+	int offb, offd;
+	const int stride = CMD_PACKET_SIZE - 4;
+	u8 *wbuf = instance->snd_buf;
+	u8 *rbuf = instance->rcv_buf;
+	int wbuflen = ((wsize - 1) / stride + 1) * CMD_PACKET_SIZE;
+	int rbuflen = ((rsize - 1) / stride + 1) * CMD_PACKET_SIZE;
+
+	if (wbuflen > PAGE_SIZE || rbuflen > PAGE_SIZE) {
+		if (printk_ratelimit())
+			usb_err(instance->usbatm, "requested transfer size too large (%d, %d)\n",
+				wbuflen, rbuflen);
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	mutex_lock(&instance->cm_serialize);
+
+	/* submit reading urb before the writing one */
+	init_completion(&instance->rcv_done);
+	ret = usb_submit_urb(instance->rcv_urb, GFP_KERNEL);
+	if (ret < 0) {
+		if (printk_ratelimit())
+			usb_err(instance->usbatm, "submit of read urb for cm %#x failed (%d)\n",
+				cm, ret);
+		goto fail;
+	}
+
+	memset(wbuf, 0, wbuflen);
+	/* handle wsize == 0 */
+	wbuf[0] = cm;
+	for (offb = offd = 0; offd < wsize; offd += stride, offb += CMD_PACKET_SIZE) {
+		wbuf[offb] = cm;
+		memcpy(wbuf + offb + 4, wdata + offd, min_t(int, stride, wsize - offd));
+	}
+
+	instance->snd_urb->transfer_buffer_length = wbuflen;
+	init_completion(&instance->snd_done);
+	ret = usb_submit_urb(instance->snd_urb, GFP_KERNEL);
+	if (ret < 0) {
+		if (printk_ratelimit())
+			usb_err(instance->usbatm, "submit of write urb for cm %#x failed (%d)\n",
+				cm, ret);
+		goto fail;
+	}
+
+	ret = cxacru_start_wait_urb(instance->snd_urb, &instance->snd_done, NULL);
+	if (ret < 0) {
+		if (printk_ratelimit())
+			usb_err(instance->usbatm, "send of cm %#x failed (%d)\n", cm, ret);
+		goto fail;
+	}
+
+	ret = cxacru_start_wait_urb(instance->rcv_urb, &instance->rcv_done, &actlen);
+	if (ret < 0) {
+		if (printk_ratelimit())
+			usb_err(instance->usbatm, "receive of cm %#x failed (%d)\n", cm, ret);
+		goto fail;
+	}
+	if (actlen % CMD_PACKET_SIZE || !actlen) {
+		if (printk_ratelimit())
+			usb_err(instance->usbatm, "invalid response length to cm %#x: %d\n",
+				cm, actlen);
+		ret = -EIO;
+		goto fail;
+	}
+
+	/* check the return status and copy the data to the output buffer, if needed */
+	for (offb = offd = 0; offd < rsize && offb < actlen; offb += CMD_PACKET_SIZE) {
+		if (rbuf[offb] != cm) {
+			if (printk_ratelimit())
+				usb_err(instance->usbatm, "wrong cm %#x in response to cm %#x\n",
+					rbuf[offb], cm);
+			ret = -EIO;
+			goto fail;
+		}
+		if (rbuf[offb + 1] != CM_STATUS_SUCCESS) {
+			if (printk_ratelimit())
+				usb_err(instance->usbatm, "response to cm %#x failed: %#x\n",
+					cm, rbuf[offb + 1]);
+			ret = -EIO;
+			goto fail;
+		}
+		if (offd >= rsize)
+			break;
+		memcpy(rdata + offd, rbuf + offb + 4, min_t(int, stride, rsize - offd));
+		offd += stride;
+	}
+
+	ret = offd;
+	usb_dbg(instance->usbatm, "cm %#x\n", cm);
+fail:
+	mutex_unlock(&instance->cm_serialize);
+err:
+	return ret;
+}
+
+static int cxacru_cm_get_array(struct cxacru_data *instance, enum cxacru_cm_request cm,
+			       u32 *data, int size)
+{
+	int ret, len;
+	__le32 *buf;
+	int offb;
+	unsigned int offd;
+	const int stride = CMD_PACKET_SIZE / (4 * 2) - 1;
+	int buflen =  ((size - 1) / stride + 1 + size * 2) * 4;
+
+	buf = kmalloc(buflen, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = cxacru_cm(instance, cm, NULL, 0, (u8 *) buf, buflen);
+	if (ret < 0)
+		goto cleanup;
+
+	/* len > 0 && len % 4 == 0 guaranteed by cxacru_cm() */
+	len = ret / 4;
+	for (offb = 0; offb < len; ) {
+		int l = le32_to_cpu(buf[offb++]);
+
+		if (l < 0 || l > stride || l > (len - offb) / 2) {
+			if (printk_ratelimit())
+				usb_err(instance->usbatm, "invalid data length from cm %#x: %d\n",
+					cm, l);
+			ret = -EIO;
+			goto cleanup;
+		}
+		while (l--) {
+			offd = le32_to_cpu(buf[offb++]);
+			if (offd >= size) {
+				if (printk_ratelimit())
+					usb_err(instance->usbatm, "wrong index %#x in response to cm %#x\n",
+						offd, cm);
+				ret = -EIO;
+				goto cleanup;
+			}
+			data[offd] = le32_to_cpu(buf[offb++]);
+		}
+	}
+
+	ret = 0;
+
+cleanup:
+	kfree(buf);
+	return ret;
+}
+
+static int cxacru_card_status(struct cxacru_data *instance)
+{
+	int ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0);
+
+	if (ret < 0) {		/* firmware not loaded */
+		usb_dbg(instance->usbatm, "cxacru_adsl_start: CARD_GET_STATUS returned %d\n", ret);
+		return ret;
+	}
+	return 0;
+}
+
+static void cxacru_remove_device_files(struct usbatm_data *usbatm_instance,
+		struct atm_dev *atm_dev)
+{
+	struct usb_interface *intf = usbatm_instance->usb_intf;
+
+	#define CXACRU_DEVICE_REMOVE_FILE(_name) \
+		device_remove_file(&intf->dev, &dev_attr_##_name);
+	CXACRU_ALL_FILES(REMOVE);
+	#undef CXACRU_DEVICE_REMOVE_FILE
+}
+
+static int cxacru_atm_start(struct usbatm_data *usbatm_instance,
+		struct atm_dev *atm_dev)
+{
+	struct cxacru_data *instance = usbatm_instance->driver_data;
+	struct usb_interface *intf = usbatm_instance->usb_intf;
+	int ret;
+	int start_polling = 1;
+
+	dev_dbg(&intf->dev, "%s\n", __func__);
+
+	/* Read MAC address */
+	ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_MAC_ADDRESS, NULL, 0,
+			atm_dev->esi, sizeof(atm_dev->esi));
+	if (ret < 0) {
+		atm_err(usbatm_instance, "cxacru_atm_start: CARD_GET_MAC_ADDRESS returned %d\n", ret);
+		return ret;
+	}
+
+	#define CXACRU_DEVICE_CREATE_FILE(_name) \
+		ret = device_create_file(&intf->dev, &dev_attr_##_name); \
+		if (unlikely(ret)) \
+			goto fail_sysfs;
+	CXACRU_ALL_FILES(CREATE);
+	#undef CXACRU_DEVICE_CREATE_FILE
+
+	/* start ADSL */
+	mutex_lock(&instance->adsl_state_serialize);
+	ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0);
+	if (ret < 0)
+		atm_err(usbatm_instance, "cxacru_atm_start: CHIP_ADSL_LINE_START returned %d\n", ret);
+
+	/* Start status polling */
+	mutex_lock(&instance->poll_state_serialize);
+	switch (instance->poll_state) {
+	case CXPOLL_STOPPED:
+		/* start polling */
+		instance->poll_state = CXPOLL_POLLING;
+		break;
+
+	case CXPOLL_STOPPING:
+		/* abort stop request */
+		instance->poll_state = CXPOLL_POLLING;
+		/* fall through */
+	case CXPOLL_POLLING:
+	case CXPOLL_SHUTDOWN:
+		/* don't start polling */
+		start_polling = 0;
+	}
+	mutex_unlock(&instance->poll_state_serialize);
+	mutex_unlock(&instance->adsl_state_serialize);
+
+	printk(KERN_INFO "%s%d: %s %pM\n", atm_dev->type, atm_dev->number,
+			usbatm_instance->description, atm_dev->esi);
+
+	if (start_polling)
+		cxacru_poll_status(&instance->poll_work.work);
+	return 0;
+
+fail_sysfs:
+	usb_err(usbatm_instance, "cxacru_atm_start: device_create_file failed (%d)\n", ret);
+	cxacru_remove_device_files(usbatm_instance, atm_dev);
+	return ret;
+}
+
+static void cxacru_poll_status(struct work_struct *work)
+{
+	struct cxacru_data *instance =
+		container_of(work, struct cxacru_data, poll_work.work);
+	u32 buf[CXINF_MAX] = {};
+	struct usbatm_data *usbatm = instance->usbatm;
+	struct atm_dev *atm_dev = usbatm->atm_dev;
+	int keep_polling = 1;
+	int ret;
+
+	ret = cxacru_cm_get_array(instance, CM_REQUEST_CARD_INFO_GET, buf, CXINF_MAX);
+	if (ret < 0) {
+		if (ret != -ESHUTDOWN)
+			atm_warn(usbatm, "poll status: error %d\n", ret);
+
+		mutex_lock(&instance->poll_state_serialize);
+		if (instance->poll_state != CXPOLL_SHUTDOWN) {
+			instance->poll_state = CXPOLL_STOPPED;
+
+			if (ret != -ESHUTDOWN)
+				atm_warn(usbatm, "polling disabled, set adsl_state"
+						" to 'start' or 'poll' to resume\n");
+		}
+		mutex_unlock(&instance->poll_state_serialize);
+		goto reschedule;
+	}
+
+	memcpy(instance->card_info, buf, sizeof(instance->card_info));
+
+	if (instance->adsl_status != buf[CXINF_LINE_STARTABLE]) {
+		instance->adsl_status = buf[CXINF_LINE_STARTABLE];
+
+		switch (instance->adsl_status) {
+		case 0:
+			atm_printk(KERN_INFO, usbatm, "ADSL state: running\n");
+			break;
+
+		case 1:
+			atm_printk(KERN_INFO, usbatm, "ADSL state: stopped\n");
+			break;
+
+		default:
+			atm_printk(KERN_INFO, usbatm, "Unknown adsl status %02x\n", instance->adsl_status);
+			break;
+		}
+	}
+
+	if (instance->line_status == buf[CXINF_LINE_STATUS])
+		goto reschedule;
+
+	instance->line_status = buf[CXINF_LINE_STATUS];
+	switch (instance->line_status) {
+	case 0:
+		atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
+		atm_info(usbatm, "ADSL line: down\n");
+		break;
+
+	case 1:
+		atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
+		atm_info(usbatm, "ADSL line: attempting to activate\n");
+		break;
+
+	case 2:
+		atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
+		atm_info(usbatm, "ADSL line: training\n");
+		break;
+
+	case 3:
+		atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
+		atm_info(usbatm, "ADSL line: channel analysis\n");
+		break;
+
+	case 4:
+		atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
+		atm_info(usbatm, "ADSL line: exchange\n");
+		break;
+
+	case 5:
+		atm_dev->link_rate = buf[CXINF_DOWNSTREAM_RATE] * 1000 / 424;
+		atm_dev_signal_change(atm_dev, ATM_PHY_SIG_FOUND);
+
+		atm_info(usbatm, "ADSL line: up (%d kb/s down | %d kb/s up)\n",
+		     buf[CXINF_DOWNSTREAM_RATE], buf[CXINF_UPSTREAM_RATE]);
+		break;
+
+	case 6:
+		atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
+		atm_info(usbatm, "ADSL line: waiting\n");
+		break;
+
+	case 7:
+		atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
+		atm_info(usbatm, "ADSL line: initializing\n");
+		break;
+
+	default:
+		atm_dev_signal_change(atm_dev, ATM_PHY_SIG_UNKNOWN);
+		atm_info(usbatm, "Unknown line state %02x\n", instance->line_status);
+		break;
+	}
+reschedule:
+
+	mutex_lock(&instance->poll_state_serialize);
+	if (instance->poll_state == CXPOLL_STOPPING &&
+				instance->adsl_status == 1 && /* stopped */
+				instance->line_status == 0) /* down */
+		instance->poll_state = CXPOLL_STOPPED;
+
+	if (instance->poll_state == CXPOLL_STOPPED)
+		keep_polling = 0;
+	mutex_unlock(&instance->poll_state_serialize);
+
+	if (keep_polling)
+		schedule_delayed_work(&instance->poll_work,
+				round_jiffies_relative(POLL_INTERVAL*HZ));
+}
+
+static int cxacru_fw(struct usb_device *usb_dev, enum cxacru_fw_request fw,
+		     u8 code1, u8 code2, u32 addr, const u8 *data, int size)
+{
+	int ret;
+	u8 *buf;
+	int offd, offb;
+	const int stride = CMD_PACKET_SIZE - 8;
+
+	buf = (u8 *) __get_free_page(GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	offb = offd = 0;
+	do {
+		int l = min_t(int, stride, size - offd);
+
+		buf[offb++] = fw;
+		buf[offb++] = l;
+		buf[offb++] = code1;
+		buf[offb++] = code2;
+		put_unaligned(cpu_to_le32(addr), (__le32 *)(buf + offb));
+		offb += 4;
+		addr += l;
+		if (l)
+			memcpy(buf + offb, data + offd, l);
+		if (l < stride)
+			memset(buf + offb + l, 0, stride - l);
+		offb += stride;
+		offd += stride;
+		if ((offb >= PAGE_SIZE) || (offd >= size)) {
+			ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD),
+					   buf, offb, NULL, CMD_TIMEOUT);
+			if (ret < 0) {
+				dev_dbg(&usb_dev->dev, "sending fw %#x failed\n", fw);
+				goto cleanup;
+			}
+			offb = 0;
+		}
+	} while (offd < size);
+	dev_dbg(&usb_dev->dev, "sent fw %#x\n", fw);
+
+	ret = 0;
+
+cleanup:
+	free_page((unsigned long) buf);
+	return ret;
+}
+
+static void cxacru_upload_firmware(struct cxacru_data *instance,
+				   const struct firmware *fw,
+				   const struct firmware *bp)
+{
+	int ret;
+	struct usbatm_data *usbatm = instance->usbatm;
+	struct usb_device *usb_dev = usbatm->usb_dev;
+	__le16 signature[] = { usb_dev->descriptor.idVendor,
+			       usb_dev->descriptor.idProduct };
+	__le32 val;
+
+	usb_dbg(usbatm, "%s\n", __func__);
+
+	/* FirmwarePllFClkValue */
+	val = cpu_to_le32(instance->modem_type->pll_f_clk);
+	ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, PLLFCLK_ADDR, (u8 *) &val, 4);
+	if (ret) {
+		usb_err(usbatm, "FirmwarePllFClkValue failed: %d\n", ret);
+		return;
+	}
+
+	/* FirmwarePllBClkValue */
+	val = cpu_to_le32(instance->modem_type->pll_b_clk);
+	ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, PLLBCLK_ADDR, (u8 *) &val, 4);
+	if (ret) {
+		usb_err(usbatm, "FirmwarePllBClkValue failed: %d\n", ret);
+		return;
+	}
+
+	/* Enable SDRAM */
+	val = cpu_to_le32(SDRAM_ENA);
+	ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, SDRAMEN_ADDR, (u8 *) &val, 4);
+	if (ret) {
+		usb_err(usbatm, "Enable SDRAM failed: %d\n", ret);
+		return;
+	}
+
+	/* Firmware */
+	usb_info(usbatm, "loading firmware\n");
+	ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, FW_ADDR, fw->data, fw->size);
+	if (ret) {
+		usb_err(usbatm, "Firmware upload failed: %d\n", ret);
+		return;
+	}
+
+	/* Boot ROM patch */
+	if (instance->modem_type->boot_rom_patch) {
+		usb_info(usbatm, "loading boot ROM patch\n");
+		ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, BR_ADDR, bp->data, bp->size);
+		if (ret) {
+			usb_err(usbatm, "Boot ROM patching failed: %d\n", ret);
+			return;
+		}
+	}
+
+	/* Signature */
+	ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, SIG_ADDR, (u8 *) signature, 4);
+	if (ret) {
+		usb_err(usbatm, "Signature storing failed: %d\n", ret);
+		return;
+	}
+
+	usb_info(usbatm, "starting device\n");
+	if (instance->modem_type->boot_rom_patch) {
+		val = cpu_to_le32(BR_ADDR);
+		ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, BR_STACK_ADDR, (u8 *) &val, 4);
+	} else {
+		ret = cxacru_fw(usb_dev, FW_GOTO_MEM, 0x0, 0x0, FW_ADDR, NULL, 0);
+	}
+	if (ret) {
+		usb_err(usbatm, "Passing control to firmware failed: %d\n", ret);
+		return;
+	}
+
+	/* Delay to allow firmware to start up. */
+	msleep_interruptible(1000);
+
+	usb_clear_halt(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD));
+	usb_clear_halt(usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_CMD));
+	usb_clear_halt(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_DATA));
+	usb_clear_halt(usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_DATA));
+
+	ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0);
+	if (ret < 0) {
+		usb_err(usbatm, "modem failed to initialize: %d\n", ret);
+		return;
+	}
+}
+
+static int cxacru_find_firmware(struct cxacru_data *instance,
+				char *phase, const struct firmware **fw_p)
+{
+	struct usbatm_data *usbatm = instance->usbatm;
+	struct device *dev = &usbatm->usb_intf->dev;
+	char buf[16];
+
+	sprintf(buf, "cxacru-%s.bin", phase);
+	usb_dbg(usbatm, "cxacru_find_firmware: looking for %s\n", buf);
+
+	if (request_firmware(fw_p, buf, dev)) {
+		usb_dbg(usbatm, "no stage %s firmware found\n", phase);
+		return -ENOENT;
+	}
+
+	usb_info(usbatm, "found firmware %s\n", buf);
+
+	return 0;
+}
+
+static int cxacru_heavy_init(struct usbatm_data *usbatm_instance,
+			     struct usb_interface *usb_intf)
+{
+	const struct firmware *fw, *bp;
+	struct cxacru_data *instance = usbatm_instance->driver_data;
+	int ret = cxacru_find_firmware(instance, "fw", &fw);
+
+	if (ret) {
+		usb_warn(usbatm_instance, "firmware (cxacru-fw.bin) unavailable (system misconfigured?)\n");
+		return ret;
+	}
+
+	if (instance->modem_type->boot_rom_patch) {
+		ret = cxacru_find_firmware(instance, "bp", &bp);
+		if (ret) {
+			usb_warn(usbatm_instance, "boot ROM patch (cxacru-bp.bin) unavailable (system misconfigured?)\n");
+			release_firmware(fw);
+			return ret;
+		}
+	}
+
+	cxacru_upload_firmware(instance, fw, bp);
+
+	if (instance->modem_type->boot_rom_patch)
+		release_firmware(bp);
+	release_firmware(fw);
+
+	ret = cxacru_card_status(instance);
+	if (ret)
+		usb_dbg(usbatm_instance, "modem initialisation failed\n");
+	else
+		usb_dbg(usbatm_instance, "done setting up the modem\n");
+
+	return ret;
+}
+
+static int cxacru_bind(struct usbatm_data *usbatm_instance,
+		       struct usb_interface *intf, const struct usb_device_id *id)
+{
+	struct cxacru_data *instance;
+	struct usb_device *usb_dev = interface_to_usbdev(intf);
+	struct usb_host_endpoint *cmd_ep = usb_dev->ep_in[CXACRU_EP_CMD];
+	int ret;
+
+	/* instance init */
+	instance = kzalloc(sizeof(*instance), GFP_KERNEL);
+	if (!instance)
+		return -ENOMEM;
+
+	instance->usbatm = usbatm_instance;
+	instance->modem_type = (struct cxacru_modem_type *) id->driver_info;
+
+	mutex_init(&instance->poll_state_serialize);
+	instance->poll_state = CXPOLL_STOPPED;
+	instance->line_status = -1;
+	instance->adsl_status = -1;
+
+	mutex_init(&instance->adsl_state_serialize);
+
+	instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL);
+	if (!instance->rcv_buf) {
+		usb_dbg(usbatm_instance, "cxacru_bind: no memory for rcv_buf\n");
+		ret = -ENOMEM;
+		goto fail;
+	}
+	instance->snd_buf = (u8 *) __get_free_page(GFP_KERNEL);
+	if (!instance->snd_buf) {
+		usb_dbg(usbatm_instance, "cxacru_bind: no memory for snd_buf\n");
+		ret = -ENOMEM;
+		goto fail;
+	}
+	instance->rcv_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!instance->rcv_urb) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+	instance->snd_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!instance->snd_urb) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	if (!cmd_ep) {
+		usb_dbg(usbatm_instance, "cxacru_bind: no command endpoint\n");
+		ret = -ENODEV;
+		goto fail;
+	}
+
+	if ((cmd_ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
+			== USB_ENDPOINT_XFER_INT) {
+		usb_fill_int_urb(instance->rcv_urb,
+			usb_dev, usb_rcvintpipe(usb_dev, CXACRU_EP_CMD),
+			instance->rcv_buf, PAGE_SIZE,
+			cxacru_blocking_completion, &instance->rcv_done, 1);
+
+		usb_fill_int_urb(instance->snd_urb,
+			usb_dev, usb_sndintpipe(usb_dev, CXACRU_EP_CMD),
+			instance->snd_buf, PAGE_SIZE,
+			cxacru_blocking_completion, &instance->snd_done, 4);
+	} else {
+		usb_fill_bulk_urb(instance->rcv_urb,
+			usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_CMD),
+			instance->rcv_buf, PAGE_SIZE,
+			cxacru_blocking_completion, &instance->rcv_done);
+
+		usb_fill_bulk_urb(instance->snd_urb,
+			usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD),
+			instance->snd_buf, PAGE_SIZE,
+			cxacru_blocking_completion, &instance->snd_done);
+	}
+
+	mutex_init(&instance->cm_serialize);
+
+	INIT_DELAYED_WORK(&instance->poll_work, cxacru_poll_status);
+
+	usbatm_instance->driver_data = instance;
+
+	usbatm_instance->flags = (cxacru_card_status(instance) ? 0 : UDSL_SKIP_HEAVY_INIT);
+
+	return 0;
+
+ fail:
+	free_page((unsigned long) instance->snd_buf);
+	free_page((unsigned long) instance->rcv_buf);
+	usb_free_urb(instance->snd_urb);
+	usb_free_urb(instance->rcv_urb);
+	kfree(instance);
+
+	return ret;
+}
+
+static void cxacru_unbind(struct usbatm_data *usbatm_instance,
+		struct usb_interface *intf)
+{
+	struct cxacru_data *instance = usbatm_instance->driver_data;
+	int is_polling = 1;
+
+	usb_dbg(usbatm_instance, "cxacru_unbind entered\n");
+
+	if (!instance) {
+		usb_dbg(usbatm_instance, "cxacru_unbind: NULL instance!\n");
+		return;
+	}
+
+	mutex_lock(&instance->poll_state_serialize);
+	BUG_ON(instance->poll_state == CXPOLL_SHUTDOWN);
+
+	/* ensure that status polling continues unless
+	 * it has already stopped */
+	if (instance->poll_state == CXPOLL_STOPPED)
+		is_polling = 0;
+
+	/* stop polling from being stopped or started */
+	instance->poll_state = CXPOLL_SHUTDOWN;
+	mutex_unlock(&instance->poll_state_serialize);
+
+	if (is_polling)
+		cancel_delayed_work_sync(&instance->poll_work);
+
+	usb_kill_urb(instance->snd_urb);
+	usb_kill_urb(instance->rcv_urb);
+	usb_free_urb(instance->snd_urb);
+	usb_free_urb(instance->rcv_urb);
+
+	free_page((unsigned long) instance->snd_buf);
+	free_page((unsigned long) instance->rcv_buf);
+
+	kfree(instance);
+
+	usbatm_instance->driver_data = NULL;
+}
+
+static const struct cxacru_modem_type cxacru_cafe = {
+	.pll_f_clk = 0x02d874df,
+	.pll_b_clk = 0x0196a51a,
+	.boot_rom_patch = 1,
+};
+
+static const struct cxacru_modem_type cxacru_cb00 = {
+	.pll_f_clk = 0x5,
+	.pll_b_clk = 0x3,
+	.boot_rom_patch = 0,
+};
+
+static const struct usb_device_id cxacru_usb_ids[] = {
+	{ /* V = Conexant			P = ADSL modem (Euphrates project)	*/
+		USB_DEVICE(0x0572, 0xcafe),	.driver_info = (unsigned long) &cxacru_cafe
+	},
+	{ /* V = Conexant			P = ADSL modem (Hasbani project)	*/
+		USB_DEVICE(0x0572, 0xcb00),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Conexant			P = ADSL modem				*/
+		USB_DEVICE(0x0572, 0xcb01),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Conexant			P = ADSL modem (Well PTI-800) */
+		USB_DEVICE(0x0572, 0xcb02),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Conexant			P = ADSL modem				*/
+		USB_DEVICE(0x0572, 0xcb06),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Conexant			P = ADSL modem (ZTE ZXDSL 852)		*/
+		USB_DEVICE(0x0572, 0xcb07),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Olitec				P = ADSL modem version 2		*/
+		USB_DEVICE(0x08e3, 0x0100),	.driver_info = (unsigned long) &cxacru_cafe
+	},
+	{ /* V = Olitec				P = ADSL modem version 3		*/
+		USB_DEVICE(0x08e3, 0x0102),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Trust/Amigo Technology Co.	P = AMX-CA86U				*/
+		USB_DEVICE(0x0eb0, 0x3457),	.driver_info = (unsigned long) &cxacru_cafe
+	},
+	{ /* V = Zoom				P = 5510				*/
+		USB_DEVICE(0x1803, 0x5510),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Draytek			P = Vigor 318				*/
+		USB_DEVICE(0x0675, 0x0200),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Zyxel				P = 630-C1 aka OMNI ADSL USB (Annex A)	*/
+		USB_DEVICE(0x0586, 0x330a),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Zyxel				P = 630-C3 aka OMNI ADSL USB (Annex B)	*/
+		USB_DEVICE(0x0586, 0x330b),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Aethra				P = Starmodem UM1020			*/
+		USB_DEVICE(0x0659, 0x0020),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Aztech Systems			P = ? AKA Pirelli AUA-010		*/
+		USB_DEVICE(0x0509, 0x0812),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Netopia			P = Cayman 3341(Annex A)/3351(Annex B)	*/
+		USB_DEVICE(0x100d, 0xcb01),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{ /* V = Netopia			P = Cayman 3342(Annex A)/3352(Annex B)	*/
+		USB_DEVICE(0x100d, 0x3342),	.driver_info = (unsigned long) &cxacru_cb00
+	},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, cxacru_usb_ids);
+
+static struct usbatm_driver cxacru_driver = {
+	.driver_name	= cxacru_driver_name,
+	.bind		= cxacru_bind,
+	.heavy_init	= cxacru_heavy_init,
+	.unbind		= cxacru_unbind,
+	.atm_start	= cxacru_atm_start,
+	.atm_stop	= cxacru_remove_device_files,
+	.bulk_in	= CXACRU_EP_DATA,
+	.bulk_out	= CXACRU_EP_DATA,
+	.rx_padding	= 3,
+	.tx_padding	= 11,
+};
+
+static int cxacru_usb_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	struct usb_device *usb_dev = interface_to_usbdev(intf);
+	char buf[15];
+
+	/* Avoid ADSL routers (cx82310_eth).
+	 * Abort if bDeviceClass is 0xff and iProduct is "USB NET CARD".
+	 */
+	if (usb_dev->descriptor.bDeviceClass == USB_CLASS_VENDOR_SPEC
+			&& usb_string(usb_dev, usb_dev->descriptor.iProduct,
+				buf, sizeof(buf)) > 0) {
+		if (!strcmp(buf, "USB NET CARD")) {
+			dev_info(&intf->dev, "ignoring cx82310_eth device\n");
+			return -ENODEV;
+		}
+	}
+
+	return usbatm_usb_probe(intf, id, &cxacru_driver);
+}
+
+static struct usb_driver cxacru_usb_driver = {
+	.name		= cxacru_driver_name,
+	.probe		= cxacru_usb_probe,
+	.disconnect	= usbatm_usb_disconnect,
+	.id_table	= cxacru_usb_ids
+};
+
+module_usb_driver(cxacru_usb_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/atm/speedtch.c b/drivers/usb/atm/speedtch.c
new file mode 100644
index 0000000..973548b
--- /dev/null
+++ b/drivers/usb/atm/speedtch.c
@@ -0,0 +1,946 @@
+// SPDX-License-Identifier: GPL-2.0+
+/******************************************************************************
+ *  speedtch.c  -  Alcatel SpeedTouch USB xDSL modem driver
+ *
+ *  Copyright (C) 2001, Alcatel
+ *  Copyright (C) 2003, Duncan Sands
+ *  Copyright (C) 2004, David Woodhouse
+ *
+ *  Based on "modem_run.c", copyright (C) 2001, Benoit Papillault
+ ******************************************************************************/
+
+#include <asm/page.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/firmware.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/usb/ch9.h>
+#include <linux/workqueue.h>
+
+#include "usbatm.h"
+
+#define DRIVER_AUTHOR	"Johan Verrept, Duncan Sands <duncan.sands@free.fr>"
+#define DRIVER_DESC	"Alcatel SpeedTouch USB driver"
+
+static const char speedtch_driver_name[] = "speedtch";
+
+#define CTRL_TIMEOUT 2000	/* milliseconds */
+#define DATA_TIMEOUT 2000	/* milliseconds */
+
+#define OFFSET_7	0		/* size 1 */
+#define OFFSET_b	1		/* size 8 */
+#define OFFSET_d	9		/* size 4 */
+#define OFFSET_e	13		/* size 1 */
+#define OFFSET_f	14		/* size 1 */
+
+#define SIZE_7		1
+#define SIZE_b		8
+#define SIZE_d		4
+#define SIZE_e		1
+#define SIZE_f		1
+
+#define MIN_POLL_DELAY		5000	/* milliseconds */
+#define MAX_POLL_DELAY		60000	/* milliseconds */
+
+#define RESUBMIT_DELAY		1000	/* milliseconds */
+
+#define DEFAULT_BULK_ALTSETTING	1
+#define DEFAULT_ISOC_ALTSETTING	3
+#define DEFAULT_DL_512_FIRST	0
+#define DEFAULT_ENABLE_ISOC	0
+#define DEFAULT_SW_BUFFERING	0
+
+static unsigned int altsetting = 0; /* zero means: use the default */
+static bool dl_512_first = DEFAULT_DL_512_FIRST;
+static bool enable_isoc = DEFAULT_ENABLE_ISOC;
+static bool sw_buffering = DEFAULT_SW_BUFFERING;
+
+#define DEFAULT_B_MAX_DSL	8128
+#define DEFAULT_MODEM_MODE	11
+#define MODEM_OPTION_LENGTH	16
+static const unsigned char DEFAULT_MODEM_OPTION[MODEM_OPTION_LENGTH] = {
+	0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static unsigned int BMaxDSL = DEFAULT_B_MAX_DSL;
+static unsigned char ModemMode = DEFAULT_MODEM_MODE;
+static unsigned char ModemOption[MODEM_OPTION_LENGTH];
+static unsigned int num_ModemOption;
+
+module_param(altsetting, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(altsetting,
+		"Alternative setting for data interface (bulk_default: "
+		__MODULE_STRING(DEFAULT_BULK_ALTSETTING) "; isoc_default: "
+		__MODULE_STRING(DEFAULT_ISOC_ALTSETTING) ")");
+
+module_param(dl_512_first, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(dl_512_first,
+		 "Read 512 bytes before sending firmware (default: "
+		 __MODULE_STRING(DEFAULT_DL_512_FIRST) ")");
+
+module_param(enable_isoc, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(enable_isoc,
+		"Use isochronous transfers if available (default: "
+		__MODULE_STRING(DEFAULT_ENABLE_ISOC) ")");
+
+module_param(sw_buffering, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(sw_buffering,
+		 "Enable software buffering (default: "
+		 __MODULE_STRING(DEFAULT_SW_BUFFERING) ")");
+
+module_param(BMaxDSL, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(BMaxDSL,
+		"default: " __MODULE_STRING(DEFAULT_B_MAX_DSL));
+
+module_param(ModemMode, byte, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(ModemMode,
+		"default: " __MODULE_STRING(DEFAULT_MODEM_MODE));
+
+module_param_array(ModemOption, byte, &num_ModemOption, S_IRUGO);
+MODULE_PARM_DESC(ModemOption, "default: 0x10,0x00,0x00,0x00,0x20");
+
+#define INTERFACE_DATA		1
+#define ENDPOINT_INT		0x81
+#define ENDPOINT_BULK_DATA	0x07
+#define ENDPOINT_ISOC_DATA	0x07
+#define ENDPOINT_FIRMWARE	0x05
+
+struct speedtch_params {
+	unsigned int altsetting;
+	unsigned int BMaxDSL;
+	unsigned char ModemMode;
+	unsigned char ModemOption[MODEM_OPTION_LENGTH];
+};
+
+struct speedtch_instance_data {
+	struct usbatm_data *usbatm;
+
+	struct speedtch_params params; /* set in probe, constant afterwards */
+
+	struct timer_list status_check_timer;
+	struct work_struct status_check_work;
+
+	unsigned char last_status;
+
+	int poll_delay; /* milliseconds */
+
+	struct timer_list resubmit_timer;
+	struct urb *int_urb;
+	unsigned char int_data[16];
+
+	unsigned char scratch_buffer[16];
+};
+
+/***************
+**  firmware  **
+***************/
+
+static void speedtch_set_swbuff(struct speedtch_instance_data *instance, int state)
+{
+	struct usbatm_data *usbatm = instance->usbatm;
+	struct usb_device *usb_dev = usbatm->usb_dev;
+	int ret;
+
+	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			      0x32, 0x40, state ? 0x01 : 0x00, 0x00, NULL, 0, CTRL_TIMEOUT);
+	if (ret < 0)
+		usb_warn(usbatm,
+			 "%sabling SW buffering: usb_control_msg returned %d\n",
+			 state ? "En" : "Dis", ret);
+	else
+		usb_dbg(usbatm, "speedtch_set_swbuff: %sbled SW buffering\n", state ? "En" : "Dis");
+}
+
+static void speedtch_test_sequence(struct speedtch_instance_data *instance)
+{
+	struct usbatm_data *usbatm = instance->usbatm;
+	struct usb_device *usb_dev = usbatm->usb_dev;
+	unsigned char *buf = instance->scratch_buffer;
+	int ret;
+
+	/* URB 147 */
+	buf[0] = 0x1c;
+	buf[1] = 0x50;
+	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			      0x01, 0x40, 0x0b, 0x00, buf, 2, CTRL_TIMEOUT);
+	if (ret < 0)
+		usb_warn(usbatm, "%s failed on URB147: %d\n", __func__, ret);
+
+	/* URB 148 */
+	buf[0] = 0x32;
+	buf[1] = 0x00;
+	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			      0x01, 0x40, 0x02, 0x00, buf, 2, CTRL_TIMEOUT);
+	if (ret < 0)
+		usb_warn(usbatm, "%s failed on URB148: %d\n", __func__, ret);
+
+	/* URB 149 */
+	buf[0] = 0x01;
+	buf[1] = 0x00;
+	buf[2] = 0x01;
+	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			      0x01, 0x40, 0x03, 0x00, buf, 3, CTRL_TIMEOUT);
+	if (ret < 0)
+		usb_warn(usbatm, "%s failed on URB149: %d\n", __func__, ret);
+
+	/* URB 150 */
+	buf[0] = 0x01;
+	buf[1] = 0x00;
+	buf[2] = 0x01;
+	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			      0x01, 0x40, 0x04, 0x00, buf, 3, CTRL_TIMEOUT);
+	if (ret < 0)
+		usb_warn(usbatm, "%s failed on URB150: %d\n", __func__, ret);
+
+	/* Extra initialisation in recent drivers - gives higher speeds */
+
+	/* URBext1 */
+	buf[0] = instance->params.ModemMode;
+	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			      0x01, 0x40, 0x11, 0x00, buf, 1, CTRL_TIMEOUT);
+	if (ret < 0)
+		usb_warn(usbatm, "%s failed on URBext1: %d\n", __func__, ret);
+
+	/* URBext2 */
+	/* This seems to be the one which actually triggers the higher sync
+	   rate -- it does require the new firmware too, although it works OK
+	   with older firmware */
+	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			      0x01, 0x40, 0x14, 0x00,
+			      instance->params.ModemOption,
+			      MODEM_OPTION_LENGTH, CTRL_TIMEOUT);
+	if (ret < 0)
+		usb_warn(usbatm, "%s failed on URBext2: %d\n", __func__, ret);
+
+	/* URBext3 */
+	buf[0] = instance->params.BMaxDSL & 0xff;
+	buf[1] = instance->params.BMaxDSL >> 8;
+	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			      0x01, 0x40, 0x12, 0x00, buf, 2, CTRL_TIMEOUT);
+	if (ret < 0)
+		usb_warn(usbatm, "%s failed on URBext3: %d\n", __func__, ret);
+}
+
+static int speedtch_upload_firmware(struct speedtch_instance_data *instance,
+				     const struct firmware *fw1,
+				     const struct firmware *fw2)
+{
+	unsigned char *buffer;
+	struct usbatm_data *usbatm = instance->usbatm;
+	struct usb_device *usb_dev = usbatm->usb_dev;
+	int actual_length;
+	int ret = 0;
+	int offset;
+
+	usb_dbg(usbatm, "%s entered\n", __func__);
+
+	buffer = (unsigned char *)__get_free_page(GFP_KERNEL);
+	if (!buffer) {
+		ret = -ENOMEM;
+		usb_dbg(usbatm, "%s: no memory for buffer!\n", __func__);
+		goto out;
+	}
+
+	if (!usb_ifnum_to_if(usb_dev, 2)) {
+		ret = -ENODEV;
+		usb_dbg(usbatm, "%s: interface not found!\n", __func__);
+		goto out_free;
+	}
+
+	/* URB 7 */
+	if (dl_512_first) {	/* some modems need a read before writing the firmware */
+		ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, ENDPOINT_FIRMWARE),
+				   buffer, 0x200, &actual_length, 2000);
+
+		if (ret < 0 && ret != -ETIMEDOUT)
+			usb_warn(usbatm, "%s: read BLOCK0 from modem failed (%d)!\n", __func__, ret);
+		else
+			usb_dbg(usbatm, "%s: BLOCK0 downloaded (%d bytes)\n", __func__, ret);
+	}
+
+	/* URB 8 : both leds are static green */
+	for (offset = 0; offset < fw1->size; offset += PAGE_SIZE) {
+		int thislen = min_t(int, PAGE_SIZE, fw1->size - offset);
+		memcpy(buffer, fw1->data + offset, thislen);
+
+		ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, ENDPOINT_FIRMWARE),
+				   buffer, thislen, &actual_length, DATA_TIMEOUT);
+
+		if (ret < 0) {
+			usb_err(usbatm, "%s: write BLOCK1 to modem failed (%d)!\n", __func__, ret);
+			goto out_free;
+		}
+		usb_dbg(usbatm, "%s: BLOCK1 uploaded (%zu bytes)\n", __func__, fw1->size);
+	}
+
+	/* USB led blinking green, ADSL led off */
+
+	/* URB 11 */
+	ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, ENDPOINT_FIRMWARE),
+			   buffer, 0x200, &actual_length, DATA_TIMEOUT);
+
+	if (ret < 0) {
+		usb_err(usbatm, "%s: read BLOCK2 from modem failed (%d)!\n", __func__, ret);
+		goto out_free;
+	}
+	usb_dbg(usbatm, "%s: BLOCK2 downloaded (%d bytes)\n", __func__, actual_length);
+
+	/* URBs 12 to 139 - USB led blinking green, ADSL led off */
+	for (offset = 0; offset < fw2->size; offset += PAGE_SIZE) {
+		int thislen = min_t(int, PAGE_SIZE, fw2->size - offset);
+		memcpy(buffer, fw2->data + offset, thislen);
+
+		ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, ENDPOINT_FIRMWARE),
+				   buffer, thislen, &actual_length, DATA_TIMEOUT);
+
+		if (ret < 0) {
+			usb_err(usbatm, "%s: write BLOCK3 to modem failed (%d)!\n", __func__, ret);
+			goto out_free;
+		}
+	}
+	usb_dbg(usbatm, "%s: BLOCK3 uploaded (%zu bytes)\n", __func__, fw2->size);
+
+	/* USB led static green, ADSL led static red */
+
+	/* URB 142 */
+	ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, ENDPOINT_FIRMWARE),
+			   buffer, 0x200, &actual_length, DATA_TIMEOUT);
+
+	if (ret < 0) {
+		usb_err(usbatm, "%s: read BLOCK4 from modem failed (%d)!\n", __func__, ret);
+		goto out_free;
+	}
+
+	/* success */
+	usb_dbg(usbatm, "%s: BLOCK4 downloaded (%d bytes)\n", __func__, actual_length);
+
+	/* Delay to allow firmware to start up. We can do this here
+	   because we're in our own kernel thread anyway. */
+	msleep_interruptible(1000);
+
+	if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, instance->params.altsetting)) < 0) {
+		usb_err(usbatm, "%s: setting interface to %d failed (%d)!\n", __func__, instance->params.altsetting, ret);
+		goto out_free;
+	}
+
+	/* Enable software buffering, if requested */
+	if (sw_buffering)
+		speedtch_set_swbuff(instance, 1);
+
+	/* Magic spell; don't ask us what this does */
+	speedtch_test_sequence(instance);
+
+	ret = 0;
+
+out_free:
+	free_page((unsigned long)buffer);
+out:
+	return ret;
+}
+
+static int speedtch_find_firmware(struct usbatm_data *usbatm, struct usb_interface *intf,
+				  int phase, const struct firmware **fw_p)
+{
+	struct device *dev = &intf->dev;
+	const u16 bcdDevice = le16_to_cpu(interface_to_usbdev(intf)->descriptor.bcdDevice);
+	const u8 major_revision = bcdDevice >> 8;
+	const u8 minor_revision = bcdDevice & 0xff;
+	char buf[24];
+
+	sprintf(buf, "speedtch-%d.bin.%x.%02x", phase, major_revision, minor_revision);
+	usb_dbg(usbatm, "%s: looking for %s\n", __func__, buf);
+
+	if (request_firmware(fw_p, buf, dev)) {
+		sprintf(buf, "speedtch-%d.bin.%x", phase, major_revision);
+		usb_dbg(usbatm, "%s: looking for %s\n", __func__, buf);
+
+		if (request_firmware(fw_p, buf, dev)) {
+			sprintf(buf, "speedtch-%d.bin", phase);
+			usb_dbg(usbatm, "%s: looking for %s\n", __func__, buf);
+
+			if (request_firmware(fw_p, buf, dev)) {
+				usb_err(usbatm, "%s: no stage %d firmware found!\n", __func__, phase);
+				return -ENOENT;
+			}
+		}
+	}
+
+	usb_info(usbatm, "found stage %d firmware %s\n", phase, buf);
+
+	return 0;
+}
+
+static int speedtch_heavy_init(struct usbatm_data *usbatm, struct usb_interface *intf)
+{
+	const struct firmware *fw1, *fw2;
+	struct speedtch_instance_data *instance = usbatm->driver_data;
+	int ret;
+
+	if ((ret = speedtch_find_firmware(usbatm, intf, 1, &fw1)) < 0)
+		return ret;
+
+	if ((ret = speedtch_find_firmware(usbatm, intf, 2, &fw2)) < 0) {
+		release_firmware(fw1);
+		return ret;
+	}
+
+	if ((ret = speedtch_upload_firmware(instance, fw1, fw2)) < 0)
+		usb_err(usbatm, "%s: firmware upload failed (%d)!\n", __func__, ret);
+
+	release_firmware(fw2);
+	release_firmware(fw1);
+
+	return ret;
+}
+
+
+/**********
+**  ATM  **
+**********/
+
+static int speedtch_read_status(struct speedtch_instance_data *instance)
+{
+	struct usbatm_data *usbatm = instance->usbatm;
+	struct usb_device *usb_dev = usbatm->usb_dev;
+	unsigned char *buf = instance->scratch_buffer;
+	int ret;
+
+	memset(buf, 0, 16);
+
+	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+			      0x12, 0xc0, 0x07, 0x00, buf + OFFSET_7, SIZE_7,
+			      CTRL_TIMEOUT);
+	if (ret < 0) {
+		atm_dbg(usbatm, "%s: MSG 7 failed\n", __func__);
+		return ret;
+	}
+
+	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+			      0x12, 0xc0, 0x0b, 0x00, buf + OFFSET_b, SIZE_b,
+			      CTRL_TIMEOUT);
+	if (ret < 0) {
+		atm_dbg(usbatm, "%s: MSG B failed\n", __func__);
+		return ret;
+	}
+
+	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+			      0x12, 0xc0, 0x0d, 0x00, buf + OFFSET_d, SIZE_d,
+			      CTRL_TIMEOUT);
+	if (ret < 0) {
+		atm_dbg(usbatm, "%s: MSG D failed\n", __func__);
+		return ret;
+	}
+
+	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+			      0x01, 0xc0, 0x0e, 0x00, buf + OFFSET_e, SIZE_e,
+			      CTRL_TIMEOUT);
+	if (ret < 0) {
+		atm_dbg(usbatm, "%s: MSG E failed\n", __func__);
+		return ret;
+	}
+
+	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+			      0x01, 0xc0, 0x0f, 0x00, buf + OFFSET_f, SIZE_f,
+			      CTRL_TIMEOUT);
+	if (ret < 0) {
+		atm_dbg(usbatm, "%s: MSG F failed\n", __func__);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int speedtch_start_synchro(struct speedtch_instance_data *instance)
+{
+	struct usbatm_data *usbatm = instance->usbatm;
+	struct usb_device *usb_dev = usbatm->usb_dev;
+	unsigned char *buf = instance->scratch_buffer;
+	int ret;
+
+	atm_dbg(usbatm, "%s entered\n", __func__);
+
+	memset(buf, 0, 2);
+
+	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+			      0x12, 0xc0, 0x04, 0x00,
+			      buf, 2, CTRL_TIMEOUT);
+
+	if (ret < 0)
+		atm_warn(usbatm, "failed to start ADSL synchronisation: %d\n", ret);
+	else
+		atm_dbg(usbatm, "%s: modem prodded. %d bytes returned: %02x %02x\n",
+			__func__, ret, buf[0], buf[1]);
+
+	return ret;
+}
+
+static void speedtch_check_status(struct work_struct *work)
+{
+	struct speedtch_instance_data *instance =
+		container_of(work, struct speedtch_instance_data,
+			     status_check_work);
+	struct usbatm_data *usbatm = instance->usbatm;
+	struct atm_dev *atm_dev = usbatm->atm_dev;
+	unsigned char *buf = instance->scratch_buffer;
+	int down_speed, up_speed, ret;
+	unsigned char status;
+
+#ifdef VERBOSE_DEBUG
+	atm_dbg(usbatm, "%s entered\n", __func__);
+#endif
+
+	ret = speedtch_read_status(instance);
+	if (ret < 0) {
+		atm_warn(usbatm, "error %d fetching device status\n", ret);
+		instance->poll_delay = min(2 * instance->poll_delay, MAX_POLL_DELAY);
+		return;
+	}
+
+	instance->poll_delay = max(instance->poll_delay / 2, MIN_POLL_DELAY);
+
+	status = buf[OFFSET_7];
+
+	if ((status != instance->last_status) || !status) {
+		atm_dbg(usbatm, "%s: line state 0x%02x\n", __func__, status);
+
+		switch (status) {
+		case 0:
+			atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
+			if (instance->last_status)
+				atm_info(usbatm, "ADSL line is down\n");
+			/* It may never resync again unless we ask it to... */
+			ret = speedtch_start_synchro(instance);
+			break;
+
+		case 0x08:
+			atm_dev_signal_change(atm_dev, ATM_PHY_SIG_UNKNOWN);
+			atm_info(usbatm, "ADSL line is blocked?\n");
+			break;
+
+		case 0x10:
+			atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
+			atm_info(usbatm, "ADSL line is synchronising\n");
+			break;
+
+		case 0x20:
+			down_speed = buf[OFFSET_b] | (buf[OFFSET_b + 1] << 8)
+				| (buf[OFFSET_b + 2] << 16) | (buf[OFFSET_b + 3] << 24);
+			up_speed = buf[OFFSET_b + 4] | (buf[OFFSET_b + 5] << 8)
+				| (buf[OFFSET_b + 6] << 16) | (buf[OFFSET_b + 7] << 24);
+
+			if (!(down_speed & 0x0000ffff) && !(up_speed & 0x0000ffff)) {
+				down_speed >>= 16;
+				up_speed >>= 16;
+			}
+
+			atm_dev->link_rate = down_speed * 1000 / 424;
+			atm_dev_signal_change(atm_dev, ATM_PHY_SIG_FOUND);
+
+			atm_info(usbatm,
+				 "ADSL line is up (%d kb/s down | %d kb/s up)\n",
+				 down_speed, up_speed);
+			break;
+
+		default:
+			atm_dev_signal_change(atm_dev, ATM_PHY_SIG_UNKNOWN);
+			atm_info(usbatm, "unknown line state %02x\n", status);
+			break;
+		}
+
+		instance->last_status = status;
+	}
+}
+
+static void speedtch_status_poll(struct timer_list *t)
+{
+	struct speedtch_instance_data *instance = from_timer(instance, t,
+						             status_check_timer);
+
+	schedule_work(&instance->status_check_work);
+
+	/* The following check is racy, but the race is harmless */
+	if (instance->poll_delay < MAX_POLL_DELAY)
+		mod_timer(&instance->status_check_timer, jiffies + msecs_to_jiffies(instance->poll_delay));
+	else
+		atm_warn(instance->usbatm, "Too many failures - disabling line status polling\n");
+}
+
+static void speedtch_resubmit_int(struct timer_list *t)
+{
+	struct speedtch_instance_data *instance = from_timer(instance, t,
+							     resubmit_timer);
+	struct urb *int_urb = instance->int_urb;
+	int ret;
+
+	atm_dbg(instance->usbatm, "%s entered\n", __func__);
+
+	if (int_urb) {
+		ret = usb_submit_urb(int_urb, GFP_ATOMIC);
+		if (!ret)
+			schedule_work(&instance->status_check_work);
+		else {
+			atm_dbg(instance->usbatm, "%s: usb_submit_urb failed with result %d\n", __func__, ret);
+			mod_timer(&instance->resubmit_timer, jiffies + msecs_to_jiffies(RESUBMIT_DELAY));
+		}
+	}
+}
+
+static void speedtch_handle_int(struct urb *int_urb)
+{
+	struct speedtch_instance_data *instance = int_urb->context;
+	struct usbatm_data *usbatm = instance->usbatm;
+	unsigned int count = int_urb->actual_length;
+	int status = int_urb->status;
+	int ret;
+
+	/* The magic interrupt for "up state" */
+	static const unsigned char up_int[6]   = { 0xa1, 0x00, 0x01, 0x00, 0x00, 0x00 };
+	/* The magic interrupt for "down state" */
+	static const unsigned char down_int[6] = { 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+	atm_dbg(usbatm, "%s entered\n", __func__);
+
+	if (status < 0) {
+		atm_dbg(usbatm, "%s: nonzero urb status %d!\n", __func__, status);
+		goto fail;
+	}
+
+	if ((count == 6) && !memcmp(up_int, instance->int_data, 6)) {
+		del_timer(&instance->status_check_timer);
+		atm_info(usbatm, "DSL line goes up\n");
+	} else if ((count == 6) && !memcmp(down_int, instance->int_data, 6)) {
+		atm_info(usbatm, "DSL line goes down\n");
+	} else {
+		int i;
+
+		atm_dbg(usbatm, "%s: unknown interrupt packet of length %d:", __func__, count);
+		for (i = 0; i < count; i++)
+			printk(" %02x", instance->int_data[i]);
+		printk("\n");
+		goto fail;
+	}
+
+	int_urb = instance->int_urb;
+	if (int_urb) {
+		ret = usb_submit_urb(int_urb, GFP_ATOMIC);
+		schedule_work(&instance->status_check_work);
+		if (ret < 0) {
+			atm_dbg(usbatm, "%s: usb_submit_urb failed with result %d\n", __func__, ret);
+			goto fail;
+		}
+	}
+
+	return;
+
+fail:
+	int_urb = instance->int_urb;
+	if (int_urb)
+		mod_timer(&instance->resubmit_timer, jiffies + msecs_to_jiffies(RESUBMIT_DELAY));
+}
+
+static int speedtch_atm_start(struct usbatm_data *usbatm, struct atm_dev *atm_dev)
+{
+	struct usb_device *usb_dev = usbatm->usb_dev;
+	struct speedtch_instance_data *instance = usbatm->driver_data;
+	int i, ret;
+	unsigned char mac_str[13];
+
+	atm_dbg(usbatm, "%s entered\n", __func__);
+
+	/* Set MAC address, it is stored in the serial number */
+	memset(atm_dev->esi, 0, sizeof(atm_dev->esi));
+	if (usb_string(usb_dev, usb_dev->descriptor.iSerialNumber, mac_str, sizeof(mac_str)) == 12) {
+		for (i = 0; i < 6; i++)
+			atm_dev->esi[i] = (hex_to_bin(mac_str[i * 2]) << 4) +
+				hex_to_bin(mac_str[i * 2 + 1]);
+	}
+
+	/* Start modem synchronisation */
+	ret = speedtch_start_synchro(instance);
+
+	/* Set up interrupt endpoint */
+	if (instance->int_urb) {
+		ret = usb_submit_urb(instance->int_urb, GFP_KERNEL);
+		if (ret < 0) {
+			/* Doesn't matter; we'll poll anyway */
+			atm_dbg(usbatm, "%s: submission of interrupt URB failed (%d)!\n", __func__, ret);
+			usb_free_urb(instance->int_urb);
+			instance->int_urb = NULL;
+		}
+	}
+
+	/* Start status polling */
+	mod_timer(&instance->status_check_timer, jiffies + msecs_to_jiffies(1000));
+
+	return 0;
+}
+
+static void speedtch_atm_stop(struct usbatm_data *usbatm, struct atm_dev *atm_dev)
+{
+	struct speedtch_instance_data *instance = usbatm->driver_data;
+	struct urb *int_urb = instance->int_urb;
+
+	atm_dbg(usbatm, "%s entered\n", __func__);
+
+	del_timer_sync(&instance->status_check_timer);
+
+	/*
+	 * Since resubmit_timer and int_urb can schedule themselves and
+	 * each other, shutting them down correctly takes some care
+	 */
+	instance->int_urb = NULL; /* signal shutdown */
+	mb();
+	usb_kill_urb(int_urb);
+	del_timer_sync(&instance->resubmit_timer);
+	/*
+	 * At this point, speedtch_handle_int and speedtch_resubmit_int
+	 * can run or be running, but instance->int_urb == NULL means that
+	 * they will not reschedule
+	 */
+	usb_kill_urb(int_urb);
+	del_timer_sync(&instance->resubmit_timer);
+	usb_free_urb(int_urb);
+
+	flush_work(&instance->status_check_work);
+}
+
+static int speedtch_pre_reset(struct usb_interface *intf)
+{
+	return 0;
+}
+
+static int speedtch_post_reset(struct usb_interface *intf)
+{
+	return 0;
+}
+
+
+/**********
+**  USB  **
+**********/
+
+static const struct usb_device_id speedtch_usb_ids[] = {
+	{USB_DEVICE(0x06b9, 0x4061)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, speedtch_usb_ids);
+
+static int speedtch_usb_probe(struct usb_interface *, const struct usb_device_id *);
+
+static struct usb_driver speedtch_usb_driver = {
+	.name		= speedtch_driver_name,
+	.probe		= speedtch_usb_probe,
+	.disconnect	= usbatm_usb_disconnect,
+	.pre_reset	= speedtch_pre_reset,
+	.post_reset	= speedtch_post_reset,
+	.id_table	= speedtch_usb_ids
+};
+
+static void speedtch_release_interfaces(struct usb_device *usb_dev,
+					int num_interfaces)
+{
+	struct usb_interface *cur_intf;
+	int i;
+
+	for (i = 0; i < num_interfaces; i++) {
+		cur_intf = usb_ifnum_to_if(usb_dev, i);
+		if (cur_intf) {
+			usb_set_intfdata(cur_intf, NULL);
+			usb_driver_release_interface(&speedtch_usb_driver, cur_intf);
+		}
+	}
+}
+
+static int speedtch_bind(struct usbatm_data *usbatm,
+			 struct usb_interface *intf,
+			 const struct usb_device_id *id)
+{
+	struct usb_device *usb_dev = interface_to_usbdev(intf);
+	struct usb_interface *cur_intf, *data_intf;
+	struct speedtch_instance_data *instance;
+	int ifnum = intf->altsetting->desc.bInterfaceNumber;
+	int num_interfaces = usb_dev->actconfig->desc.bNumInterfaces;
+	int i, ret;
+	int use_isoc;
+
+	usb_dbg(usbatm, "%s entered\n", __func__);
+
+	/* sanity checks */
+
+	if (usb_dev->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) {
+		usb_err(usbatm, "%s: wrong device class %d\n", __func__, usb_dev->descriptor.bDeviceClass);
+		return -ENODEV;
+	}
+
+	data_intf = usb_ifnum_to_if(usb_dev, INTERFACE_DATA);
+	if (!data_intf) {
+		usb_err(usbatm, "%s: data interface not found!\n", __func__);
+		return -ENODEV;
+	}
+
+	/* claim all interfaces */
+
+	for (i = 0; i < num_interfaces; i++) {
+		cur_intf = usb_ifnum_to_if(usb_dev, i);
+
+		if ((i != ifnum) && cur_intf) {
+			ret = usb_driver_claim_interface(&speedtch_usb_driver, cur_intf, usbatm);
+
+			if (ret < 0) {
+				usb_err(usbatm, "%s: failed to claim interface %2d (%d)!\n", __func__, i, ret);
+				speedtch_release_interfaces(usb_dev, i);
+				return ret;
+			}
+		}
+	}
+
+	instance = kzalloc(sizeof(*instance), GFP_KERNEL);
+
+	if (!instance) {
+		ret = -ENOMEM;
+		goto fail_release;
+	}
+
+	instance->usbatm = usbatm;
+
+	/* module parameters may change at any moment, so take a snapshot */
+	instance->params.altsetting = altsetting;
+	instance->params.BMaxDSL = BMaxDSL;
+	instance->params.ModemMode = ModemMode;
+	memcpy(instance->params.ModemOption, DEFAULT_MODEM_OPTION, MODEM_OPTION_LENGTH);
+	memcpy(instance->params.ModemOption, ModemOption, num_ModemOption);
+	use_isoc = enable_isoc;
+
+	if (instance->params.altsetting)
+		if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, instance->params.altsetting)) < 0) {
+			usb_err(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, instance->params.altsetting, ret);
+			instance->params.altsetting = 0; /* fall back to default */
+		}
+
+	if (!instance->params.altsetting && use_isoc)
+		if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, DEFAULT_ISOC_ALTSETTING)) < 0) {
+			usb_dbg(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, DEFAULT_ISOC_ALTSETTING, ret);
+			use_isoc = 0; /* fall back to bulk */
+		}
+
+	if (use_isoc) {
+		const struct usb_host_interface *desc = data_intf->cur_altsetting;
+		const __u8 target_address = USB_DIR_IN | usbatm->driver->isoc_in;
+
+		use_isoc = 0; /* fall back to bulk if endpoint not found */
+
+		for (i = 0; i < desc->desc.bNumEndpoints; i++) {
+			const struct usb_endpoint_descriptor *endpoint_desc = &desc->endpoint[i].desc;
+
+			if ((endpoint_desc->bEndpointAddress == target_address)) {
+				use_isoc =
+					usb_endpoint_xfer_isoc(endpoint_desc);
+				break;
+			}
+		}
+
+		if (!use_isoc)
+			usb_info(usbatm, "isochronous transfer not supported - using bulk\n");
+	}
+
+	if (!use_isoc && !instance->params.altsetting)
+		if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, DEFAULT_BULK_ALTSETTING)) < 0) {
+			usb_err(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, DEFAULT_BULK_ALTSETTING, ret);
+			goto fail_free;
+		}
+
+	if (!instance->params.altsetting)
+		instance->params.altsetting = use_isoc ? DEFAULT_ISOC_ALTSETTING : DEFAULT_BULK_ALTSETTING;
+
+	usbatm->flags |= (use_isoc ? UDSL_USE_ISOC : 0);
+
+	INIT_WORK(&instance->status_check_work, speedtch_check_status);
+	timer_setup(&instance->status_check_timer, speedtch_status_poll, 0);
+	instance->last_status = 0xff;
+	instance->poll_delay = MIN_POLL_DELAY;
+
+	timer_setup(&instance->resubmit_timer, speedtch_resubmit_int, 0);
+
+	instance->int_urb = usb_alloc_urb(0, GFP_KERNEL);
+
+	if (instance->int_urb)
+		usb_fill_int_urb(instance->int_urb, usb_dev,
+				 usb_rcvintpipe(usb_dev, ENDPOINT_INT),
+				 instance->int_data, sizeof(instance->int_data),
+				 speedtch_handle_int, instance, 16);
+	else
+		usb_dbg(usbatm, "%s: no memory for interrupt urb!\n", __func__);
+
+	/* check whether the modem already seems to be alive */
+	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+			      0x12, 0xc0, 0x07, 0x00,
+			      instance->scratch_buffer + OFFSET_7, SIZE_7, 500);
+
+	usbatm->flags |= (ret == SIZE_7 ? UDSL_SKIP_HEAVY_INIT : 0);
+
+	usb_dbg(usbatm, "%s: firmware %s loaded\n", __func__, usbatm->flags & UDSL_SKIP_HEAVY_INIT ? "already" : "not");
+
+	if (!(usbatm->flags & UDSL_SKIP_HEAVY_INIT))
+		if ((ret = usb_reset_device(usb_dev)) < 0) {
+			usb_err(usbatm, "%s: device reset failed (%d)!\n", __func__, ret);
+			goto fail_free;
+		}
+
+        usbatm->driver_data = instance;
+
+	return 0;
+
+fail_free:
+	usb_free_urb(instance->int_urb);
+	kfree(instance);
+fail_release:
+	speedtch_release_interfaces(usb_dev, num_interfaces);
+	return ret;
+}
+
+static void speedtch_unbind(struct usbatm_data *usbatm, struct usb_interface *intf)
+{
+	struct usb_device *usb_dev = interface_to_usbdev(intf);
+	struct speedtch_instance_data *instance = usbatm->driver_data;
+
+	usb_dbg(usbatm, "%s entered\n", __func__);
+
+	speedtch_release_interfaces(usb_dev, usb_dev->actconfig->desc.bNumInterfaces);
+	usb_free_urb(instance->int_urb);
+	kfree(instance);
+}
+
+
+/***********
+**  init  **
+***********/
+
+static struct usbatm_driver speedtch_usbatm_driver = {
+	.driver_name	= speedtch_driver_name,
+	.bind		= speedtch_bind,
+	.heavy_init	= speedtch_heavy_init,
+	.unbind		= speedtch_unbind,
+	.atm_start	= speedtch_atm_start,
+	.atm_stop	= speedtch_atm_stop,
+	.bulk_in	= ENDPOINT_BULK_DATA,
+	.bulk_out	= ENDPOINT_BULK_DATA,
+	.isoc_in	= ENDPOINT_ISOC_DATA
+};
+
+static int speedtch_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	return usbatm_usb_probe(intf, id, &speedtch_usbatm_driver);
+}
+
+module_usb_driver(speedtch_usb_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c
new file mode 100644
index 0000000..2754b4c
--- /dev/null
+++ b/drivers/usb/atm/ueagle-atm.c
@@ -0,0 +1,2801 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*-
+ * Copyright (c) 2003, 2004
+ *	Damien Bergamini <damien.bergamini@free.fr>. All rights reserved.
+ *
+ * Copyright (c) 2005-2007 Matthieu Castet <castet.matthieu@free.fr>
+ * Copyright (c) 2005-2007 Stanislaw Gruszka <stf_xl@wp.pl>
+ *
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose to be licensed under the terms of the GNU
+ * General Public License (GPL) Version 2, available from the file
+ * COPYING in the main directory of this source tree, or the
+ * BSD license below:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice unmodified, this list of conditions, and the following
+ *    disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * GPL license :
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ *
+ * HISTORY : some part of the code was base on ueagle 1.3 BSD driver,
+ * Damien Bergamini agree to put his code under a DUAL GPL/BSD license.
+ *
+ * The rest of the code was was rewritten from scratch.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/crc32.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <linux/ctype.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/mutex.h>
+#include <linux/freezer.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+
+#include <asm/unaligned.h>
+
+#include "usbatm.h"
+
+#define EAGLEUSBVERSION "ueagle 1.4"
+
+
+/*
+ * Debug macros
+ */
+#define uea_dbg(usb_dev, format, args...)	\
+	do { \
+		if (debug >= 1) \
+			dev_dbg(&(usb_dev)->dev, \
+				"[ueagle-atm dbg] %s: " format, \
+					__func__, ##args); \
+	} while (0)
+
+#define uea_vdbg(usb_dev, format, args...)	\
+	do { \
+		if (debug >= 2) \
+			dev_dbg(&(usb_dev)->dev, \
+				"[ueagle-atm vdbg]  " format, ##args); \
+	} while (0)
+
+#define uea_enters(usb_dev) \
+	uea_vdbg(usb_dev, "entering %s\n" , __func__)
+
+#define uea_leaves(usb_dev) \
+	uea_vdbg(usb_dev, "leaving  %s\n" , __func__)
+
+#define uea_err(usb_dev, format, args...) \
+	dev_err(&(usb_dev)->dev , "[UEAGLE-ATM] " format , ##args)
+
+#define uea_warn(usb_dev, format, args...) \
+	dev_warn(&(usb_dev)->dev , "[Ueagle-atm] " format, ##args)
+
+#define uea_info(usb_dev, format, args...) \
+	dev_info(&(usb_dev)->dev , "[ueagle-atm] " format, ##args)
+
+struct intr_pkt;
+
+/* cmv's from firmware */
+struct uea_cmvs_v1 {
+	u32 address;
+	u16 offset;
+	u32 data;
+} __packed;
+
+struct uea_cmvs_v2 {
+	u32 group;
+	u32 address;
+	u32 offset;
+	u32 data;
+} __packed;
+
+/* information about currently processed cmv */
+struct cmv_dsc_e1 {
+	u8 function;
+	u16 idx;
+	u32 address;
+	u16 offset;
+};
+
+struct cmv_dsc_e4 {
+	u16 function;
+	u16 offset;
+	u16 address;
+	u16 group;
+};
+
+union cmv_dsc {
+	struct cmv_dsc_e1 e1;
+	struct cmv_dsc_e4 e4;
+};
+
+struct uea_softc {
+	struct usb_device *usb_dev;
+	struct usbatm_data *usbatm;
+
+	int modem_index;
+	unsigned int driver_info;
+	int annex;
+#define ANNEXA 0
+#define ANNEXB 1
+
+	int booting;
+	int reset;
+
+	wait_queue_head_t sync_q;
+
+	struct task_struct *kthread;
+	u32 data;
+	u32 data1;
+
+	int cmv_ack;
+	union cmv_dsc cmv_dsc;
+
+	struct work_struct task;
+	u16 pageno;
+	u16 ovl;
+
+	const struct firmware *dsp_firm;
+	struct urb *urb_int;
+
+	void (*dispatch_cmv)(struct uea_softc *, struct intr_pkt *);
+	void (*schedule_load_page)(struct uea_softc *, struct intr_pkt *);
+	int (*stat)(struct uea_softc *);
+	int (*send_cmvs)(struct uea_softc *);
+
+	/* keep in sync with eaglectl */
+	struct uea_stats {
+		struct {
+			u32 state;
+			u32 flags;
+			u32 mflags;
+			u32 vidcpe;
+			u32 vidco;
+			u32 dsrate;
+			u32 usrate;
+			u32 dsunc;
+			u32 usunc;
+			u32 dscorr;
+			u32 uscorr;
+			u32 txflow;
+			u32 rxflow;
+			u32 usattenuation;
+			u32 dsattenuation;
+			u32 dsmargin;
+			u32 usmargin;
+			u32 firmid;
+		} phy;
+	} stats;
+};
+
+/*
+ * Elsa IDs
+ */
+#define ELSA_VID		0x05CC
+#define ELSA_PID_PSTFIRM	0x3350
+#define ELSA_PID_PREFIRM	0x3351
+
+#define ELSA_PID_A_PREFIRM	0x3352
+#define ELSA_PID_A_PSTFIRM	0x3353
+#define ELSA_PID_B_PREFIRM	0x3362
+#define ELSA_PID_B_PSTFIRM	0x3363
+
+/*
+ * Devolo IDs : pots if (pid & 0x10)
+ */
+#define DEVOLO_VID			0x1039
+#define DEVOLO_EAGLE_I_A_PID_PSTFIRM	0x2110
+#define DEVOLO_EAGLE_I_A_PID_PREFIRM	0x2111
+
+#define DEVOLO_EAGLE_I_B_PID_PSTFIRM	0x2100
+#define DEVOLO_EAGLE_I_B_PID_PREFIRM	0x2101
+
+#define DEVOLO_EAGLE_II_A_PID_PSTFIRM	0x2130
+#define DEVOLO_EAGLE_II_A_PID_PREFIRM	0x2131
+
+#define DEVOLO_EAGLE_II_B_PID_PSTFIRM	0x2120
+#define DEVOLO_EAGLE_II_B_PID_PREFIRM	0x2121
+
+/*
+ * Reference design USB IDs
+ */
+#define ANALOG_VID		0x1110
+#define ADI930_PID_PREFIRM	0x9001
+#define ADI930_PID_PSTFIRM	0x9000
+
+#define EAGLE_I_PID_PREFIRM	0x9010	/* Eagle I */
+#define EAGLE_I_PID_PSTFIRM	0x900F	/* Eagle I */
+
+#define EAGLE_IIC_PID_PREFIRM	0x9024	/* Eagle IIC */
+#define EAGLE_IIC_PID_PSTFIRM	0x9023	/* Eagle IIC */
+
+#define EAGLE_II_PID_PREFIRM	0x9022	/* Eagle II */
+#define EAGLE_II_PID_PSTFIRM	0x9021	/* Eagle II */
+
+#define EAGLE_III_PID_PREFIRM	0x9032	/* Eagle III */
+#define EAGLE_III_PID_PSTFIRM	0x9031	/* Eagle III */
+
+#define EAGLE_IV_PID_PREFIRM	0x9042  /* Eagle IV */
+#define EAGLE_IV_PID_PSTFIRM	0x9041  /* Eagle IV */
+
+/*
+ * USR USB IDs
+ */
+#define USR_VID			0x0BAF
+#define MILLER_A_PID_PREFIRM	0x00F2
+#define MILLER_A_PID_PSTFIRM	0x00F1
+#define MILLER_B_PID_PREFIRM	0x00FA
+#define MILLER_B_PID_PSTFIRM	0x00F9
+#define HEINEKEN_A_PID_PREFIRM	0x00F6
+#define HEINEKEN_A_PID_PSTFIRM	0x00F5
+#define HEINEKEN_B_PID_PREFIRM	0x00F8
+#define HEINEKEN_B_PID_PSTFIRM	0x00F7
+
+#define PREFIRM 0
+#define PSTFIRM (1<<7)
+#define AUTO_ANNEX_A (1<<8)
+#define AUTO_ANNEX_B (1<<9)
+
+enum {
+	ADI930 = 0,
+	EAGLE_I,
+	EAGLE_II,
+	EAGLE_III,
+	EAGLE_IV
+};
+
+/* macros for both struct usb_device_id and struct uea_softc */
+#define UEA_IS_PREFIRM(x) \
+	(!((x)->driver_info & PSTFIRM))
+#define UEA_CHIP_VERSION(x) \
+	((x)->driver_info & 0xf)
+
+#define IS_ISDN(x) \
+	((x)->annex & ANNEXB)
+
+#define INS_TO_USBDEV(ins) (ins->usb_dev)
+
+#define GET_STATUS(data) \
+	((data >> 8) & 0xf)
+
+#define IS_OPERATIONAL(sc) \
+	((UEA_CHIP_VERSION(sc) != EAGLE_IV) ? \
+	(GET_STATUS(sc->stats.phy.state) == 2) : \
+	(sc->stats.phy.state == 7))
+
+/*
+ * Set of macros to handle unaligned data in the firmware blob.
+ * The FW_GET_BYTE() macro is provided only for consistency.
+ */
+
+#define FW_GET_BYTE(p) (*((__u8 *) (p)))
+
+#define FW_DIR "ueagle-atm/"
+#define EAGLE_FIRMWARE FW_DIR "eagle.fw"
+#define ADI930_FIRMWARE FW_DIR "adi930.fw"
+#define EAGLE_I_FIRMWARE FW_DIR "eagleI.fw"
+#define EAGLE_II_FIRMWARE FW_DIR "eagleII.fw"
+#define EAGLE_III_FIRMWARE FW_DIR "eagleIII.fw"
+#define EAGLE_IV_FIRMWARE FW_DIR "eagleIV.fw"
+
+#define DSP4I_FIRMWARE FW_DIR "DSP4i.bin"
+#define DSP4P_FIRMWARE FW_DIR "DSP4p.bin"
+#define DSP9I_FIRMWARE FW_DIR "DSP9i.bin"
+#define DSP9P_FIRMWARE FW_DIR "DSP9p.bin"
+#define DSPEI_FIRMWARE FW_DIR "DSPei.bin"
+#define DSPEP_FIRMWARE FW_DIR "DSPep.bin"
+#define FPGA930_FIRMWARE FW_DIR "930-fpga.bin"
+
+#define CMV4P_FIRMWARE FW_DIR "CMV4p.bin"
+#define CMV4PV2_FIRMWARE FW_DIR "CMV4p.bin.v2"
+#define CMV4I_FIRMWARE FW_DIR "CMV4i.bin"
+#define CMV4IV2_FIRMWARE FW_DIR "CMV4i.bin.v2"
+#define CMV9P_FIRMWARE FW_DIR "CMV9p.bin"
+#define CMV9PV2_FIRMWARE FW_DIR "CMV9p.bin.v2"
+#define CMV9I_FIRMWARE FW_DIR "CMV9i.bin"
+#define CMV9IV2_FIRMWARE FW_DIR "CMV9i.bin.v2"
+#define CMVEP_FIRMWARE FW_DIR "CMVep.bin"
+#define CMVEPV2_FIRMWARE FW_DIR "CMVep.bin.v2"
+#define CMVEI_FIRMWARE FW_DIR "CMVei.bin"
+#define CMVEIV2_FIRMWARE FW_DIR "CMVei.bin.v2"
+
+#define UEA_FW_NAME_MAX 30
+#define NB_MODEM 4
+
+#define BULK_TIMEOUT 300
+#define CTRL_TIMEOUT 1000
+
+#define ACK_TIMEOUT msecs_to_jiffies(3000)
+
+#define UEA_INTR_IFACE_NO	0
+#define UEA_US_IFACE_NO		1
+#define UEA_DS_IFACE_NO		2
+
+#define FASTEST_ISO_INTF	8
+
+#define UEA_BULK_DATA_PIPE	0x02
+#define UEA_IDMA_PIPE		0x04
+#define UEA_INTR_PIPE		0x04
+#define UEA_ISO_DATA_PIPE	0x08
+
+#define UEA_E1_SET_BLOCK	0x0001
+#define UEA_E4_SET_BLOCK	0x002c
+#define UEA_SET_MODE		0x0003
+#define UEA_SET_2183_DATA	0x0004
+#define UEA_SET_TIMEOUT		0x0011
+
+#define UEA_LOOPBACK_OFF	0x0002
+#define UEA_LOOPBACK_ON		0x0003
+#define UEA_BOOT_IDMA		0x0006
+#define UEA_START_RESET		0x0007
+#define UEA_END_RESET		0x0008
+
+#define UEA_SWAP_MAILBOX	(0x3fcd | 0x4000)
+#define UEA_MPTX_START		(0x3fce | 0x4000)
+#define UEA_MPTX_MAILBOX	(0x3fd6 | 0x4000)
+#define UEA_MPRX_MAILBOX	(0x3fdf | 0x4000)
+
+/* block information in eagle4 dsp firmware  */
+struct block_index {
+	__le32 PageOffset;
+	__le32 NotLastBlock;
+	__le32 dummy;
+	__le32 PageSize;
+	__le32 PageAddress;
+	__le16 dummy1;
+	__le16 PageNumber;
+} __packed;
+
+#define E4_IS_BOOT_PAGE(PageSize) ((le32_to_cpu(PageSize)) & 0x80000000)
+#define E4_PAGE_BYTES(PageSize) ((le32_to_cpu(PageSize) & 0x7fffffff) * 4)
+
+#define E4_L1_STRING_HEADER 0x10
+#define E4_MAX_PAGE_NUMBER 0x58
+#define E4_NO_SWAPPAGE_HEADERS 0x31
+
+/* l1_code is eagle4 dsp firmware format */
+struct l1_code {
+	u8 string_header[E4_L1_STRING_HEADER];
+	u8 page_number_to_block_index[E4_MAX_PAGE_NUMBER];
+	struct block_index page_header[E4_NO_SWAPPAGE_HEADERS];
+	u8 code[0];
+} __packed;
+
+/* structures describing a block within a DSP page */
+struct block_info_e1 {
+	__le16 wHdr;
+	__le16 wAddress;
+	__le16 wSize;
+	__le16 wOvlOffset;
+	__le16 wOvl;		/* overlay */
+	__le16 wLast;
+} __packed;
+#define E1_BLOCK_INFO_SIZE 12
+
+struct block_info_e4 {
+	__be16 wHdr;
+	__u8 bBootPage;
+	__u8 bPageNumber;
+	__be32 dwSize;
+	__be32 dwAddress;
+	__be16 wReserved;
+} __packed;
+#define E4_BLOCK_INFO_SIZE 14
+
+#define UEA_BIHDR 0xabcd
+#define UEA_RESERVED 0xffff
+
+/* constants describing cmv type */
+#define E1_PREAMBLE 0x535c
+#define E1_MODEMTOHOST 0x01
+#define E1_HOSTTOMODEM 0x10
+
+#define E1_MEMACCESS 0x1
+#define E1_ADSLDIRECTIVE 0x7
+#define E1_FUNCTION_TYPE(f) ((f) >> 4)
+#define E1_FUNCTION_SUBTYPE(f) ((f) & 0x0f)
+
+#define E4_MEMACCESS 0
+#define E4_ADSLDIRECTIVE 0xf
+#define E4_FUNCTION_TYPE(f) ((f) >> 8)
+#define E4_FUNCTION_SIZE(f) ((f) & 0x0f)
+#define E4_FUNCTION_SUBTYPE(f) (((f) >> 4) & 0x0f)
+
+/* for MEMACCESS */
+#define E1_REQUESTREAD	0x0
+#define E1_REQUESTWRITE	0x1
+#define E1_REPLYREAD	0x2
+#define E1_REPLYWRITE	0x3
+
+#define E4_REQUESTREAD	0x0
+#define E4_REQUESTWRITE	0x4
+#define E4_REPLYREAD	(E4_REQUESTREAD | 1)
+#define E4_REPLYWRITE	(E4_REQUESTWRITE | 1)
+
+/* for ADSLDIRECTIVE */
+#define E1_KERNELREADY 0x0
+#define E1_MODEMREADY  0x1
+
+#define E4_KERNELREADY 0x0
+#define E4_MODEMREADY  0x1
+
+#define E1_MAKEFUNCTION(t, s) (((t) & 0xf) << 4 | ((s) & 0xf))
+#define E4_MAKEFUNCTION(t, st, s) (((t) & 0xf) << 8 | \
+	((st) & 0xf) << 4 | ((s) & 0xf))
+
+#define E1_MAKESA(a, b, c, d)						\
+	(((c) & 0xff) << 24 |						\
+	 ((d) & 0xff) << 16 |						\
+	 ((a) & 0xff) << 8  |						\
+	 ((b) & 0xff))
+
+#define E1_GETSA1(a) ((a >> 8) & 0xff)
+#define E1_GETSA2(a) (a & 0xff)
+#define E1_GETSA3(a) ((a >> 24) & 0xff)
+#define E1_GETSA4(a) ((a >> 16) & 0xff)
+
+#define E1_SA_CNTL E1_MAKESA('C', 'N', 'T', 'L')
+#define E1_SA_DIAG E1_MAKESA('D', 'I', 'A', 'G')
+#define E1_SA_INFO E1_MAKESA('I', 'N', 'F', 'O')
+#define E1_SA_OPTN E1_MAKESA('O', 'P', 'T', 'N')
+#define E1_SA_RATE E1_MAKESA('R', 'A', 'T', 'E')
+#define E1_SA_STAT E1_MAKESA('S', 'T', 'A', 'T')
+
+#define E4_SA_CNTL 1
+#define E4_SA_STAT 2
+#define E4_SA_INFO 3
+#define E4_SA_TEST 4
+#define E4_SA_OPTN 5
+#define E4_SA_RATE 6
+#define E4_SA_DIAG 7
+#define E4_SA_CNFG 8
+
+/* structures representing a CMV (Configuration and Management Variable) */
+struct cmv_e1 {
+	__le16 wPreamble;
+	__u8 bDirection;
+	__u8 bFunction;
+	__le16 wIndex;
+	__le32 dwSymbolicAddress;
+	__le16 wOffsetAddress;
+	__le32 dwData;
+} __packed;
+
+struct cmv_e4 {
+	__be16 wGroup;
+	__be16 wFunction;
+	__be16 wOffset;
+	__be16 wAddress;
+	__be32 dwData[6];
+} __packed;
+
+/* structures representing swap information */
+struct swap_info_e1 {
+	__u8 bSwapPageNo;
+	__u8 bOvl;		/* overlay */
+} __packed;
+
+struct swap_info_e4 {
+	__u8 bSwapPageNo;
+} __packed;
+
+/* structures representing interrupt data */
+#define e1_bSwapPageNo	u.e1.s1.swapinfo.bSwapPageNo
+#define e1_bOvl		u.e1.s1.swapinfo.bOvl
+#define e4_bSwapPageNo  u.e4.s1.swapinfo.bSwapPageNo
+
+#define INT_LOADSWAPPAGE 0x0001
+#define INT_INCOMINGCMV  0x0002
+
+union intr_data_e1 {
+	struct {
+		struct swap_info_e1 swapinfo;
+		__le16 wDataSize;
+	} __packed s1;
+	struct {
+		struct cmv_e1 cmv;
+		__le16 wDataSize;
+	} __packed s2;
+} __packed;
+
+union intr_data_e4 {
+	struct {
+		struct swap_info_e4 swapinfo;
+		__le16 wDataSize;
+	} __packed s1;
+	struct {
+		struct cmv_e4 cmv;
+		__le16 wDataSize;
+	} __packed s2;
+} __packed;
+
+struct intr_pkt {
+	__u8 bType;
+	__u8 bNotification;
+	__le16 wValue;
+	__le16 wIndex;
+	__le16 wLength;
+	__le16 wInterrupt;
+	union {
+		union intr_data_e1 e1;
+		union intr_data_e4 e4;
+	} u;
+} __packed;
+
+#define E1_INTR_PKT_SIZE 28
+#define E4_INTR_PKT_SIZE 64
+
+static struct usb_driver uea_driver;
+static DEFINE_MUTEX(uea_mutex);
+static const char * const chip_name[] = {
+	"ADI930", "Eagle I", "Eagle II", "Eagle III", "Eagle IV"};
+
+static int modem_index;
+static unsigned int debug;
+static unsigned int altsetting[NB_MODEM] = {
+				[0 ... (NB_MODEM - 1)] = FASTEST_ISO_INTF};
+static bool sync_wait[NB_MODEM];
+static char *cmv_file[NB_MODEM];
+static int annex[NB_MODEM];
+
+module_param(debug, uint, 0644);
+MODULE_PARM_DESC(debug, "module debug level (0=off,1=on,2=verbose)");
+module_param_array(altsetting, uint, NULL, 0644);
+MODULE_PARM_DESC(altsetting, "alternate setting for incoming traffic: 0=bulk, "
+			     "1=isoc slowest, ... , 8=isoc fastest (default)");
+module_param_array(sync_wait, bool, NULL, 0644);
+MODULE_PARM_DESC(sync_wait, "wait the synchronisation before starting ATM");
+module_param_array(cmv_file, charp, NULL, 0644);
+MODULE_PARM_DESC(cmv_file,
+		"file name with configuration and management variables");
+module_param_array(annex, uint, NULL, 0644);
+MODULE_PARM_DESC(annex,
+		"manually set annex a/b (0=auto, 1=annex a, 2=annex b)");
+
+#define uea_wait(sc, cond, timeo) \
+({ \
+	int _r = wait_event_interruptible_timeout(sc->sync_q, \
+			(cond) || kthread_should_stop(), timeo); \
+	if (kthread_should_stop()) \
+		_r = -ENODEV; \
+	_r; \
+})
+
+#define UPDATE_ATM_STAT(type, val) \
+	do { \
+		if (sc->usbatm->atm_dev) \
+			sc->usbatm->atm_dev->type = val; \
+	} while (0)
+
+#define UPDATE_ATM_SIGNAL(val) \
+	do { \
+		if (sc->usbatm->atm_dev) \
+			atm_dev_signal_change(sc->usbatm->atm_dev, val); \
+	} while (0)
+
+
+/* Firmware loading */
+#define LOAD_INTERNAL     0xA0
+#define F8051_USBCS       0x7f92
+
+/**
+ * uea_send_modem_cmd - Send a command for pre-firmware devices.
+ */
+static int uea_send_modem_cmd(struct usb_device *usb,
+			      u16 addr, u16 size, const u8 *buff)
+{
+	int ret = -ENOMEM;
+	u8 *xfer_buff;
+
+	xfer_buff = kmemdup(buff, size, GFP_KERNEL);
+	if (xfer_buff) {
+		ret = usb_control_msg(usb,
+				      usb_sndctrlpipe(usb, 0),
+				      LOAD_INTERNAL,
+				      USB_DIR_OUT | USB_TYPE_VENDOR |
+				      USB_RECIP_DEVICE, addr, 0, xfer_buff,
+				      size, CTRL_TIMEOUT);
+		kfree(xfer_buff);
+	}
+
+	if (ret < 0)
+		return ret;
+
+	return (ret == size) ? 0 : -EIO;
+}
+
+static void uea_upload_pre_firmware(const struct firmware *fw_entry,
+								void *context)
+{
+	struct usb_device *usb = context;
+	const u8 *pfw;
+	u8 value;
+	u32 crc = 0;
+	int ret, size;
+
+	uea_enters(usb);
+	if (!fw_entry) {
+		uea_err(usb, "firmware is not available\n");
+		goto err;
+	}
+
+	pfw = fw_entry->data;
+	size = fw_entry->size;
+	if (size < 4)
+		goto err_fw_corrupted;
+
+	crc = get_unaligned_le32(pfw);
+	pfw += 4;
+	size -= 4;
+	if (crc32_be(0, pfw, size) != crc)
+		goto err_fw_corrupted;
+
+	/*
+	 * Start to upload firmware : send reset
+	 */
+	value = 1;
+	ret = uea_send_modem_cmd(usb, F8051_USBCS, sizeof(value), &value);
+
+	if (ret < 0) {
+		uea_err(usb, "modem reset failed with error %d\n", ret);
+		goto err;
+	}
+
+	while (size > 3) {
+		u8 len = FW_GET_BYTE(pfw);
+		u16 add = get_unaligned_le16(pfw + 1);
+
+		size -= len + 3;
+		if (size < 0)
+			goto err_fw_corrupted;
+
+		ret = uea_send_modem_cmd(usb, add, len, pfw + 3);
+		if (ret < 0) {
+			uea_err(usb, "uploading firmware data failed "
+					"with error %d\n", ret);
+			goto err;
+		}
+		pfw += len + 3;
+	}
+
+	if (size != 0)
+		goto err_fw_corrupted;
+
+	/*
+	 * Tell the modem we finish : de-assert reset
+	 */
+	value = 0;
+	ret = uea_send_modem_cmd(usb, F8051_USBCS, 1, &value);
+	if (ret < 0)
+		uea_err(usb, "modem de-assert failed with error %d\n", ret);
+	else
+		uea_info(usb, "firmware uploaded\n");
+
+	goto err;
+
+err_fw_corrupted:
+	uea_err(usb, "firmware is corrupted\n");
+err:
+	release_firmware(fw_entry);
+	uea_leaves(usb);
+}
+
+/**
+ * uea_load_firmware - Load usb firmware for pre-firmware devices.
+ */
+static int uea_load_firmware(struct usb_device *usb, unsigned int ver)
+{
+	int ret;
+	char *fw_name = EAGLE_FIRMWARE;
+
+	uea_enters(usb);
+	uea_info(usb, "pre-firmware device, uploading firmware\n");
+
+	switch (ver) {
+	case ADI930:
+		fw_name = ADI930_FIRMWARE;
+		break;
+	case EAGLE_I:
+		fw_name = EAGLE_I_FIRMWARE;
+		break;
+	case EAGLE_II:
+		fw_name = EAGLE_II_FIRMWARE;
+		break;
+	case EAGLE_III:
+		fw_name = EAGLE_III_FIRMWARE;
+		break;
+	case EAGLE_IV:
+		fw_name = EAGLE_IV_FIRMWARE;
+		break;
+	}
+
+	ret = request_firmware_nowait(THIS_MODULE, 1, fw_name, &usb->dev,
+					GFP_KERNEL, usb,
+					uea_upload_pre_firmware);
+	if (ret)
+		uea_err(usb, "firmware %s is not available\n", fw_name);
+	else
+		uea_info(usb, "loading firmware %s\n", fw_name);
+
+	uea_leaves(usb);
+	return ret;
+}
+
+/* modem management : dsp firmware, send/read CMV, monitoring statistic
+ */
+
+/*
+ * Make sure that the DSP code provided is safe to use.
+ */
+static int check_dsp_e1(const u8 *dsp, unsigned int len)
+{
+	u8 pagecount, blockcount;
+	u16 blocksize;
+	u32 pageoffset;
+	unsigned int i, j, p, pp;
+
+	pagecount = FW_GET_BYTE(dsp);
+	p = 1;
+
+	/* enough space for page offsets? */
+	if (p + 4 * pagecount > len)
+		return 1;
+
+	for (i = 0; i < pagecount; i++) {
+
+		pageoffset = get_unaligned_le32(dsp + p);
+		p += 4;
+
+		if (pageoffset == 0)
+			continue;
+
+		/* enough space for blockcount? */
+		if (pageoffset >= len)
+			return 1;
+
+		pp = pageoffset;
+		blockcount = FW_GET_BYTE(dsp + pp);
+		pp += 1;
+
+		for (j = 0; j < blockcount; j++) {
+
+			/* enough space for block header? */
+			if (pp + 4 > len)
+				return 1;
+
+			pp += 2;	/* skip blockaddr */
+			blocksize = get_unaligned_le16(dsp + pp);
+			pp += 2;
+
+			/* enough space for block data? */
+			if (pp + blocksize > len)
+				return 1;
+
+			pp += blocksize;
+		}
+	}
+
+	return 0;
+}
+
+static int check_dsp_e4(const u8 *dsp, int len)
+{
+	int i;
+	struct l1_code *p = (struct l1_code *) dsp;
+	unsigned int sum = p->code - dsp;
+
+	if (len < sum)
+		return 1;
+
+	if (strcmp("STRATIPHY ANEXA", p->string_header) != 0 &&
+	    strcmp("STRATIPHY ANEXB", p->string_header) != 0)
+		return 1;
+
+	for (i = 0; i < E4_MAX_PAGE_NUMBER; i++) {
+		struct block_index *blockidx;
+		u8 blockno = p->page_number_to_block_index[i];
+		if (blockno >= E4_NO_SWAPPAGE_HEADERS)
+			continue;
+
+		do {
+			u64 l;
+
+			if (blockno >= E4_NO_SWAPPAGE_HEADERS)
+				return 1;
+
+			blockidx = &p->page_header[blockno++];
+			if ((u8 *)(blockidx + 1) - dsp  >= len)
+				return 1;
+
+			if (le16_to_cpu(blockidx->PageNumber) != i)
+				return 1;
+
+			l = E4_PAGE_BYTES(blockidx->PageSize);
+			sum += l;
+			l += le32_to_cpu(blockidx->PageOffset);
+			if (l > len)
+				return 1;
+
+		/* zero is zero regardless endianes */
+		} while (blockidx->NotLastBlock);
+	}
+
+	return (sum == len) ? 0 : 1;
+}
+
+/*
+ * send data to the idma pipe
+ * */
+static int uea_idma_write(struct uea_softc *sc, const void *data, u32 size)
+{
+	int ret = -ENOMEM;
+	u8 *xfer_buff;
+	int bytes_read;
+
+	xfer_buff = kmemdup(data, size, GFP_KERNEL);
+	if (!xfer_buff) {
+		uea_err(INS_TO_USBDEV(sc), "can't allocate xfer_buff\n");
+		return ret;
+	}
+
+	ret = usb_bulk_msg(sc->usb_dev,
+			 usb_sndbulkpipe(sc->usb_dev, UEA_IDMA_PIPE),
+			 xfer_buff, size, &bytes_read, BULK_TIMEOUT);
+
+	kfree(xfer_buff);
+	if (ret < 0)
+		return ret;
+	if (size != bytes_read) {
+		uea_err(INS_TO_USBDEV(sc), "size != bytes_read %d %d\n", size,
+		       bytes_read);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int request_dsp(struct uea_softc *sc)
+{
+	int ret;
+	char *dsp_name;
+
+	if (UEA_CHIP_VERSION(sc) == EAGLE_IV) {
+		if (IS_ISDN(sc))
+			dsp_name = DSP4I_FIRMWARE;
+		else
+			dsp_name = DSP4P_FIRMWARE;
+	} else if (UEA_CHIP_VERSION(sc) == ADI930) {
+		if (IS_ISDN(sc))
+			dsp_name = DSP9I_FIRMWARE;
+		else
+			dsp_name = DSP9P_FIRMWARE;
+	} else {
+		if (IS_ISDN(sc))
+			dsp_name = DSPEI_FIRMWARE;
+		else
+			dsp_name = DSPEP_FIRMWARE;
+	}
+
+	ret = request_firmware(&sc->dsp_firm, dsp_name, &sc->usb_dev->dev);
+	if (ret < 0) {
+		uea_err(INS_TO_USBDEV(sc),
+		       "requesting firmware %s failed with error %d\n",
+			dsp_name, ret);
+		return ret;
+	}
+
+	if (UEA_CHIP_VERSION(sc) == EAGLE_IV)
+		ret = check_dsp_e4(sc->dsp_firm->data, sc->dsp_firm->size);
+	else
+		ret = check_dsp_e1(sc->dsp_firm->data, sc->dsp_firm->size);
+
+	if (ret) {
+		uea_err(INS_TO_USBDEV(sc), "firmware %s is corrupted\n",
+		       dsp_name);
+		release_firmware(sc->dsp_firm);
+		sc->dsp_firm = NULL;
+		return -EILSEQ;
+	}
+
+	return 0;
+}
+
+/*
+ * The uea_load_page() function must be called within a process context
+ */
+static void uea_load_page_e1(struct work_struct *work)
+{
+	struct uea_softc *sc = container_of(work, struct uea_softc, task);
+	u16 pageno = sc->pageno;
+	u16 ovl = sc->ovl;
+	struct block_info_e1 bi;
+
+	const u8 *p;
+	u8 pagecount, blockcount;
+	u16 blockaddr, blocksize;
+	u32 pageoffset;
+	int i;
+
+	/* reload firmware when reboot start and it's loaded already */
+	if (ovl == 0 && pageno == 0) {
+		release_firmware(sc->dsp_firm);
+		sc->dsp_firm = NULL;
+	}
+
+	if (sc->dsp_firm == NULL && request_dsp(sc) < 0)
+		return;
+
+	p = sc->dsp_firm->data;
+	pagecount = FW_GET_BYTE(p);
+	p += 1;
+
+	if (pageno >= pagecount)
+		goto bad1;
+
+	p += 4 * pageno;
+	pageoffset = get_unaligned_le32(p);
+
+	if (pageoffset == 0)
+		goto bad1;
+
+	p = sc->dsp_firm->data + pageoffset;
+	blockcount = FW_GET_BYTE(p);
+	p += 1;
+
+	uea_dbg(INS_TO_USBDEV(sc),
+	       "sending %u blocks for DSP page %u\n", blockcount, pageno);
+
+	bi.wHdr = cpu_to_le16(UEA_BIHDR);
+	bi.wOvl = cpu_to_le16(ovl);
+	bi.wOvlOffset = cpu_to_le16(ovl | 0x8000);
+
+	for (i = 0; i < blockcount; i++) {
+		blockaddr = get_unaligned_le16(p);
+		p += 2;
+
+		blocksize = get_unaligned_le16(p);
+		p += 2;
+
+		bi.wSize = cpu_to_le16(blocksize);
+		bi.wAddress = cpu_to_le16(blockaddr);
+		bi.wLast = cpu_to_le16((i == blockcount - 1) ? 1 : 0);
+
+		/* send block info through the IDMA pipe */
+		if (uea_idma_write(sc, &bi, E1_BLOCK_INFO_SIZE))
+			goto bad2;
+
+		/* send block data through the IDMA pipe */
+		if (uea_idma_write(sc, p, blocksize))
+			goto bad2;
+
+		p += blocksize;
+	}
+
+	return;
+
+bad2:
+	uea_err(INS_TO_USBDEV(sc), "sending DSP block %u failed\n", i);
+	return;
+bad1:
+	uea_err(INS_TO_USBDEV(sc), "invalid DSP page %u requested\n", pageno);
+}
+
+static void __uea_load_page_e4(struct uea_softc *sc, u8 pageno, int boot)
+{
+	struct block_info_e4 bi;
+	struct block_index *blockidx;
+	struct l1_code *p = (struct l1_code *) sc->dsp_firm->data;
+	u8 blockno = p->page_number_to_block_index[pageno];
+
+	bi.wHdr = cpu_to_be16(UEA_BIHDR);
+	bi.bBootPage = boot;
+	bi.bPageNumber = pageno;
+	bi.wReserved = cpu_to_be16(UEA_RESERVED);
+
+	do {
+		const u8 *blockoffset;
+		unsigned int blocksize;
+
+		blockidx = &p->page_header[blockno];
+		blocksize = E4_PAGE_BYTES(blockidx->PageSize);
+		blockoffset = sc->dsp_firm->data + le32_to_cpu(
+							blockidx->PageOffset);
+
+		bi.dwSize = cpu_to_be32(blocksize);
+		bi.dwAddress = cpu_to_be32(le32_to_cpu(blockidx->PageAddress));
+
+		uea_dbg(INS_TO_USBDEV(sc),
+			"sending block %u for DSP page "
+			"%u size %u address %x\n",
+			blockno, pageno, blocksize,
+			le32_to_cpu(blockidx->PageAddress));
+
+		/* send block info through the IDMA pipe */
+		if (uea_idma_write(sc, &bi, E4_BLOCK_INFO_SIZE))
+			goto bad;
+
+		/* send block data through the IDMA pipe */
+		if (uea_idma_write(sc, blockoffset, blocksize))
+			goto bad;
+
+		blockno++;
+	} while (blockidx->NotLastBlock);
+
+	return;
+
+bad:
+	uea_err(INS_TO_USBDEV(sc), "sending DSP block %u failed\n", blockno);
+	return;
+}
+
+static void uea_load_page_e4(struct work_struct *work)
+{
+	struct uea_softc *sc = container_of(work, struct uea_softc, task);
+	u8 pageno = sc->pageno;
+	int i;
+	struct block_info_e4 bi;
+	struct l1_code *p;
+
+	uea_dbg(INS_TO_USBDEV(sc), "sending DSP page %u\n", pageno);
+
+	/* reload firmware when reboot start and it's loaded already */
+	if (pageno == 0) {
+		release_firmware(sc->dsp_firm);
+		sc->dsp_firm = NULL;
+	}
+
+	if (sc->dsp_firm == NULL && request_dsp(sc) < 0)
+		return;
+
+	p = (struct l1_code *) sc->dsp_firm->data;
+	if (pageno >= le16_to_cpu(p->page_header[0].PageNumber)) {
+		uea_err(INS_TO_USBDEV(sc), "invalid DSP "
+						"page %u requested\n", pageno);
+		return;
+	}
+
+	if (pageno != 0) {
+		__uea_load_page_e4(sc, pageno, 0);
+		return;
+	}
+
+	uea_dbg(INS_TO_USBDEV(sc),
+	       "sending Main DSP page %u\n", p->page_header[0].PageNumber);
+
+	for (i = 0; i < le16_to_cpu(p->page_header[0].PageNumber); i++) {
+		if (E4_IS_BOOT_PAGE(p->page_header[i].PageSize))
+			__uea_load_page_e4(sc, i, 1);
+	}
+
+	uea_dbg(INS_TO_USBDEV(sc) , "sending start bi\n");
+
+	bi.wHdr = cpu_to_be16(UEA_BIHDR);
+	bi.bBootPage = 0;
+	bi.bPageNumber = 0xff;
+	bi.wReserved = cpu_to_be16(UEA_RESERVED);
+	bi.dwSize = cpu_to_be32(E4_PAGE_BYTES(p->page_header[0].PageSize));
+	bi.dwAddress = cpu_to_be32(le32_to_cpu(p->page_header[0].PageAddress));
+
+	/* send block info through the IDMA pipe */
+	if (uea_idma_write(sc, &bi, E4_BLOCK_INFO_SIZE))
+		uea_err(INS_TO_USBDEV(sc), "sending DSP start bi failed\n");
+}
+
+static inline void wake_up_cmv_ack(struct uea_softc *sc)
+{
+	BUG_ON(sc->cmv_ack);
+	sc->cmv_ack = 1;
+	wake_up(&sc->sync_q);
+}
+
+static inline int wait_cmv_ack(struct uea_softc *sc)
+{
+	int ret = uea_wait(sc, sc->cmv_ack , ACK_TIMEOUT);
+
+	sc->cmv_ack = 0;
+
+	uea_dbg(INS_TO_USBDEV(sc), "wait_event_timeout : %d ms\n",
+			jiffies_to_msecs(ret));
+
+	if (ret < 0)
+		return ret;
+
+	return (ret == 0) ? -ETIMEDOUT : 0;
+}
+
+#define UCDC_SEND_ENCAPSULATED_COMMAND 0x00
+
+static int uea_request(struct uea_softc *sc,
+		u16 value, u16 index, u16 size, const void *data)
+{
+	u8 *xfer_buff;
+	int ret = -ENOMEM;
+
+	xfer_buff = kmemdup(data, size, GFP_KERNEL);
+	if (!xfer_buff) {
+		uea_err(INS_TO_USBDEV(sc), "can't allocate xfer_buff\n");
+		return ret;
+	}
+
+	ret = usb_control_msg(sc->usb_dev, usb_sndctrlpipe(sc->usb_dev, 0),
+			      UCDC_SEND_ENCAPSULATED_COMMAND,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      value, index, xfer_buff, size, CTRL_TIMEOUT);
+
+	kfree(xfer_buff);
+	if (ret < 0) {
+		uea_err(INS_TO_USBDEV(sc), "usb_control_msg error %d\n", ret);
+		return ret;
+	}
+
+	if (ret != size) {
+		uea_err(INS_TO_USBDEV(sc),
+		       "usb_control_msg send only %d bytes (instead of %d)\n",
+		       ret, size);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int uea_cmv_e1(struct uea_softc *sc,
+		u8 function, u32 address, u16 offset, u32 data)
+{
+	struct cmv_e1 cmv;
+	int ret;
+
+	uea_enters(INS_TO_USBDEV(sc));
+	uea_vdbg(INS_TO_USBDEV(sc), "Function : %d-%d, Address : %c%c%c%c, "
+			"offset : 0x%04x, data : 0x%08x\n",
+			E1_FUNCTION_TYPE(function),
+			E1_FUNCTION_SUBTYPE(function),
+			E1_GETSA1(address), E1_GETSA2(address),
+			E1_GETSA3(address),
+			E1_GETSA4(address), offset, data);
+
+	/* we send a request, but we expect a reply */
+	sc->cmv_dsc.e1.function = function | 0x2;
+	sc->cmv_dsc.e1.idx++;
+	sc->cmv_dsc.e1.address = address;
+	sc->cmv_dsc.e1.offset = offset;
+
+	cmv.wPreamble = cpu_to_le16(E1_PREAMBLE);
+	cmv.bDirection = E1_HOSTTOMODEM;
+	cmv.bFunction = function;
+	cmv.wIndex = cpu_to_le16(sc->cmv_dsc.e1.idx);
+	put_unaligned_le32(address, &cmv.dwSymbolicAddress);
+	cmv.wOffsetAddress = cpu_to_le16(offset);
+	put_unaligned_le32(data >> 16 | data << 16, &cmv.dwData);
+
+	ret = uea_request(sc, UEA_E1_SET_BLOCK, UEA_MPTX_START,
+							sizeof(cmv), &cmv);
+	if (ret < 0)
+		return ret;
+	ret = wait_cmv_ack(sc);
+	uea_leaves(INS_TO_USBDEV(sc));
+	return ret;
+}
+
+static int uea_cmv_e4(struct uea_softc *sc,
+		u16 function, u16 group, u16 address, u16 offset, u32 data)
+{
+	struct cmv_e4 cmv;
+	int ret;
+
+	uea_enters(INS_TO_USBDEV(sc));
+	memset(&cmv, 0, sizeof(cmv));
+
+	uea_vdbg(INS_TO_USBDEV(sc), "Function : %d-%d, Group : 0x%04x, "
+		 "Address : 0x%04x, offset : 0x%04x, data : 0x%08x\n",
+		 E4_FUNCTION_TYPE(function), E4_FUNCTION_SUBTYPE(function),
+		 group, address, offset, data);
+
+	/* we send a request, but we expect a reply */
+	sc->cmv_dsc.e4.function = function | (0x1 << 4);
+	sc->cmv_dsc.e4.offset = offset;
+	sc->cmv_dsc.e4.address = address;
+	sc->cmv_dsc.e4.group = group;
+
+	cmv.wFunction = cpu_to_be16(function);
+	cmv.wGroup = cpu_to_be16(group);
+	cmv.wAddress = cpu_to_be16(address);
+	cmv.wOffset = cpu_to_be16(offset);
+	cmv.dwData[0] = cpu_to_be32(data);
+
+	ret = uea_request(sc, UEA_E4_SET_BLOCK, UEA_MPTX_START,
+							sizeof(cmv), &cmv);
+	if (ret < 0)
+		return ret;
+	ret = wait_cmv_ack(sc);
+	uea_leaves(INS_TO_USBDEV(sc));
+	return ret;
+}
+
+static inline int uea_read_cmv_e1(struct uea_softc *sc,
+		u32 address, u16 offset, u32 *data)
+{
+	int ret = uea_cmv_e1(sc, E1_MAKEFUNCTION(E1_MEMACCESS, E1_REQUESTREAD),
+			  address, offset, 0);
+	if (ret < 0)
+		uea_err(INS_TO_USBDEV(sc),
+			"reading cmv failed with error %d\n", ret);
+	else
+		*data = sc->data;
+
+	return ret;
+}
+
+static inline int uea_read_cmv_e4(struct uea_softc *sc,
+		u8 size, u16 group, u16 address, u16 offset, u32 *data)
+{
+	int ret = uea_cmv_e4(sc, E4_MAKEFUNCTION(E4_MEMACCESS,
+							E4_REQUESTREAD, size),
+			  group, address, offset, 0);
+	if (ret < 0)
+		uea_err(INS_TO_USBDEV(sc),
+			"reading cmv failed with error %d\n", ret);
+	else {
+		*data = sc->data;
+		/* size is in 16-bit word quantities */
+		if (size > 2)
+			*(data + 1) = sc->data1;
+	}
+	return ret;
+}
+
+static inline int uea_write_cmv_e1(struct uea_softc *sc,
+		u32 address, u16 offset, u32 data)
+{
+	int ret = uea_cmv_e1(sc, E1_MAKEFUNCTION(E1_MEMACCESS, E1_REQUESTWRITE),
+			  address, offset, data);
+	if (ret < 0)
+		uea_err(INS_TO_USBDEV(sc),
+			"writing cmv failed with error %d\n", ret);
+
+	return ret;
+}
+
+static inline int uea_write_cmv_e4(struct uea_softc *sc,
+		u8 size, u16 group, u16 address, u16 offset, u32 data)
+{
+	int ret = uea_cmv_e4(sc, E4_MAKEFUNCTION(E4_MEMACCESS,
+							E4_REQUESTWRITE, size),
+			  group, address, offset, data);
+	if (ret < 0)
+		uea_err(INS_TO_USBDEV(sc),
+			"writing cmv failed with error %d\n", ret);
+
+	return ret;
+}
+
+static void uea_set_bulk_timeout(struct uea_softc *sc, u32 dsrate)
+{
+	int ret;
+	u16 timeout;
+
+	/* in bulk mode the modem have problem with high rate
+	 * changing internal timing could improve things, but the
+	 * value is mysterious.
+	 * ADI930 don't support it (-EPIPE error).
+	 */
+
+	if (UEA_CHIP_VERSION(sc) == ADI930 ||
+	    altsetting[sc->modem_index] > 0 ||
+	    sc->stats.phy.dsrate == dsrate)
+		return;
+
+	/* Original timming (1Mbit/s) from ADI (used in windows driver) */
+	timeout = (dsrate <= 1024*1024) ? 0 : 1;
+	ret = uea_request(sc, UEA_SET_TIMEOUT, timeout, 0, NULL);
+	uea_info(INS_TO_USBDEV(sc), "setting new timeout %d%s\n",
+		 timeout,  ret < 0 ? " failed" : "");
+
+}
+
+/*
+ * Monitor the modem and update the stat
+ * return 0 if everything is ok
+ * return < 0 if an error occurs (-EAGAIN reboot needed)
+ */
+static int uea_stat_e1(struct uea_softc *sc)
+{
+	u32 data;
+	int ret;
+
+	uea_enters(INS_TO_USBDEV(sc));
+	data = sc->stats.phy.state;
+
+	ret = uea_read_cmv_e1(sc, E1_SA_STAT, 0, &sc->stats.phy.state);
+	if (ret < 0)
+		return ret;
+
+	switch (GET_STATUS(sc->stats.phy.state)) {
+	case 0:		/* not yet synchronized */
+		uea_dbg(INS_TO_USBDEV(sc),
+		       "modem not yet synchronized\n");
+		return 0;
+
+	case 1:		/* initialization */
+		uea_dbg(INS_TO_USBDEV(sc), "modem initializing\n");
+		return 0;
+
+	case 2:		/* operational */
+		uea_vdbg(INS_TO_USBDEV(sc), "modem operational\n");
+		break;
+
+	case 3:		/* fail ... */
+		uea_info(INS_TO_USBDEV(sc), "modem synchronization failed"
+					" (may be try other cmv/dsp)\n");
+		return -EAGAIN;
+
+	case 4 ... 6:	/* test state */
+		uea_warn(INS_TO_USBDEV(sc),
+				"modem in test mode - not supported\n");
+		return -EAGAIN;
+
+	case 7:		/* fast-retain ... */
+		uea_info(INS_TO_USBDEV(sc), "modem in fast-retain mode\n");
+		return 0;
+	default:
+		uea_err(INS_TO_USBDEV(sc), "modem invalid SW mode %d\n",
+			GET_STATUS(sc->stats.phy.state));
+		return -EAGAIN;
+	}
+
+	if (GET_STATUS(data) != 2) {
+		uea_request(sc, UEA_SET_MODE, UEA_LOOPBACK_OFF, 0, NULL);
+		uea_info(INS_TO_USBDEV(sc), "modem operational\n");
+
+		/* release the dsp firmware as it is not needed until
+		 * the next failure
+		 */
+		release_firmware(sc->dsp_firm);
+		sc->dsp_firm = NULL;
+	}
+
+	/* always update it as atm layer could not be init when we switch to
+	 * operational state
+	 */
+	UPDATE_ATM_SIGNAL(ATM_PHY_SIG_FOUND);
+
+	/* wake up processes waiting for synchronization */
+	wake_up(&sc->sync_q);
+
+	ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 2, &sc->stats.phy.flags);
+	if (ret < 0)
+		return ret;
+	sc->stats.phy.mflags |= sc->stats.phy.flags;
+
+	/* in case of a flags ( for example delineation LOSS (& 0x10)),
+	 * we check the status again in order to detect the failure earlier
+	 */
+	if (sc->stats.phy.flags) {
+		uea_dbg(INS_TO_USBDEV(sc), "Stat flag = 0x%x\n",
+		       sc->stats.phy.flags);
+		return 0;
+	}
+
+	ret = uea_read_cmv_e1(sc, E1_SA_RATE, 0, &data);
+	if (ret < 0)
+		return ret;
+
+	uea_set_bulk_timeout(sc, (data >> 16) * 32);
+	sc->stats.phy.dsrate = (data >> 16) * 32;
+	sc->stats.phy.usrate = (data & 0xffff) * 32;
+	UPDATE_ATM_STAT(link_rate, sc->stats.phy.dsrate * 1000 / 424);
+
+	ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 23, &data);
+	if (ret < 0)
+		return ret;
+	sc->stats.phy.dsattenuation = (data & 0xff) / 2;
+
+	ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 47, &data);
+	if (ret < 0)
+		return ret;
+	sc->stats.phy.usattenuation = (data & 0xff) / 2;
+
+	ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 25, &sc->stats.phy.dsmargin);
+	if (ret < 0)
+		return ret;
+
+	ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 49, &sc->stats.phy.usmargin);
+	if (ret < 0)
+		return ret;
+
+	ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 51, &sc->stats.phy.rxflow);
+	if (ret < 0)
+		return ret;
+
+	ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 52, &sc->stats.phy.txflow);
+	if (ret < 0)
+		return ret;
+
+	ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 54, &sc->stats.phy.dsunc);
+	if (ret < 0)
+		return ret;
+
+	/* only for atu-c */
+	ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 58, &sc->stats.phy.usunc);
+	if (ret < 0)
+		return ret;
+
+	ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 53, &sc->stats.phy.dscorr);
+	if (ret < 0)
+		return ret;
+
+	/* only for atu-c */
+	ret = uea_read_cmv_e1(sc, E1_SA_DIAG, 57, &sc->stats.phy.uscorr);
+	if (ret < 0)
+		return ret;
+
+	ret = uea_read_cmv_e1(sc, E1_SA_INFO, 8, &sc->stats.phy.vidco);
+	if (ret < 0)
+		return ret;
+
+	ret = uea_read_cmv_e1(sc, E1_SA_INFO, 13, &sc->stats.phy.vidcpe);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int uea_stat_e4(struct uea_softc *sc)
+{
+	u32 data;
+	u32 tmp_arr[2];
+	int ret;
+
+	uea_enters(INS_TO_USBDEV(sc));
+	data = sc->stats.phy.state;
+
+	/* XXX only need to be done before operationnal... */
+	ret = uea_read_cmv_e4(sc, 1, E4_SA_STAT, 0, 0, &sc->stats.phy.state);
+	if (ret < 0)
+		return ret;
+
+	switch (sc->stats.phy.state) {
+	case 0x0:	/* not yet synchronized */
+	case 0x1:
+	case 0x3:
+	case 0x4:
+		uea_dbg(INS_TO_USBDEV(sc), "modem not yet "
+						"synchronized\n");
+		return 0;
+	case 0x5:	/* initialization */
+	case 0x6:
+	case 0x9:
+	case 0xa:
+		uea_dbg(INS_TO_USBDEV(sc), "modem initializing\n");
+		return 0;
+	case 0x2:	/* fail ... */
+		uea_info(INS_TO_USBDEV(sc), "modem synchronization "
+				"failed (may be try other cmv/dsp)\n");
+		return -EAGAIN;
+	case 0x7:	/* operational */
+		break;
+	default:
+		uea_warn(INS_TO_USBDEV(sc), "unknown state: %x\n",
+						sc->stats.phy.state);
+		return 0;
+	}
+
+	if (data != 7) {
+		uea_request(sc, UEA_SET_MODE, UEA_LOOPBACK_OFF, 0, NULL);
+		uea_info(INS_TO_USBDEV(sc), "modem operational\n");
+
+		/* release the dsp firmware as it is not needed until
+		 * the next failure
+		 */
+		release_firmware(sc->dsp_firm);
+		sc->dsp_firm = NULL;
+	}
+
+	/* always update it as atm layer could not be init when we switch to
+	 * operational state
+	 */
+	UPDATE_ATM_SIGNAL(ATM_PHY_SIG_FOUND);
+
+	/* wake up processes waiting for synchronization */
+	wake_up(&sc->sync_q);
+
+	/* TODO improve this state machine :
+	 * we need some CMV info : what they do and their unit
+	 * we should find the equivalent of eagle3- CMV
+	 */
+	/* check flags */
+	ret = uea_read_cmv_e4(sc, 1, E4_SA_DIAG, 0, 0, &sc->stats.phy.flags);
+	if (ret < 0)
+		return ret;
+	sc->stats.phy.mflags |= sc->stats.phy.flags;
+
+	/* in case of a flags ( for example delineation LOSS (& 0x10)),
+	 * we check the status again in order to detect the failure earlier
+	 */
+	if (sc->stats.phy.flags) {
+		uea_dbg(INS_TO_USBDEV(sc), "Stat flag = 0x%x\n",
+		       sc->stats.phy.flags);
+		if (sc->stats.phy.flags & 1) /* delineation LOSS */
+			return -EAGAIN;
+		if (sc->stats.phy.flags & 0x4000) /* Reset Flag */
+			return -EAGAIN;
+		return 0;
+	}
+
+	/* rate data may be in upper or lower half of 64 bit word, strange */
+	ret = uea_read_cmv_e4(sc, 4, E4_SA_RATE, 0, 0, tmp_arr);
+	if (ret < 0)
+		return ret;
+	data = (tmp_arr[0]) ? tmp_arr[0] : tmp_arr[1];
+	sc->stats.phy.usrate = data / 1000;
+
+	ret = uea_read_cmv_e4(sc, 4, E4_SA_RATE, 1, 0, tmp_arr);
+	if (ret < 0)
+		return ret;
+	data = (tmp_arr[0]) ? tmp_arr[0] : tmp_arr[1];
+	uea_set_bulk_timeout(sc, data / 1000);
+	sc->stats.phy.dsrate = data / 1000;
+	UPDATE_ATM_STAT(link_rate, sc->stats.phy.dsrate * 1000 / 424);
+
+	ret = uea_read_cmv_e4(sc, 1, E4_SA_INFO, 68, 1, &data);
+	if (ret < 0)
+		return ret;
+	sc->stats.phy.dsattenuation = data / 10;
+
+	ret = uea_read_cmv_e4(sc, 1, E4_SA_INFO, 69, 1, &data);
+	if (ret < 0)
+		return ret;
+	sc->stats.phy.usattenuation = data / 10;
+
+	ret = uea_read_cmv_e4(sc, 1, E4_SA_INFO, 68, 3, &data);
+	if (ret < 0)
+		return ret;
+	sc->stats.phy.dsmargin = data / 2;
+
+	ret = uea_read_cmv_e4(sc, 1, E4_SA_INFO, 69, 3, &data);
+	if (ret < 0)
+		return ret;
+	sc->stats.phy.usmargin = data / 10;
+
+	return 0;
+}
+
+static void cmvs_file_name(struct uea_softc *sc, char *const cmv_name, int ver)
+{
+	char file_arr[] = "CMVxy.bin";
+	char *file;
+
+	kernel_param_lock(THIS_MODULE);
+	/* set proper name corresponding modem version and line type */
+	if (cmv_file[sc->modem_index] == NULL) {
+		if (UEA_CHIP_VERSION(sc) == ADI930)
+			file_arr[3] = '9';
+		else if (UEA_CHIP_VERSION(sc) == EAGLE_IV)
+			file_arr[3] = '4';
+		else
+			file_arr[3] = 'e';
+
+		file_arr[4] = IS_ISDN(sc) ? 'i' : 'p';
+		file = file_arr;
+	} else
+		file = cmv_file[sc->modem_index];
+
+	strcpy(cmv_name, FW_DIR);
+	strlcat(cmv_name, file, UEA_FW_NAME_MAX);
+	if (ver == 2)
+		strlcat(cmv_name, ".v2", UEA_FW_NAME_MAX);
+	kernel_param_unlock(THIS_MODULE);
+}
+
+static int request_cmvs_old(struct uea_softc *sc,
+		 void **cmvs, const struct firmware **fw)
+{
+	int ret, size;
+	u8 *data;
+	char cmv_name[UEA_FW_NAME_MAX]; /* 30 bytes stack variable */
+
+	cmvs_file_name(sc, cmv_name, 1);
+	ret = request_firmware(fw, cmv_name, &sc->usb_dev->dev);
+	if (ret < 0) {
+		uea_err(INS_TO_USBDEV(sc),
+		       "requesting firmware %s failed with error %d\n",
+		       cmv_name, ret);
+		return ret;
+	}
+
+	data = (u8 *) (*fw)->data;
+	size = (*fw)->size;
+	if (size < 1)
+		goto err_fw_corrupted;
+
+	if (size != *data * sizeof(struct uea_cmvs_v1) + 1)
+		goto err_fw_corrupted;
+
+	*cmvs = (void *)(data + 1);
+	return *data;
+
+err_fw_corrupted:
+	uea_err(INS_TO_USBDEV(sc), "firmware %s is corrupted\n", cmv_name);
+	release_firmware(*fw);
+	return -EILSEQ;
+}
+
+static int request_cmvs(struct uea_softc *sc,
+		 void **cmvs, const struct firmware **fw, int *ver)
+{
+	int ret, size;
+	u32 crc;
+	u8 *data;
+	char cmv_name[UEA_FW_NAME_MAX]; /* 30 bytes stack variable */
+
+	cmvs_file_name(sc, cmv_name, 2);
+	ret = request_firmware(fw, cmv_name, &sc->usb_dev->dev);
+	if (ret < 0) {
+		/* if caller can handle old version, try to provide it */
+		if (*ver == 1) {
+			uea_warn(INS_TO_USBDEV(sc), "requesting "
+							"firmware %s failed, "
+				"try to get older cmvs\n", cmv_name);
+			return request_cmvs_old(sc, cmvs, fw);
+		}
+		uea_err(INS_TO_USBDEV(sc),
+		       "requesting firmware %s failed with error %d\n",
+		       cmv_name, ret);
+		return ret;
+	}
+
+	size = (*fw)->size;
+	data = (u8 *) (*fw)->data;
+	if (size < 4 || strncmp(data, "cmv2", 4) != 0) {
+		if (*ver == 1) {
+			uea_warn(INS_TO_USBDEV(sc), "firmware %s is corrupted,"
+				" try to get older cmvs\n", cmv_name);
+			release_firmware(*fw);
+			return request_cmvs_old(sc, cmvs, fw);
+		}
+		goto err_fw_corrupted;
+	}
+
+	*ver = 2;
+
+	data += 4;
+	size -= 4;
+	if (size < 5)
+		goto err_fw_corrupted;
+
+	crc = get_unaligned_le32(data);
+	data += 4;
+	size -= 4;
+	if (crc32_be(0, data, size) != crc)
+		goto err_fw_corrupted;
+
+	if (size != *data * sizeof(struct uea_cmvs_v2) + 1)
+		goto err_fw_corrupted;
+
+	*cmvs = (void *) (data + 1);
+	return *data;
+
+err_fw_corrupted:
+	uea_err(INS_TO_USBDEV(sc), "firmware %s is corrupted\n", cmv_name);
+	release_firmware(*fw);
+	return -EILSEQ;
+}
+
+static int uea_send_cmvs_e1(struct uea_softc *sc)
+{
+	int i, ret, len;
+	void *cmvs_ptr;
+	const struct firmware *cmvs_fw;
+	int ver = 1; /* we can handle v1 cmv firmware version; */
+
+	/* Enter in R-IDLE (cmv) until instructed otherwise */
+	ret = uea_write_cmv_e1(sc, E1_SA_CNTL, 0, 1);
+	if (ret < 0)
+		return ret;
+
+	/* Dump firmware version */
+	ret = uea_read_cmv_e1(sc, E1_SA_INFO, 10, &sc->stats.phy.firmid);
+	if (ret < 0)
+		return ret;
+	uea_info(INS_TO_USBDEV(sc), "ATU-R firmware version : %x\n",
+			sc->stats.phy.firmid);
+
+	/* get options */
+	ret = len = request_cmvs(sc, &cmvs_ptr, &cmvs_fw, &ver);
+	if (ret < 0)
+		return ret;
+
+	/* send options */
+	if (ver == 1) {
+		struct uea_cmvs_v1 *cmvs_v1 = cmvs_ptr;
+
+		uea_warn(INS_TO_USBDEV(sc), "use deprecated cmvs version, "
+			"please update your firmware\n");
+
+		for (i = 0; i < len; i++) {
+			ret = uea_write_cmv_e1(sc,
+				get_unaligned_le32(&cmvs_v1[i].address),
+				get_unaligned_le16(&cmvs_v1[i].offset),
+				get_unaligned_le32(&cmvs_v1[i].data));
+			if (ret < 0)
+				goto out;
+		}
+	} else if (ver == 2) {
+		struct uea_cmvs_v2 *cmvs_v2 = cmvs_ptr;
+
+		for (i = 0; i < len; i++) {
+			ret = uea_write_cmv_e1(sc,
+				get_unaligned_le32(&cmvs_v2[i].address),
+				(u16) get_unaligned_le32(&cmvs_v2[i].offset),
+				get_unaligned_le32(&cmvs_v2[i].data));
+			if (ret < 0)
+				goto out;
+		}
+	} else {
+		/* This really should not happen */
+		uea_err(INS_TO_USBDEV(sc), "bad cmvs version %d\n", ver);
+		goto out;
+	}
+
+	/* Enter in R-ACT-REQ */
+	ret = uea_write_cmv_e1(sc, E1_SA_CNTL, 0, 2);
+	uea_vdbg(INS_TO_USBDEV(sc), "Entering in R-ACT-REQ state\n");
+	uea_info(INS_TO_USBDEV(sc), "modem started, waiting "
+						"synchronization...\n");
+out:
+	release_firmware(cmvs_fw);
+	return ret;
+}
+
+static int uea_send_cmvs_e4(struct uea_softc *sc)
+{
+	int i, ret, len;
+	void *cmvs_ptr;
+	const struct firmware *cmvs_fw;
+	int ver = 2; /* we can only handle v2 cmv firmware version; */
+
+	/* Enter in R-IDLE (cmv) until instructed otherwise */
+	ret = uea_write_cmv_e4(sc, 1, E4_SA_CNTL, 0, 0, 1);
+	if (ret < 0)
+		return ret;
+
+	/* Dump firmware version */
+	/* XXX don't read the 3th byte as it is always 6 */
+	ret = uea_read_cmv_e4(sc, 2, E4_SA_INFO, 55, 0, &sc->stats.phy.firmid);
+	if (ret < 0)
+		return ret;
+	uea_info(INS_TO_USBDEV(sc), "ATU-R firmware version : %x\n",
+			sc->stats.phy.firmid);
+
+
+	/* get options */
+	ret = len = request_cmvs(sc, &cmvs_ptr, &cmvs_fw, &ver);
+	if (ret < 0)
+		return ret;
+
+	/* send options */
+	if (ver == 2) {
+		struct uea_cmvs_v2 *cmvs_v2 = cmvs_ptr;
+
+		for (i = 0; i < len; i++) {
+			ret = uea_write_cmv_e4(sc, 1,
+				get_unaligned_le32(&cmvs_v2[i].group),
+				get_unaligned_le32(&cmvs_v2[i].address),
+				get_unaligned_le32(&cmvs_v2[i].offset),
+				get_unaligned_le32(&cmvs_v2[i].data));
+			if (ret < 0)
+				goto out;
+		}
+	} else {
+		/* This really should not happen */
+		uea_err(INS_TO_USBDEV(sc), "bad cmvs version %d\n", ver);
+		goto out;
+	}
+
+	/* Enter in R-ACT-REQ */
+	ret = uea_write_cmv_e4(sc, 1, E4_SA_CNTL, 0, 0, 2);
+	uea_vdbg(INS_TO_USBDEV(sc), "Entering in R-ACT-REQ state\n");
+	uea_info(INS_TO_USBDEV(sc), "modem started, waiting "
+						"synchronization...\n");
+out:
+	release_firmware(cmvs_fw);
+	return ret;
+}
+
+/* Start boot post firmware modem:
+ * - send reset commands through usb control pipe
+ * - start workqueue for DSP loading
+ * - send CMV options to modem
+ */
+
+static int uea_start_reset(struct uea_softc *sc)
+{
+	u16 zero = 0;	/* ;-) */
+	int ret;
+
+	uea_enters(INS_TO_USBDEV(sc));
+	uea_info(INS_TO_USBDEV(sc), "(re)booting started\n");
+
+	/* mask interrupt */
+	sc->booting = 1;
+	/* We need to set this here because, a ack timeout could have occurred,
+	 * but before we start the reboot, the ack occurs and set this to 1.
+	 * So we will failed to wait Ready CMV.
+	 */
+	sc->cmv_ack = 0;
+	UPDATE_ATM_SIGNAL(ATM_PHY_SIG_LOST);
+
+	/* reset statistics */
+	memset(&sc->stats, 0, sizeof(struct uea_stats));
+
+	/* tell the modem that we want to boot in IDMA mode */
+	uea_request(sc, UEA_SET_MODE, UEA_LOOPBACK_ON, 0, NULL);
+	uea_request(sc, UEA_SET_MODE, UEA_BOOT_IDMA, 0, NULL);
+
+	/* enter reset mode */
+	uea_request(sc, UEA_SET_MODE, UEA_START_RESET, 0, NULL);
+
+	/* original driver use 200ms, but windows driver use 100ms */
+	ret = uea_wait(sc, 0, msecs_to_jiffies(100));
+	if (ret < 0)
+		return ret;
+
+	/* leave reset mode */
+	uea_request(sc, UEA_SET_MODE, UEA_END_RESET, 0, NULL);
+
+	if (UEA_CHIP_VERSION(sc) != EAGLE_IV) {
+		/* clear tx and rx mailboxes */
+		uea_request(sc, UEA_SET_2183_DATA, UEA_MPTX_MAILBOX, 2, &zero);
+		uea_request(sc, UEA_SET_2183_DATA, UEA_MPRX_MAILBOX, 2, &zero);
+		uea_request(sc, UEA_SET_2183_DATA, UEA_SWAP_MAILBOX, 2, &zero);
+	}
+
+	ret = uea_wait(sc, 0, msecs_to_jiffies(1000));
+	if (ret < 0)
+		return ret;
+
+	if (UEA_CHIP_VERSION(sc) == EAGLE_IV)
+		sc->cmv_dsc.e4.function = E4_MAKEFUNCTION(E4_ADSLDIRECTIVE,
+							E4_MODEMREADY, 1);
+	else
+		sc->cmv_dsc.e1.function = E1_MAKEFUNCTION(E1_ADSLDIRECTIVE,
+							E1_MODEMREADY);
+
+	/* demask interrupt */
+	sc->booting = 0;
+
+	/* start loading DSP */
+	sc->pageno = 0;
+	sc->ovl = 0;
+	schedule_work(&sc->task);
+
+	/* wait for modem ready CMV */
+	ret = wait_cmv_ack(sc);
+	if (ret < 0)
+		return ret;
+
+	uea_vdbg(INS_TO_USBDEV(sc), "Ready CMV received\n");
+
+	ret = sc->send_cmvs(sc);
+	if (ret < 0)
+		return ret;
+
+	sc->reset = 0;
+	uea_leaves(INS_TO_USBDEV(sc));
+	return ret;
+}
+
+/*
+ * In case of an error wait 1s before rebooting the modem
+ * if the modem don't request reboot (-EAGAIN).
+ * Monitor the modem every 1s.
+ */
+
+static int uea_kthread(void *data)
+{
+	struct uea_softc *sc = data;
+	int ret = -EAGAIN;
+
+	set_freezable();
+	uea_enters(INS_TO_USBDEV(sc));
+	while (!kthread_should_stop()) {
+		if (ret < 0 || sc->reset)
+			ret = uea_start_reset(sc);
+		if (!ret)
+			ret = sc->stat(sc);
+		if (ret != -EAGAIN)
+			uea_wait(sc, 0, msecs_to_jiffies(1000));
+		try_to_freeze();
+	}
+	uea_leaves(INS_TO_USBDEV(sc));
+	return ret;
+}
+
+/* Load second usb firmware for ADI930 chip */
+static int load_XILINX_firmware(struct uea_softc *sc)
+{
+	const struct firmware *fw_entry;
+	int ret, size, u, ln;
+	const u8 *pfw;
+	u8 value;
+	char *fw_name = FPGA930_FIRMWARE;
+
+	uea_enters(INS_TO_USBDEV(sc));
+
+	ret = request_firmware(&fw_entry, fw_name, &sc->usb_dev->dev);
+	if (ret) {
+		uea_err(INS_TO_USBDEV(sc), "firmware %s is not available\n",
+		       fw_name);
+		goto err0;
+	}
+
+	pfw = fw_entry->data;
+	size = fw_entry->size;
+	if (size != 0x577B) {
+		uea_err(INS_TO_USBDEV(sc), "firmware %s is corrupted\n",
+		       fw_name);
+		ret = -EILSEQ;
+		goto err1;
+	}
+	for (u = 0; u < size; u += ln) {
+		ln = min(size - u, 64);
+		ret = uea_request(sc, 0xe, 0, ln, pfw + u);
+		if (ret < 0) {
+			uea_err(INS_TO_USBDEV(sc),
+			       "elsa download data failed (%d)\n", ret);
+			goto err1;
+		}
+	}
+
+	/* finish to send the fpga */
+	ret = uea_request(sc, 0xe, 1, 0, NULL);
+	if (ret < 0) {
+		uea_err(INS_TO_USBDEV(sc),
+				"elsa download data failed (%d)\n", ret);
+		goto err1;
+	}
+
+	/* Tell the modem we finish : de-assert reset */
+	value = 0;
+	ret = uea_send_modem_cmd(sc->usb_dev, 0xe, 1, &value);
+	if (ret < 0)
+		uea_err(sc->usb_dev, "elsa de-assert failed with error"
+								" %d\n", ret);
+
+err1:
+	release_firmware(fw_entry);
+err0:
+	uea_leaves(INS_TO_USBDEV(sc));
+	return ret;
+}
+
+/* The modem send us an ack. First with check if it right */
+static void uea_dispatch_cmv_e1(struct uea_softc *sc, struct intr_pkt *intr)
+{
+	struct cmv_dsc_e1 *dsc = &sc->cmv_dsc.e1;
+	struct cmv_e1 *cmv = &intr->u.e1.s2.cmv;
+
+	uea_enters(INS_TO_USBDEV(sc));
+	if (le16_to_cpu(cmv->wPreamble) != E1_PREAMBLE)
+		goto bad1;
+
+	if (cmv->bDirection != E1_MODEMTOHOST)
+		goto bad1;
+
+	/* FIXME : ADI930 reply wrong preambule (func = 2, sub = 2) to
+	 * the first MEMACCESS cmv. Ignore it...
+	 */
+	if (cmv->bFunction != dsc->function) {
+		if (UEA_CHIP_VERSION(sc) == ADI930
+				&& cmv->bFunction ==  E1_MAKEFUNCTION(2, 2)) {
+			cmv->wIndex = cpu_to_le16(dsc->idx);
+			put_unaligned_le32(dsc->address,
+						&cmv->dwSymbolicAddress);
+			cmv->wOffsetAddress = cpu_to_le16(dsc->offset);
+		} else
+			goto bad2;
+	}
+
+	if (cmv->bFunction == E1_MAKEFUNCTION(E1_ADSLDIRECTIVE,
+							E1_MODEMREADY)) {
+		wake_up_cmv_ack(sc);
+		uea_leaves(INS_TO_USBDEV(sc));
+		return;
+	}
+
+	/* in case of MEMACCESS */
+	if (le16_to_cpu(cmv->wIndex) != dsc->idx ||
+	    get_unaligned_le32(&cmv->dwSymbolicAddress) != dsc->address ||
+	    le16_to_cpu(cmv->wOffsetAddress) != dsc->offset)
+		goto bad2;
+
+	sc->data = get_unaligned_le32(&cmv->dwData);
+	sc->data = sc->data << 16 | sc->data >> 16;
+
+	wake_up_cmv_ack(sc);
+	uea_leaves(INS_TO_USBDEV(sc));
+	return;
+
+bad2:
+	uea_err(INS_TO_USBDEV(sc), "unexpected cmv received, "
+			"Function : %d, Subfunction : %d\n",
+			E1_FUNCTION_TYPE(cmv->bFunction),
+			E1_FUNCTION_SUBTYPE(cmv->bFunction));
+	uea_leaves(INS_TO_USBDEV(sc));
+	return;
+
+bad1:
+	uea_err(INS_TO_USBDEV(sc), "invalid cmv received, "
+			"wPreamble %d, bDirection %d\n",
+			le16_to_cpu(cmv->wPreamble), cmv->bDirection);
+	uea_leaves(INS_TO_USBDEV(sc));
+}
+
+/* The modem send us an ack. First with check if it right */
+static void uea_dispatch_cmv_e4(struct uea_softc *sc, struct intr_pkt *intr)
+{
+	struct cmv_dsc_e4 *dsc = &sc->cmv_dsc.e4;
+	struct cmv_e4 *cmv = &intr->u.e4.s2.cmv;
+
+	uea_enters(INS_TO_USBDEV(sc));
+	uea_dbg(INS_TO_USBDEV(sc), "cmv %x %x %x %x %x %x\n",
+		be16_to_cpu(cmv->wGroup), be16_to_cpu(cmv->wFunction),
+		be16_to_cpu(cmv->wOffset), be16_to_cpu(cmv->wAddress),
+		be32_to_cpu(cmv->dwData[0]), be32_to_cpu(cmv->dwData[1]));
+
+	if (be16_to_cpu(cmv->wFunction) != dsc->function)
+		goto bad2;
+
+	if (be16_to_cpu(cmv->wFunction) == E4_MAKEFUNCTION(E4_ADSLDIRECTIVE,
+						E4_MODEMREADY, 1)) {
+		wake_up_cmv_ack(sc);
+		uea_leaves(INS_TO_USBDEV(sc));
+		return;
+	}
+
+	/* in case of MEMACCESS */
+	if (be16_to_cpu(cmv->wOffset) != dsc->offset ||
+	    be16_to_cpu(cmv->wGroup) != dsc->group ||
+	    be16_to_cpu(cmv->wAddress) != dsc->address)
+		goto bad2;
+
+	sc->data = be32_to_cpu(cmv->dwData[0]);
+	sc->data1 = be32_to_cpu(cmv->dwData[1]);
+	wake_up_cmv_ack(sc);
+	uea_leaves(INS_TO_USBDEV(sc));
+	return;
+
+bad2:
+	uea_err(INS_TO_USBDEV(sc), "unexpected cmv received, "
+			"Function : %d, Subfunction : %d\n",
+			E4_FUNCTION_TYPE(cmv->wFunction),
+			E4_FUNCTION_SUBTYPE(cmv->wFunction));
+	uea_leaves(INS_TO_USBDEV(sc));
+	return;
+}
+
+static void uea_schedule_load_page_e1(struct uea_softc *sc,
+						struct intr_pkt *intr)
+{
+	sc->pageno = intr->e1_bSwapPageNo;
+	sc->ovl = intr->e1_bOvl >> 4 | intr->e1_bOvl << 4;
+	schedule_work(&sc->task);
+}
+
+static void uea_schedule_load_page_e4(struct uea_softc *sc,
+						struct intr_pkt *intr)
+{
+	sc->pageno = intr->e4_bSwapPageNo;
+	schedule_work(&sc->task);
+}
+
+/*
+ * interrupt handler
+ */
+static void uea_intr(struct urb *urb)
+{
+	struct uea_softc *sc = urb->context;
+	struct intr_pkt *intr = urb->transfer_buffer;
+	int status = urb->status;
+
+	uea_enters(INS_TO_USBDEV(sc));
+
+	if (unlikely(status < 0)) {
+		uea_err(INS_TO_USBDEV(sc), "uea_intr() failed with %d\n",
+		       status);
+		return;
+	}
+
+	/* device-to-host interrupt */
+	if (intr->bType != 0x08 || sc->booting) {
+		uea_err(INS_TO_USBDEV(sc), "wrong interrupt\n");
+		goto resubmit;
+	}
+
+	switch (le16_to_cpu(intr->wInterrupt)) {
+	case INT_LOADSWAPPAGE:
+		sc->schedule_load_page(sc, intr);
+		break;
+
+	case INT_INCOMINGCMV:
+		sc->dispatch_cmv(sc, intr);
+		break;
+
+	default:
+		uea_err(INS_TO_USBDEV(sc), "unknown interrupt %u\n",
+		       le16_to_cpu(intr->wInterrupt));
+	}
+
+resubmit:
+	usb_submit_urb(sc->urb_int, GFP_ATOMIC);
+}
+
+/*
+ * Start the modem : init the data and start kernel thread
+ */
+static int uea_boot(struct uea_softc *sc)
+{
+	int ret, size;
+	struct intr_pkt *intr;
+
+	uea_enters(INS_TO_USBDEV(sc));
+
+	if (UEA_CHIP_VERSION(sc) == EAGLE_IV) {
+		size = E4_INTR_PKT_SIZE;
+		sc->dispatch_cmv = uea_dispatch_cmv_e4;
+		sc->schedule_load_page = uea_schedule_load_page_e4;
+		sc->stat = uea_stat_e4;
+		sc->send_cmvs = uea_send_cmvs_e4;
+		INIT_WORK(&sc->task, uea_load_page_e4);
+	} else {
+		size = E1_INTR_PKT_SIZE;
+		sc->dispatch_cmv = uea_dispatch_cmv_e1;
+		sc->schedule_load_page = uea_schedule_load_page_e1;
+		sc->stat = uea_stat_e1;
+		sc->send_cmvs = uea_send_cmvs_e1;
+		INIT_WORK(&sc->task, uea_load_page_e1);
+	}
+
+	init_waitqueue_head(&sc->sync_q);
+
+	if (UEA_CHIP_VERSION(sc) == ADI930)
+		load_XILINX_firmware(sc);
+
+	intr = kmalloc(size, GFP_KERNEL);
+	if (!intr)
+		goto err0;
+
+	sc->urb_int = usb_alloc_urb(0, GFP_KERNEL);
+	if (!sc->urb_int)
+		goto err1;
+
+	usb_fill_int_urb(sc->urb_int, sc->usb_dev,
+			 usb_rcvintpipe(sc->usb_dev, UEA_INTR_PIPE),
+			 intr, size, uea_intr, sc,
+			 sc->usb_dev->actconfig->interface[0]->altsetting[0].
+			 endpoint[0].desc.bInterval);
+
+	ret = usb_submit_urb(sc->urb_int, GFP_KERNEL);
+	if (ret < 0) {
+		uea_err(INS_TO_USBDEV(sc),
+		       "urb submission failed with error %d\n", ret);
+		goto err1;
+	}
+
+	/* Create worker thread, but don't start it here.  Start it after
+	 * all usbatm generic initialization is done.
+	 */
+	sc->kthread = kthread_create(uea_kthread, sc, "ueagle-atm");
+	if (IS_ERR(sc->kthread)) {
+		uea_err(INS_TO_USBDEV(sc), "failed to create thread\n");
+		goto err2;
+	}
+
+	uea_leaves(INS_TO_USBDEV(sc));
+	return 0;
+
+err2:
+	usb_kill_urb(sc->urb_int);
+err1:
+	usb_free_urb(sc->urb_int);
+	sc->urb_int = NULL;
+	kfree(intr);
+err0:
+	uea_leaves(INS_TO_USBDEV(sc));
+	return -ENOMEM;
+}
+
+/*
+ * Stop the modem : kill kernel thread and free data
+ */
+static void uea_stop(struct uea_softc *sc)
+{
+	int ret;
+	uea_enters(INS_TO_USBDEV(sc));
+	ret = kthread_stop(sc->kthread);
+	uea_dbg(INS_TO_USBDEV(sc), "kthread finish with status %d\n", ret);
+
+	uea_request(sc, UEA_SET_MODE, UEA_LOOPBACK_ON, 0, NULL);
+
+	usb_kill_urb(sc->urb_int);
+	kfree(sc->urb_int->transfer_buffer);
+	usb_free_urb(sc->urb_int);
+
+	/* flush the work item, when no one can schedule it */
+	flush_work(&sc->task);
+
+	release_firmware(sc->dsp_firm);
+	uea_leaves(INS_TO_USBDEV(sc));
+}
+
+/* syfs interface */
+static struct uea_softc *dev_to_uea(struct device *dev)
+{
+	struct usb_interface *intf;
+	struct usbatm_data *usbatm;
+
+	intf = to_usb_interface(dev);
+	if (!intf)
+		return NULL;
+
+	usbatm = usb_get_intfdata(intf);
+	if (!usbatm)
+		return NULL;
+
+	return usbatm->driver_data;
+}
+
+static ssize_t stat_status_show(struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	int ret = -ENODEV;
+	struct uea_softc *sc;
+
+	mutex_lock(&uea_mutex);
+	sc = dev_to_uea(dev);
+	if (!sc)
+		goto out;
+	ret = snprintf(buf, 10, "%08x\n", sc->stats.phy.state);
+out:
+	mutex_unlock(&uea_mutex);
+	return ret;
+}
+
+static ssize_t stat_status_store(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	int ret = -ENODEV;
+	struct uea_softc *sc;
+
+	mutex_lock(&uea_mutex);
+	sc = dev_to_uea(dev);
+	if (!sc)
+		goto out;
+	sc->reset = 1;
+	ret = count;
+out:
+	mutex_unlock(&uea_mutex);
+	return ret;
+}
+
+static DEVICE_ATTR_RW(stat_status);
+
+static ssize_t stat_human_status_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	int ret = -ENODEV;
+	int modem_state;
+	struct uea_softc *sc;
+
+	mutex_lock(&uea_mutex);
+	sc = dev_to_uea(dev);
+	if (!sc)
+		goto out;
+
+	if (UEA_CHIP_VERSION(sc) == EAGLE_IV) {
+		switch (sc->stats.phy.state) {
+		case 0x0:	/* not yet synchronized */
+		case 0x1:
+		case 0x3:
+		case 0x4:
+			modem_state = 0;
+			break;
+		case 0x5:	/* initialization */
+		case 0x6:
+		case 0x9:
+		case 0xa:
+			modem_state = 1;
+			break;
+		case 0x7:	/* operational */
+			modem_state = 2;
+			break;
+		case 0x2:	/* fail ... */
+			modem_state = 3;
+			break;
+		default:	/* unknown */
+			modem_state = 4;
+			break;
+		}
+	} else
+		modem_state = GET_STATUS(sc->stats.phy.state);
+
+	switch (modem_state) {
+	case 0:
+		ret = sprintf(buf, "Modem is booting\n");
+		break;
+	case 1:
+		ret = sprintf(buf, "Modem is initializing\n");
+		break;
+	case 2:
+		ret = sprintf(buf, "Modem is operational\n");
+		break;
+	case 3:
+		ret = sprintf(buf, "Modem synchronization failed\n");
+		break;
+	default:
+		ret = sprintf(buf, "Modem state is unknown\n");
+		break;
+	}
+out:
+	mutex_unlock(&uea_mutex);
+	return ret;
+}
+
+static DEVICE_ATTR_RO(stat_human_status);
+
+static ssize_t stat_delin_show(struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	int ret = -ENODEV;
+	struct uea_softc *sc;
+	char *delin = "GOOD";
+
+	mutex_lock(&uea_mutex);
+	sc = dev_to_uea(dev);
+	if (!sc)
+		goto out;
+
+	if (UEA_CHIP_VERSION(sc) == EAGLE_IV) {
+		if (sc->stats.phy.flags & 0x4000)
+			delin = "RESET";
+		else if (sc->stats.phy.flags & 0x0001)
+			delin = "LOSS";
+	} else {
+		if (sc->stats.phy.flags & 0x0C00)
+			delin = "ERROR";
+		else if (sc->stats.phy.flags & 0x0030)
+			delin = "LOSS";
+	}
+
+	ret = sprintf(buf, "%s\n", delin);
+out:
+	mutex_unlock(&uea_mutex);
+	return ret;
+}
+
+static DEVICE_ATTR_RO(stat_delin);
+
+#define UEA_ATTR(name, reset)					\
+								\
+static ssize_t stat_##name##_show(struct device *dev,		\
+		struct device_attribute *attr, char *buf)	\
+{								\
+	int ret = -ENODEV;					\
+	struct uea_softc *sc;					\
+								\
+	mutex_lock(&uea_mutex);					\
+	sc = dev_to_uea(dev);					\
+	if (!sc)						\
+		goto out;					\
+	ret = snprintf(buf, 10, "%08x\n", sc->stats.phy.name);	\
+	if (reset)						\
+		sc->stats.phy.name = 0;				\
+out:								\
+	mutex_unlock(&uea_mutex);				\
+	return ret;						\
+}								\
+								\
+static DEVICE_ATTR_RO(stat_##name)
+
+UEA_ATTR(mflags, 1);
+UEA_ATTR(vidcpe, 0);
+UEA_ATTR(usrate, 0);
+UEA_ATTR(dsrate, 0);
+UEA_ATTR(usattenuation, 0);
+UEA_ATTR(dsattenuation, 0);
+UEA_ATTR(usmargin, 0);
+UEA_ATTR(dsmargin, 0);
+UEA_ATTR(txflow, 0);
+UEA_ATTR(rxflow, 0);
+UEA_ATTR(uscorr, 0);
+UEA_ATTR(dscorr, 0);
+UEA_ATTR(usunc, 0);
+UEA_ATTR(dsunc, 0);
+UEA_ATTR(firmid, 0);
+
+/* Retrieve the device End System Identifier (MAC) */
+
+static int uea_getesi(struct uea_softc *sc, u_char *esi)
+{
+	unsigned char mac_str[2 * ETH_ALEN + 1];
+	int i;
+	if (usb_string
+	    (sc->usb_dev, sc->usb_dev->descriptor.iSerialNumber, mac_str,
+	     sizeof(mac_str)) != 2 * ETH_ALEN)
+		return 1;
+
+	for (i = 0; i < ETH_ALEN; i++)
+		esi[i] = hex_to_bin(mac_str[2 * i]) * 16 +
+			 hex_to_bin(mac_str[2 * i + 1]);
+
+	return 0;
+}
+
+/* ATM stuff */
+static int uea_atm_open(struct usbatm_data *usbatm, struct atm_dev *atm_dev)
+{
+	struct uea_softc *sc = usbatm->driver_data;
+
+	return uea_getesi(sc, atm_dev->esi);
+}
+
+static int uea_heavy(struct usbatm_data *usbatm, struct usb_interface *intf)
+{
+	struct uea_softc *sc = usbatm->driver_data;
+
+	wait_event_interruptible(sc->sync_q, IS_OPERATIONAL(sc));
+
+	return 0;
+
+}
+
+static int claim_interface(struct usb_device *usb_dev,
+			   struct usbatm_data *usbatm, int ifnum)
+{
+	int ret;
+	struct usb_interface *intf = usb_ifnum_to_if(usb_dev, ifnum);
+
+	if (!intf) {
+		uea_err(usb_dev, "interface %d not found\n", ifnum);
+		return -ENODEV;
+	}
+
+	ret = usb_driver_claim_interface(&uea_driver, intf, usbatm);
+	if (ret != 0)
+		uea_err(usb_dev, "can't claim interface %d, error %d\n", ifnum,
+		       ret);
+	return ret;
+}
+
+static struct attribute *attrs[] = {
+	&dev_attr_stat_status.attr,
+	&dev_attr_stat_mflags.attr,
+	&dev_attr_stat_human_status.attr,
+	&dev_attr_stat_delin.attr,
+	&dev_attr_stat_vidcpe.attr,
+	&dev_attr_stat_usrate.attr,
+	&dev_attr_stat_dsrate.attr,
+	&dev_attr_stat_usattenuation.attr,
+	&dev_attr_stat_dsattenuation.attr,
+	&dev_attr_stat_usmargin.attr,
+	&dev_attr_stat_dsmargin.attr,
+	&dev_attr_stat_txflow.attr,
+	&dev_attr_stat_rxflow.attr,
+	&dev_attr_stat_uscorr.attr,
+	&dev_attr_stat_dscorr.attr,
+	&dev_attr_stat_usunc.attr,
+	&dev_attr_stat_dsunc.attr,
+	&dev_attr_stat_firmid.attr,
+	NULL,
+};
+static const struct attribute_group attr_grp = {
+	.attrs = attrs,
+};
+
+static int uea_bind(struct usbatm_data *usbatm, struct usb_interface *intf,
+		   const struct usb_device_id *id)
+{
+	struct usb_device *usb = interface_to_usbdev(intf);
+	struct uea_softc *sc;
+	int ret, ifnum = intf->altsetting->desc.bInterfaceNumber;
+	unsigned int alt;
+
+	uea_enters(usb);
+
+	/* interface 0 is for firmware/monitoring */
+	if (ifnum != UEA_INTR_IFACE_NO)
+		return -ENODEV;
+
+	usbatm->flags = (sync_wait[modem_index] ? 0 : UDSL_SKIP_HEAVY_INIT);
+
+	/* interface 1 is for outbound traffic */
+	ret = claim_interface(usb, usbatm, UEA_US_IFACE_NO);
+	if (ret < 0)
+		return ret;
+
+	/* ADI930 has only 2 interfaces and inbound traffic is on interface 1 */
+	if (UEA_CHIP_VERSION(id) != ADI930) {
+		/* interface 2 is for inbound traffic */
+		ret = claim_interface(usb, usbatm, UEA_DS_IFACE_NO);
+		if (ret < 0)
+			return ret;
+	}
+
+	sc = kzalloc(sizeof(struct uea_softc), GFP_KERNEL);
+	if (!sc)
+		return -ENOMEM;
+
+	sc->usb_dev = usb;
+	usbatm->driver_data = sc;
+	sc->usbatm = usbatm;
+	sc->modem_index = (modem_index < NB_MODEM) ? modem_index++ : 0;
+	sc->driver_info = id->driver_info;
+
+	/* first try to use module parameter */
+	if (annex[sc->modem_index] == 1)
+		sc->annex = ANNEXA;
+	else if (annex[sc->modem_index] == 2)
+		sc->annex = ANNEXB;
+	/* try to autodetect annex */
+	else if (sc->driver_info & AUTO_ANNEX_A)
+		sc->annex = ANNEXA;
+	else if (sc->driver_info & AUTO_ANNEX_B)
+		sc->annex = ANNEXB;
+	else
+		sc->annex = (le16_to_cpu
+		(sc->usb_dev->descriptor.bcdDevice) & 0x80) ? ANNEXB : ANNEXA;
+
+	alt = altsetting[sc->modem_index];
+	/* ADI930 don't support iso */
+	if (UEA_CHIP_VERSION(id) != ADI930 && alt > 0) {
+		if (alt <= 8 &&
+			usb_set_interface(usb, UEA_DS_IFACE_NO, alt) == 0) {
+			uea_dbg(usb, "set alternate %u for 2 interface\n", alt);
+			uea_info(usb, "using iso mode\n");
+			usbatm->flags |= UDSL_USE_ISOC | UDSL_IGNORE_EILSEQ;
+		} else {
+			uea_err(usb, "setting alternate %u failed for "
+					"2 interface, using bulk mode\n", alt);
+		}
+	}
+
+	ret = sysfs_create_group(&intf->dev.kobj, &attr_grp);
+	if (ret < 0)
+		goto error;
+
+	ret = uea_boot(sc);
+	if (ret < 0)
+		goto error_rm_grp;
+
+	return 0;
+
+error_rm_grp:
+	sysfs_remove_group(&intf->dev.kobj, &attr_grp);
+error:
+	kfree(sc);
+	return ret;
+}
+
+static void uea_unbind(struct usbatm_data *usbatm, struct usb_interface *intf)
+{
+	struct uea_softc *sc = usbatm->driver_data;
+
+	sysfs_remove_group(&intf->dev.kobj, &attr_grp);
+	uea_stop(sc);
+	kfree(sc);
+}
+
+static struct usbatm_driver uea_usbatm_driver = {
+	.driver_name = "ueagle-atm",
+	.bind = uea_bind,
+	.atm_start = uea_atm_open,
+	.unbind = uea_unbind,
+	.heavy_init = uea_heavy,
+	.bulk_in = UEA_BULK_DATA_PIPE,
+	.bulk_out = UEA_BULK_DATA_PIPE,
+	.isoc_in = UEA_ISO_DATA_PIPE,
+};
+
+static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	struct usb_device *usb = interface_to_usbdev(intf);
+	int ret;
+
+	uea_enters(usb);
+	uea_info(usb, "ADSL device founded vid (%#X) pid (%#X) Rev (%#X): %s\n",
+		le16_to_cpu(usb->descriptor.idVendor),
+		le16_to_cpu(usb->descriptor.idProduct),
+		le16_to_cpu(usb->descriptor.bcdDevice),
+		chip_name[UEA_CHIP_VERSION(id)]);
+
+	usb_reset_device(usb);
+
+	if (UEA_IS_PREFIRM(id))
+		return uea_load_firmware(usb, UEA_CHIP_VERSION(id));
+
+	ret = usbatm_usb_probe(intf, id, &uea_usbatm_driver);
+	if (ret == 0) {
+		struct usbatm_data *usbatm = usb_get_intfdata(intf);
+		struct uea_softc *sc = usbatm->driver_data;
+
+		/* Ensure carrier is initialized to off as early as possible */
+		UPDATE_ATM_SIGNAL(ATM_PHY_SIG_LOST);
+
+		/* Only start the worker thread when all init is done */
+		wake_up_process(sc->kthread);
+	}
+
+	return ret;
+}
+
+static void uea_disconnect(struct usb_interface *intf)
+{
+	struct usb_device *usb = interface_to_usbdev(intf);
+	int ifnum = intf->altsetting->desc.bInterfaceNumber;
+	uea_enters(usb);
+
+	/* ADI930 has 2 interfaces and eagle 3 interfaces.
+	 * Pre-firmware device has one interface
+	 */
+	if (usb->config->desc.bNumInterfaces != 1 && ifnum == 0) {
+		mutex_lock(&uea_mutex);
+		usbatm_usb_disconnect(intf);
+		mutex_unlock(&uea_mutex);
+		uea_info(usb, "ADSL device removed\n");
+	}
+
+	uea_leaves(usb);
+}
+
+/*
+ * List of supported VID/PID
+ */
+static const struct usb_device_id uea_ids[] = {
+	{USB_DEVICE(ANALOG_VID,	ADI930_PID_PREFIRM),
+		.driver_info = ADI930 | PREFIRM},
+	{USB_DEVICE(ANALOG_VID,	ADI930_PID_PSTFIRM),
+		.driver_info = ADI930 | PSTFIRM},
+	{USB_DEVICE(ANALOG_VID,	EAGLE_I_PID_PREFIRM),
+		.driver_info = EAGLE_I | PREFIRM},
+	{USB_DEVICE(ANALOG_VID,	EAGLE_I_PID_PSTFIRM),
+		.driver_info = EAGLE_I | PSTFIRM},
+	{USB_DEVICE(ANALOG_VID,	EAGLE_II_PID_PREFIRM),
+		.driver_info = EAGLE_II | PREFIRM},
+	{USB_DEVICE(ANALOG_VID,	EAGLE_II_PID_PSTFIRM),
+		.driver_info = EAGLE_II | PSTFIRM},
+	{USB_DEVICE(ANALOG_VID,	EAGLE_IIC_PID_PREFIRM),
+		.driver_info = EAGLE_II | PREFIRM},
+	{USB_DEVICE(ANALOG_VID,	EAGLE_IIC_PID_PSTFIRM),
+		.driver_info = EAGLE_II | PSTFIRM},
+	{USB_DEVICE(ANALOG_VID,	EAGLE_III_PID_PREFIRM),
+		.driver_info = EAGLE_III | PREFIRM},
+	{USB_DEVICE(ANALOG_VID,	EAGLE_III_PID_PSTFIRM),
+		.driver_info = EAGLE_III | PSTFIRM},
+	{USB_DEVICE(ANALOG_VID,	EAGLE_IV_PID_PREFIRM),
+		.driver_info = EAGLE_IV | PREFIRM},
+	{USB_DEVICE(ANALOG_VID,	EAGLE_IV_PID_PSTFIRM),
+		.driver_info = EAGLE_IV | PSTFIRM},
+	{USB_DEVICE(DEVOLO_VID,	DEVOLO_EAGLE_I_A_PID_PREFIRM),
+		.driver_info = EAGLE_I | PREFIRM},
+	{USB_DEVICE(DEVOLO_VID,	DEVOLO_EAGLE_I_A_PID_PSTFIRM),
+		.driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_A},
+	{USB_DEVICE(DEVOLO_VID,	DEVOLO_EAGLE_I_B_PID_PREFIRM),
+		.driver_info = EAGLE_I | PREFIRM},
+	{USB_DEVICE(DEVOLO_VID,	DEVOLO_EAGLE_I_B_PID_PSTFIRM),
+		.driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_B},
+	{USB_DEVICE(DEVOLO_VID,	DEVOLO_EAGLE_II_A_PID_PREFIRM),
+		.driver_info = EAGLE_II | PREFIRM},
+	{USB_DEVICE(DEVOLO_VID,	DEVOLO_EAGLE_II_A_PID_PSTFIRM),
+		.driver_info = EAGLE_II | PSTFIRM | AUTO_ANNEX_A},
+	{USB_DEVICE(DEVOLO_VID,	DEVOLO_EAGLE_II_B_PID_PREFIRM),
+		.driver_info = EAGLE_II | PREFIRM},
+	{USB_DEVICE(DEVOLO_VID,	DEVOLO_EAGLE_II_B_PID_PSTFIRM),
+		.driver_info = EAGLE_II | PSTFIRM | AUTO_ANNEX_B},
+	{USB_DEVICE(ELSA_VID,	ELSA_PID_PREFIRM),
+		.driver_info = ADI930 | PREFIRM},
+	{USB_DEVICE(ELSA_VID,	ELSA_PID_PSTFIRM),
+		.driver_info = ADI930 | PSTFIRM},
+	{USB_DEVICE(ELSA_VID,	ELSA_PID_A_PREFIRM),
+		.driver_info = ADI930 | PREFIRM},
+	{USB_DEVICE(ELSA_VID,	ELSA_PID_A_PSTFIRM),
+		.driver_info = ADI930 | PSTFIRM | AUTO_ANNEX_A},
+	{USB_DEVICE(ELSA_VID,	ELSA_PID_B_PREFIRM),
+		.driver_info = ADI930 | PREFIRM},
+	{USB_DEVICE(ELSA_VID,	ELSA_PID_B_PSTFIRM),
+		.driver_info = ADI930 | PSTFIRM | AUTO_ANNEX_B},
+	{USB_DEVICE(USR_VID,	MILLER_A_PID_PREFIRM),
+		.driver_info = EAGLE_I | PREFIRM},
+	{USB_DEVICE(USR_VID,	MILLER_A_PID_PSTFIRM),
+		.driver_info = EAGLE_I | PSTFIRM  | AUTO_ANNEX_A},
+	{USB_DEVICE(USR_VID,	MILLER_B_PID_PREFIRM),
+		.driver_info = EAGLE_I | PREFIRM},
+	{USB_DEVICE(USR_VID,	MILLER_B_PID_PSTFIRM),
+		.driver_info = EAGLE_I | PSTFIRM  | AUTO_ANNEX_B},
+	{USB_DEVICE(USR_VID,	HEINEKEN_A_PID_PREFIRM),
+		.driver_info = EAGLE_I | PREFIRM},
+	{USB_DEVICE(USR_VID,	HEINEKEN_A_PID_PSTFIRM),
+		.driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_A},
+	{USB_DEVICE(USR_VID,	HEINEKEN_B_PID_PREFIRM),
+		.driver_info = EAGLE_I | PREFIRM},
+	{USB_DEVICE(USR_VID,	HEINEKEN_B_PID_PSTFIRM),
+		.driver_info = EAGLE_I | PSTFIRM | AUTO_ANNEX_B},
+	{}
+};
+
+/*
+ * USB driver descriptor
+ */
+static struct usb_driver uea_driver = {
+	.name = "ueagle-atm",
+	.id_table = uea_ids,
+	.probe = uea_probe,
+	.disconnect = uea_disconnect,
+};
+
+MODULE_DEVICE_TABLE(usb, uea_ids);
+
+module_usb_driver(uea_driver);
+
+MODULE_AUTHOR("Damien Bergamini/Matthieu Castet/Stanislaw W. Gruszka");
+MODULE_DESCRIPTION("ADI 930/Eagle USB ADSL Modem driver");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_FIRMWARE(EAGLE_FIRMWARE);
+MODULE_FIRMWARE(ADI930_FIRMWARE);
+MODULE_FIRMWARE(EAGLE_I_FIRMWARE);
+MODULE_FIRMWARE(EAGLE_II_FIRMWARE);
+MODULE_FIRMWARE(EAGLE_III_FIRMWARE);
+MODULE_FIRMWARE(EAGLE_IV_FIRMWARE);
+MODULE_FIRMWARE(DSP4I_FIRMWARE);
+MODULE_FIRMWARE(DSP4P_FIRMWARE);
+MODULE_FIRMWARE(DSP9I_FIRMWARE);
+MODULE_FIRMWARE(DSP9P_FIRMWARE);
+MODULE_FIRMWARE(DSPEI_FIRMWARE);
+MODULE_FIRMWARE(DSPEP_FIRMWARE);
+MODULE_FIRMWARE(FPGA930_FIRMWARE);
+MODULE_FIRMWARE(CMV4P_FIRMWARE);
+MODULE_FIRMWARE(CMV4PV2_FIRMWARE);
+MODULE_FIRMWARE(CMV4I_FIRMWARE);
+MODULE_FIRMWARE(CMV4IV2_FIRMWARE);
+MODULE_FIRMWARE(CMV9P_FIRMWARE);
+MODULE_FIRMWARE(CMV9PV2_FIRMWARE);
+MODULE_FIRMWARE(CMV9I_FIRMWARE);
+MODULE_FIRMWARE(CMV9IV2_FIRMWARE);
+MODULE_FIRMWARE(CMVEP_FIRMWARE);
+MODULE_FIRMWARE(CMVEPV2_FIRMWARE);
+MODULE_FIRMWARE(CMVEI_FIRMWARE);
+MODULE_FIRMWARE(CMVEIV2_FIRMWARE);
diff --git a/drivers/usb/atm/usbatm.c b/drivers/usb/atm/usbatm.c
new file mode 100644
index 0000000..dbea284
--- /dev/null
+++ b/drivers/usb/atm/usbatm.c
@@ -0,0 +1,1324 @@
+// SPDX-License-Identifier: GPL-2.0+
+/******************************************************************************
+ *  usbatm.c - Generic USB xDSL driver core
+ *
+ *  Copyright (C) 2001, Alcatel
+ *  Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas
+ *  Copyright (C) 2004, David Woodhouse, Roman Kagan
+ ******************************************************************************/
+
+/*
+ *  Written by Johan Verrept, Duncan Sands (duncan.sands@free.fr) and David Woodhouse
+ *
+ *  1.7+:	- See the check-in logs
+ *
+ *  1.6:	- No longer opens a connection if the firmware is not loaded
+ *  		- Added support for the speedtouch 330
+ *  		- Removed the limit on the number of devices
+ *  		- Module now autoloads on device plugin
+ *  		- Merged relevant parts of sarlib
+ *  		- Replaced the kernel thread with a tasklet
+ *  		- New packet transmission code
+ *  		- Changed proc file contents
+ *  		- Fixed all known SMP races
+ *  		- Many fixes and cleanups
+ *  		- Various fixes by Oliver Neukum (oliver@neukum.name)
+ *
+ *  1.5A:	- Version for inclusion in 2.5 series kernel
+ *		- Modifications by Richard Purdie (rpurdie@rpsys.net)
+ *		- made compatible with kernel 2.5.6 onwards by changing
+ *		usbatm_usb_send_data_context->urb to a pointer and adding code
+ *		to alloc and free it
+ *		- remove_wait_queue() added to usbatm_atm_processqueue_thread()
+ *
+ *  1.5:	- fixed memory leak when atmsar_decode_aal5 returned NULL.
+ *		(reported by stephen.robinson@zen.co.uk)
+ *
+ *  1.4:	- changed the spin_lock() under interrupt to spin_lock_irqsave()
+ *		- unlink all active send urbs of a vcc that is being closed.
+ *
+ *  1.3.1:	- added the version number
+ *
+ *  1.3:	- Added multiple send urb support
+ *		- fixed memory leak and vcc->tx_inuse starvation bug
+ *		  when not enough memory left in vcc.
+ *
+ *  1.2:	- Fixed race condition in usbatm_usb_send_data()
+ *  1.1:	- Turned off packet debugging
+ *
+ */
+
+#include "usbatm.h"
+
+#include <linux/uaccess.h>
+#include <linux/crc32.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/netdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/sched/signal.h>
+#include <linux/signal.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/kthread.h>
+#include <linux/ratelimit.h>
+
+#ifdef VERBOSE_DEBUG
+static int usbatm_print_packet(struct usbatm_data *instance, const unsigned char *data, int len);
+#define PACKETDEBUG(arg...)	usbatm_print_packet(arg)
+#define vdbg(arg...)		dev_dbg(arg)
+#else
+#define PACKETDEBUG(arg...)
+#define vdbg(arg...)
+#endif
+
+#define DRIVER_AUTHOR	"Johan Verrept, Duncan Sands <duncan.sands@free.fr>"
+#define DRIVER_DESC	"Generic USB ATM/DSL I/O"
+
+static const char usbatm_driver_name[] = "usbatm";
+
+#define UDSL_MAX_RCV_URBS		16
+#define UDSL_MAX_SND_URBS		16
+#define UDSL_MAX_BUF_SIZE		65536
+#define UDSL_DEFAULT_RCV_URBS		4
+#define UDSL_DEFAULT_SND_URBS		4
+#define UDSL_DEFAULT_RCV_BUF_SIZE	3392	/* 64 * ATM_CELL_SIZE */
+#define UDSL_DEFAULT_SND_BUF_SIZE	3392	/* 64 * ATM_CELL_SIZE */
+
+#define ATM_CELL_HEADER			(ATM_CELL_SIZE - ATM_CELL_PAYLOAD)
+
+#define THROTTLE_MSECS			100	/* delay to recover processing after urb submission fails */
+
+static unsigned int num_rcv_urbs = UDSL_DEFAULT_RCV_URBS;
+static unsigned int num_snd_urbs = UDSL_DEFAULT_SND_URBS;
+static unsigned int rcv_buf_bytes = UDSL_DEFAULT_RCV_BUF_SIZE;
+static unsigned int snd_buf_bytes = UDSL_DEFAULT_SND_BUF_SIZE;
+
+module_param(num_rcv_urbs, uint, S_IRUGO);
+MODULE_PARM_DESC(num_rcv_urbs,
+		 "Number of urbs used for reception (range: 0-"
+		 __MODULE_STRING(UDSL_MAX_RCV_URBS) ", default: "
+		 __MODULE_STRING(UDSL_DEFAULT_RCV_URBS) ")");
+
+module_param(num_snd_urbs, uint, S_IRUGO);
+MODULE_PARM_DESC(num_snd_urbs,
+		 "Number of urbs used for transmission (range: 0-"
+		 __MODULE_STRING(UDSL_MAX_SND_URBS) ", default: "
+		 __MODULE_STRING(UDSL_DEFAULT_SND_URBS) ")");
+
+module_param(rcv_buf_bytes, uint, S_IRUGO);
+MODULE_PARM_DESC(rcv_buf_bytes,
+		 "Size of the buffers used for reception, in bytes (range: 1-"
+		 __MODULE_STRING(UDSL_MAX_BUF_SIZE) ", default: "
+		 __MODULE_STRING(UDSL_DEFAULT_RCV_BUF_SIZE) ")");
+
+module_param(snd_buf_bytes, uint, S_IRUGO);
+MODULE_PARM_DESC(snd_buf_bytes,
+		 "Size of the buffers used for transmission, in bytes (range: 1-"
+		 __MODULE_STRING(UDSL_MAX_BUF_SIZE) ", default: "
+		 __MODULE_STRING(UDSL_DEFAULT_SND_BUF_SIZE) ")");
+
+
+/* receive */
+
+struct usbatm_vcc_data {
+	/* vpi/vci lookup */
+	struct list_head list;
+	short vpi;
+	int vci;
+	struct atm_vcc *vcc;
+
+	/* raw cell reassembly */
+	struct sk_buff *sarb;
+};
+
+
+/* send */
+
+struct usbatm_control {
+	struct atm_skb_data atm;
+	u32 len;
+	u32 crc;
+};
+
+#define UDSL_SKB(x)		((struct usbatm_control *)(x)->cb)
+
+
+/* ATM */
+
+static void usbatm_atm_dev_close(struct atm_dev *atm_dev);
+static int usbatm_atm_open(struct atm_vcc *vcc);
+static void usbatm_atm_close(struct atm_vcc *vcc);
+static int usbatm_atm_ioctl(struct atm_dev *atm_dev, unsigned int cmd, void __user *arg);
+static int usbatm_atm_send(struct atm_vcc *vcc, struct sk_buff *skb);
+static int usbatm_atm_proc_read(struct atm_dev *atm_dev, loff_t *pos, char *page);
+
+static const struct atmdev_ops usbatm_atm_devops = {
+	.dev_close	= usbatm_atm_dev_close,
+	.open		= usbatm_atm_open,
+	.close		= usbatm_atm_close,
+	.ioctl		= usbatm_atm_ioctl,
+	.send		= usbatm_atm_send,
+	.proc_read	= usbatm_atm_proc_read,
+	.owner		= THIS_MODULE,
+};
+
+
+/***********
+**  misc  **
+***********/
+
+static inline unsigned int usbatm_pdu_length(unsigned int length)
+{
+	length += ATM_CELL_PAYLOAD - 1 + ATM_AAL5_TRAILER;
+	return length - length % ATM_CELL_PAYLOAD;
+}
+
+static inline void usbatm_pop(struct atm_vcc *vcc, struct sk_buff *skb)
+{
+	if (vcc->pop)
+		vcc->pop(vcc, skb);
+	else
+		dev_kfree_skb_any(skb);
+}
+
+
+/***********
+**  urbs  **
+************/
+
+static struct urb *usbatm_pop_urb(struct usbatm_channel *channel)
+{
+	struct urb *urb;
+
+	spin_lock_irq(&channel->lock);
+	if (list_empty(&channel->list)) {
+		spin_unlock_irq(&channel->lock);
+		return NULL;
+	}
+
+	urb = list_entry(channel->list.next, struct urb, urb_list);
+	list_del(&urb->urb_list);
+	spin_unlock_irq(&channel->lock);
+
+	return urb;
+}
+
+static int usbatm_submit_urb(struct urb *urb)
+{
+	struct usbatm_channel *channel = urb->context;
+	int ret;
+
+	/* vdbg("%s: submitting urb 0x%p, size %u",
+	     __func__, urb, urb->transfer_buffer_length); */
+
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret) {
+		if (printk_ratelimit())
+			atm_warn(channel->usbatm, "%s: urb 0x%p submission failed (%d)!\n",
+				__func__, urb, ret);
+
+		/* consider all errors transient and return the buffer back to the queue */
+		urb->status = -EAGAIN;
+		spin_lock_irq(&channel->lock);
+
+		/* must add to the front when sending; doesn't matter when receiving */
+		list_add(&urb->urb_list, &channel->list);
+
+		spin_unlock_irq(&channel->lock);
+
+		/* make sure the channel doesn't stall */
+		mod_timer(&channel->delay, jiffies + msecs_to_jiffies(THROTTLE_MSECS));
+	}
+
+	return ret;
+}
+
+static void usbatm_complete(struct urb *urb)
+{
+	struct usbatm_channel *channel = urb->context;
+	unsigned long flags;
+	int status = urb->status;
+
+	/* vdbg("%s: urb 0x%p, status %d, actual_length %d",
+	     __func__, urb, status, urb->actual_length); */
+
+	/* usually in_interrupt(), but not always */
+	spin_lock_irqsave(&channel->lock, flags);
+
+	/* must add to the back when receiving; doesn't matter when sending */
+	list_add_tail(&urb->urb_list, &channel->list);
+
+	spin_unlock_irqrestore(&channel->lock, flags);
+
+	if (unlikely(status) &&
+			(!(channel->usbatm->flags & UDSL_IGNORE_EILSEQ) ||
+			 status != -EILSEQ)) {
+		if (status == -ESHUTDOWN)
+			return;
+
+		if (printk_ratelimit())
+			atm_warn(channel->usbatm, "%s: urb 0x%p failed (%d)!\n",
+				__func__, urb, status);
+		/* throttle processing in case of an error */
+		mod_timer(&channel->delay, jiffies + msecs_to_jiffies(THROTTLE_MSECS));
+	} else
+		tasklet_schedule(&channel->tasklet);
+}
+
+
+/*************
+**  decode  **
+*************/
+
+static inline struct usbatm_vcc_data *usbatm_find_vcc(struct usbatm_data *instance,
+						  short vpi, int vci)
+{
+	struct usbatm_vcc_data *vcc_data;
+
+	list_for_each_entry(vcc_data, &instance->vcc_list, list)
+		if ((vcc_data->vci == vci) && (vcc_data->vpi == vpi))
+			return vcc_data;
+	return NULL;
+}
+
+static void usbatm_extract_one_cell(struct usbatm_data *instance, unsigned char *source)
+{
+	struct atm_vcc *vcc;
+	struct sk_buff *sarb;
+	short vpi = ((source[0] & 0x0f) << 4)  | (source[1] >> 4);
+	int vci = ((source[1] & 0x0f) << 12) | (source[2] << 4) | (source[3] >> 4);
+	u8 pti = ((source[3] & 0xe) >> 1);
+
+	if ((vci != instance->cached_vci) || (vpi != instance->cached_vpi)) {
+		instance->cached_vpi = vpi;
+		instance->cached_vci = vci;
+
+		instance->cached_vcc = usbatm_find_vcc(instance, vpi, vci);
+
+		if (!instance->cached_vcc)
+			atm_rldbg(instance, "%s: unknown vpi/vci (%hd/%d)!\n", __func__, vpi, vci);
+	}
+
+	if (!instance->cached_vcc)
+		return;
+
+	vcc = instance->cached_vcc->vcc;
+
+	/* OAM F5 end-to-end */
+	if (pti == ATM_PTI_E2EF5) {
+		if (printk_ratelimit())
+			atm_warn(instance, "%s: OAM not supported (vpi %d, vci %d)!\n",
+				__func__, vpi, vci);
+		atomic_inc(&vcc->stats->rx_err);
+		return;
+	}
+
+	sarb = instance->cached_vcc->sarb;
+
+	if (sarb->tail + ATM_CELL_PAYLOAD > sarb->end) {
+		atm_rldbg(instance, "%s: buffer overrun (sarb->len %u, vcc: 0x%p)!\n",
+				__func__, sarb->len, vcc);
+		/* discard cells already received */
+		skb_trim(sarb, 0);
+	}
+
+	memcpy(skb_tail_pointer(sarb), source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD);
+	__skb_put(sarb, ATM_CELL_PAYLOAD);
+
+	if (pti & 1) {
+		struct sk_buff *skb;
+		unsigned int length;
+		unsigned int pdu_length;
+
+		length = (source[ATM_CELL_SIZE - 6] << 8) + source[ATM_CELL_SIZE - 5];
+
+		/* guard against overflow */
+		if (length > ATM_MAX_AAL5_PDU) {
+			atm_rldbg(instance, "%s: bogus length %u (vcc: 0x%p)!\n",
+				  __func__, length, vcc);
+			atomic_inc(&vcc->stats->rx_err);
+			goto out;
+		}
+
+		pdu_length = usbatm_pdu_length(length);
+
+		if (sarb->len < pdu_length) {
+			atm_rldbg(instance, "%s: bogus pdu_length %u (sarb->len: %u, vcc: 0x%p)!\n",
+				  __func__, pdu_length, sarb->len, vcc);
+			atomic_inc(&vcc->stats->rx_err);
+			goto out;
+		}
+
+		if (crc32_be(~0, skb_tail_pointer(sarb) - pdu_length, pdu_length) != 0xc704dd7b) {
+			atm_rldbg(instance, "%s: packet failed crc check (vcc: 0x%p)!\n",
+				  __func__, vcc);
+			atomic_inc(&vcc->stats->rx_err);
+			goto out;
+		}
+
+		vdbg(&instance->usb_intf->dev,
+		     "%s: got packet (length: %u, pdu_length: %u, vcc: 0x%p)",
+		     __func__, length, pdu_length, vcc);
+
+		skb = dev_alloc_skb(length);
+		if (!skb) {
+			if (printk_ratelimit())
+				atm_err(instance, "%s: no memory for skb (length: %u)!\n",
+					__func__, length);
+			atomic_inc(&vcc->stats->rx_drop);
+			goto out;
+		}
+
+		vdbg(&instance->usb_intf->dev,
+		     "%s: allocated new sk_buff (skb: 0x%p, skb->truesize: %u)",
+		     __func__, skb, skb->truesize);
+
+		if (!atm_charge(vcc, skb->truesize)) {
+			atm_rldbg(instance, "%s: failed atm_charge (skb->truesize: %u)!\n",
+				  __func__, skb->truesize);
+			dev_kfree_skb_any(skb);
+			goto out;	/* atm_charge increments rx_drop */
+		}
+
+		skb_copy_to_linear_data(skb,
+					skb_tail_pointer(sarb) - pdu_length,
+					length);
+		__skb_put(skb, length);
+
+		vdbg(&instance->usb_intf->dev,
+		     "%s: sending skb 0x%p, skb->len %u, skb->truesize %u",
+		     __func__, skb, skb->len, skb->truesize);
+
+		PACKETDEBUG(instance, skb->data, skb->len);
+
+		vcc->push(vcc, skb);
+
+		atomic_inc(&vcc->stats->rx);
+	out:
+		skb_trim(sarb, 0);
+	}
+}
+
+static void usbatm_extract_cells(struct usbatm_data *instance,
+		unsigned char *source, unsigned int avail_data)
+{
+	unsigned int stride = instance->rx_channel.stride;
+	unsigned int buf_usage = instance->buf_usage;
+
+	/* extract cells from incoming data, taking into account that
+	 * the length of avail data may not be a multiple of stride */
+
+	if (buf_usage > 0) {
+		/* we have a partially received atm cell */
+		unsigned char *cell_buf = instance->cell_buf;
+		unsigned int space_left = stride - buf_usage;
+
+		if (avail_data >= space_left) {
+			/* add new data and process cell */
+			memcpy(cell_buf + buf_usage, source, space_left);
+			source += space_left;
+			avail_data -= space_left;
+			usbatm_extract_one_cell(instance, cell_buf);
+			instance->buf_usage = 0;
+		} else {
+			/* not enough data to fill the cell */
+			memcpy(cell_buf + buf_usage, source, avail_data);
+			instance->buf_usage = buf_usage + avail_data;
+			return;
+		}
+	}
+
+	for (; avail_data >= stride; avail_data -= stride, source += stride)
+		usbatm_extract_one_cell(instance, source);
+
+	if (avail_data > 0) {
+		/* length was not a multiple of stride -
+		 * save remaining data for next call */
+		memcpy(instance->cell_buf, source, avail_data);
+		instance->buf_usage = avail_data;
+	}
+}
+
+
+/*************
+**  encode  **
+*************/
+
+static unsigned int usbatm_write_cells(struct usbatm_data *instance,
+				       struct sk_buff *skb,
+				       u8 *target, unsigned int avail_space)
+{
+	struct usbatm_control *ctrl = UDSL_SKB(skb);
+	struct atm_vcc *vcc = ctrl->atm.vcc;
+	unsigned int bytes_written;
+	unsigned int stride = instance->tx_channel.stride;
+
+	for (bytes_written = 0; bytes_written < avail_space && ctrl->len;
+	     bytes_written += stride, target += stride) {
+		unsigned int data_len = min_t(unsigned int, skb->len, ATM_CELL_PAYLOAD);
+		unsigned int left = ATM_CELL_PAYLOAD - data_len;
+		u8 *ptr = target;
+
+		ptr[0] = vcc->vpi >> 4;
+		ptr[1] = (vcc->vpi << 4) | (vcc->vci >> 12);
+		ptr[2] = vcc->vci >> 4;
+		ptr[3] = vcc->vci << 4;
+		ptr[4] = 0xec;
+		ptr += ATM_CELL_HEADER;
+
+		skb_copy_from_linear_data(skb, ptr, data_len);
+		ptr += data_len;
+		__skb_pull(skb, data_len);
+
+		if (!left)
+			continue;
+
+		memset(ptr, 0, left);
+
+		if (left >= ATM_AAL5_TRAILER) {	/* trailer will go in this cell */
+			u8 *trailer = target + ATM_CELL_SIZE - ATM_AAL5_TRAILER;
+			/* trailer[0] = 0;		UU = 0 */
+			/* trailer[1] = 0;		CPI = 0 */
+			trailer[2] = ctrl->len >> 8;
+			trailer[3] = ctrl->len;
+
+			ctrl->crc = ~crc32_be(ctrl->crc, ptr, left - 4);
+
+			trailer[4] = ctrl->crc >> 24;
+			trailer[5] = ctrl->crc >> 16;
+			trailer[6] = ctrl->crc >> 8;
+			trailer[7] = ctrl->crc;
+
+			target[3] |= 0x2;	/* adjust PTI */
+
+			ctrl->len = 0;		/* tag this skb finished */
+		} else
+			ctrl->crc = crc32_be(ctrl->crc, ptr, left);
+	}
+
+	return bytes_written;
+}
+
+
+/**************
+**  receive  **
+**************/
+
+static void usbatm_rx_process(unsigned long data)
+{
+	struct usbatm_data *instance = (struct usbatm_data *)data;
+	struct urb *urb;
+
+	while ((urb = usbatm_pop_urb(&instance->rx_channel))) {
+		vdbg(&instance->usb_intf->dev,
+		     "%s: processing urb 0x%p", __func__, urb);
+
+		if (usb_pipeisoc(urb->pipe)) {
+			unsigned char *merge_start = NULL;
+			unsigned int merge_length = 0;
+			const unsigned int packet_size = instance->rx_channel.packet_size;
+			int i;
+
+			for (i = 0; i < urb->number_of_packets; i++) {
+				if (!urb->iso_frame_desc[i].status) {
+					unsigned int actual_length = urb->iso_frame_desc[i].actual_length;
+
+					if (!merge_length)
+						merge_start = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+					merge_length += actual_length;
+					if (merge_length && (actual_length < packet_size)) {
+						usbatm_extract_cells(instance, merge_start, merge_length);
+						merge_length = 0;
+					}
+				} else {
+					atm_rldbg(instance, "%s: status %d in frame %d!\n", __func__, urb->status, i);
+					if (merge_length)
+						usbatm_extract_cells(instance, merge_start, merge_length);
+					merge_length = 0;
+					instance->buf_usage = 0;
+				}
+			}
+
+			if (merge_length)
+				usbatm_extract_cells(instance, merge_start, merge_length);
+		} else
+			if (!urb->status)
+				usbatm_extract_cells(instance, urb->transfer_buffer, urb->actual_length);
+			else
+				instance->buf_usage = 0;
+
+		if (usbatm_submit_urb(urb))
+			return;
+	}
+}
+
+
+/***********
+**  send  **
+***********/
+
+static void usbatm_tx_process(unsigned long data)
+{
+	struct usbatm_data *instance = (struct usbatm_data *)data;
+	struct sk_buff *skb = instance->current_skb;
+	struct urb *urb = NULL;
+	const unsigned int buf_size = instance->tx_channel.buf_size;
+	unsigned int bytes_written = 0;
+	u8 *buffer = NULL;
+
+	if (!skb)
+		skb = skb_dequeue(&instance->sndqueue);
+
+	while (skb) {
+		if (!urb) {
+			urb = usbatm_pop_urb(&instance->tx_channel);
+			if (!urb)
+				break;		/* no more senders */
+			buffer = urb->transfer_buffer;
+			bytes_written = (urb->status == -EAGAIN) ?
+				urb->transfer_buffer_length : 0;
+		}
+
+		bytes_written += usbatm_write_cells(instance, skb,
+						  buffer + bytes_written,
+						  buf_size - bytes_written);
+
+		vdbg(&instance->usb_intf->dev,
+		     "%s: wrote %u bytes from skb 0x%p to urb 0x%p",
+		     __func__, bytes_written, skb, urb);
+
+		if (!UDSL_SKB(skb)->len) {
+			struct atm_vcc *vcc = UDSL_SKB(skb)->atm.vcc;
+
+			usbatm_pop(vcc, skb);
+			atomic_inc(&vcc->stats->tx);
+
+			skb = skb_dequeue(&instance->sndqueue);
+		}
+
+		if (bytes_written == buf_size || (!skb && bytes_written)) {
+			urb->transfer_buffer_length = bytes_written;
+
+			if (usbatm_submit_urb(urb))
+				break;
+			urb = NULL;
+		}
+	}
+
+	instance->current_skb = skb;
+}
+
+static void usbatm_cancel_send(struct usbatm_data *instance,
+			       struct atm_vcc *vcc)
+{
+	struct sk_buff *skb, *n;
+
+	spin_lock_irq(&instance->sndqueue.lock);
+	skb_queue_walk_safe(&instance->sndqueue, skb, n) {
+		if (UDSL_SKB(skb)->atm.vcc == vcc) {
+			atm_dbg(instance, "%s: popping skb 0x%p\n", __func__, skb);
+			__skb_unlink(skb, &instance->sndqueue);
+			usbatm_pop(vcc, skb);
+		}
+	}
+	spin_unlock_irq(&instance->sndqueue.lock);
+
+	tasklet_disable(&instance->tx_channel.tasklet);
+	if ((skb = instance->current_skb) && (UDSL_SKB(skb)->atm.vcc == vcc)) {
+		atm_dbg(instance, "%s: popping current skb (0x%p)\n", __func__, skb);
+		instance->current_skb = NULL;
+		usbatm_pop(vcc, skb);
+	}
+	tasklet_enable(&instance->tx_channel.tasklet);
+}
+
+static int usbatm_atm_send(struct atm_vcc *vcc, struct sk_buff *skb)
+{
+	struct usbatm_data *instance = vcc->dev->dev_data;
+	struct usbatm_control *ctrl = UDSL_SKB(skb);
+	int err;
+
+	/* racy disconnection check - fine */
+	if (!instance || instance->disconnected) {
+#ifdef VERBOSE_DEBUG
+		printk_ratelimited(KERN_DEBUG "%s: %s!\n", __func__, instance ? "disconnected" : "NULL instance");
+#endif
+		err = -ENODEV;
+		goto fail;
+	}
+
+	if (vcc->qos.aal != ATM_AAL5) {
+		atm_rldbg(instance, "%s: unsupported ATM type %d!\n", __func__, vcc->qos.aal);
+		err = -EINVAL;
+		goto fail;
+	}
+
+	if (skb->len > ATM_MAX_AAL5_PDU) {
+		atm_rldbg(instance, "%s: packet too long (%d vs %d)!\n",
+				__func__, skb->len, ATM_MAX_AAL5_PDU);
+		err = -EINVAL;
+		goto fail;
+	}
+
+	PACKETDEBUG(instance, skb->data, skb->len);
+
+	/* initialize the control block */
+	ctrl->atm.vcc = vcc;
+	ctrl->len = skb->len;
+	ctrl->crc = crc32_be(~0, skb->data, skb->len);
+
+	skb_queue_tail(&instance->sndqueue, skb);
+	tasklet_schedule(&instance->tx_channel.tasklet);
+
+	return 0;
+
+ fail:
+	usbatm_pop(vcc, skb);
+	return err;
+}
+
+
+/********************
+**  bean counting  **
+********************/
+
+static void usbatm_destroy_instance(struct kref *kref)
+{
+	struct usbatm_data *instance = container_of(kref, struct usbatm_data, refcount);
+
+	tasklet_kill(&instance->rx_channel.tasklet);
+	tasklet_kill(&instance->tx_channel.tasklet);
+	usb_put_dev(instance->usb_dev);
+	kfree(instance);
+}
+
+static void usbatm_get_instance(struct usbatm_data *instance)
+{
+	kref_get(&instance->refcount);
+}
+
+static void usbatm_put_instance(struct usbatm_data *instance)
+{
+	kref_put(&instance->refcount, usbatm_destroy_instance);
+}
+
+
+/**********
+**  ATM  **
+**********/
+
+static void usbatm_atm_dev_close(struct atm_dev *atm_dev)
+{
+	struct usbatm_data *instance = atm_dev->dev_data;
+
+	if (!instance)
+		return;
+
+	atm_dev->dev_data = NULL; /* catch bugs */
+	usbatm_put_instance(instance);	/* taken in usbatm_atm_init */
+}
+
+static int usbatm_atm_proc_read(struct atm_dev *atm_dev, loff_t *pos, char *page)
+{
+	struct usbatm_data *instance = atm_dev->dev_data;
+	int left = *pos;
+
+	if (!instance)
+		return -ENODEV;
+
+	if (!left--)
+		return sprintf(page, "%s\n", instance->description);
+
+	if (!left--)
+		return sprintf(page, "MAC: %pM\n", atm_dev->esi);
+
+	if (!left--)
+		return sprintf(page,
+			       "AAL5: tx %d ( %d err ), rx %d ( %d err, %d drop )\n",
+			       atomic_read(&atm_dev->stats.aal5.tx),
+			       atomic_read(&atm_dev->stats.aal5.tx_err),
+			       atomic_read(&atm_dev->stats.aal5.rx),
+			       atomic_read(&atm_dev->stats.aal5.rx_err),
+			       atomic_read(&atm_dev->stats.aal5.rx_drop));
+
+	if (!left--) {
+		if (instance->disconnected)
+			return sprintf(page, "Disconnected\n");
+		else
+			switch (atm_dev->signal) {
+			case ATM_PHY_SIG_FOUND:
+				return sprintf(page, "Line up\n");
+			case ATM_PHY_SIG_LOST:
+				return sprintf(page, "Line down\n");
+			default:
+				return sprintf(page, "Line state unknown\n");
+			}
+	}
+
+	return 0;
+}
+
+static int usbatm_atm_open(struct atm_vcc *vcc)
+{
+	struct usbatm_data *instance = vcc->dev->dev_data;
+	struct usbatm_vcc_data *new = NULL;
+	int ret;
+	int vci = vcc->vci;
+	short vpi = vcc->vpi;
+
+	if (!instance)
+		return -ENODEV;
+
+	/* only support AAL5 */
+	if ((vcc->qos.aal != ATM_AAL5)) {
+		atm_warn(instance, "%s: unsupported ATM type %d!\n", __func__, vcc->qos.aal);
+		return -EINVAL;
+	}
+
+	/* sanity checks */
+	if ((vcc->qos.rxtp.max_sdu < 0) || (vcc->qos.rxtp.max_sdu > ATM_MAX_AAL5_PDU)) {
+		atm_dbg(instance, "%s: max_sdu %d out of range!\n", __func__, vcc->qos.rxtp.max_sdu);
+		return -EINVAL;
+	}
+
+	mutex_lock(&instance->serialize);	/* vs self, usbatm_atm_close, usbatm_usb_disconnect */
+
+	if (instance->disconnected) {
+		atm_dbg(instance, "%s: disconnected!\n", __func__);
+		ret = -ENODEV;
+		goto fail;
+	}
+
+	if (usbatm_find_vcc(instance, vpi, vci)) {
+		atm_dbg(instance, "%s: %hd/%d already in use!\n", __func__, vpi, vci);
+		ret = -EADDRINUSE;
+		goto fail;
+	}
+
+	new = kzalloc(sizeof(struct usbatm_vcc_data), GFP_KERNEL);
+	if (!new) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	new->vcc = vcc;
+	new->vpi = vpi;
+	new->vci = vci;
+
+	new->sarb = alloc_skb(usbatm_pdu_length(vcc->qos.rxtp.max_sdu), GFP_KERNEL);
+	if (!new->sarb) {
+		atm_err(instance, "%s: no memory for SAR buffer!\n", __func__);
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	vcc->dev_data = new;
+
+	tasklet_disable(&instance->rx_channel.tasklet);
+	instance->cached_vcc = new;
+	instance->cached_vpi = vpi;
+	instance->cached_vci = vci;
+	list_add(&new->list, &instance->vcc_list);
+	tasklet_enable(&instance->rx_channel.tasklet);
+
+	set_bit(ATM_VF_ADDR, &vcc->flags);
+	set_bit(ATM_VF_PARTIAL, &vcc->flags);
+	set_bit(ATM_VF_READY, &vcc->flags);
+
+	mutex_unlock(&instance->serialize);
+
+	atm_dbg(instance, "%s: allocated vcc data 0x%p\n", __func__, new);
+
+	return 0;
+
+fail:
+	kfree(new);
+	mutex_unlock(&instance->serialize);
+	return ret;
+}
+
+static void usbatm_atm_close(struct atm_vcc *vcc)
+{
+	struct usbatm_data *instance = vcc->dev->dev_data;
+	struct usbatm_vcc_data *vcc_data = vcc->dev_data;
+
+	if (!instance || !vcc_data)
+		return;
+
+	usbatm_cancel_send(instance, vcc);
+
+	mutex_lock(&instance->serialize);	/* vs self, usbatm_atm_open, usbatm_usb_disconnect */
+
+	tasklet_disable(&instance->rx_channel.tasklet);
+	if (instance->cached_vcc == vcc_data) {
+		instance->cached_vcc = NULL;
+		instance->cached_vpi = ATM_VPI_UNSPEC;
+		instance->cached_vci = ATM_VCI_UNSPEC;
+	}
+	list_del(&vcc_data->list);
+	tasklet_enable(&instance->rx_channel.tasklet);
+
+	kfree_skb(vcc_data->sarb);
+	vcc_data->sarb = NULL;
+
+	kfree(vcc_data);
+	vcc->dev_data = NULL;
+
+	vcc->vpi = ATM_VPI_UNSPEC;
+	vcc->vci = ATM_VCI_UNSPEC;
+	clear_bit(ATM_VF_READY, &vcc->flags);
+	clear_bit(ATM_VF_PARTIAL, &vcc->flags);
+	clear_bit(ATM_VF_ADDR, &vcc->flags);
+
+	mutex_unlock(&instance->serialize);
+}
+
+static int usbatm_atm_ioctl(struct atm_dev *atm_dev, unsigned int cmd,
+			  void __user *arg)
+{
+	struct usbatm_data *instance = atm_dev->dev_data;
+
+	if (!instance || instance->disconnected)
+		return -ENODEV;
+
+	switch (cmd) {
+	case ATM_QUERYLOOP:
+		return put_user(ATM_LM_NONE, (int __user *)arg) ? -EFAULT : 0;
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+static int usbatm_atm_init(struct usbatm_data *instance)
+{
+	struct atm_dev *atm_dev;
+	int ret, i;
+
+	/* ATM init.  The ATM initialization scheme suffers from an intrinsic race
+	 * condition: callbacks we register can be executed at once, before we have
+	 * initialized the struct atm_dev.  To protect against this, all callbacks
+	 * abort if atm_dev->dev_data is NULL. */
+	atm_dev = atm_dev_register(instance->driver_name,
+				   &instance->usb_intf->dev, &usbatm_atm_devops,
+				   -1, NULL);
+	if (!atm_dev) {
+		usb_err(instance, "%s: failed to register ATM device!\n", __func__);
+		return -1;
+	}
+
+	instance->atm_dev = atm_dev;
+
+	atm_dev->ci_range.vpi_bits = ATM_CI_MAX;
+	atm_dev->ci_range.vci_bits = ATM_CI_MAX;
+	atm_dev->signal = ATM_PHY_SIG_UNKNOWN;
+
+	/* temp init ATM device, set to 128kbit */
+	atm_dev->link_rate = 128 * 1000 / 424;
+
+	if (instance->driver->atm_start && ((ret = instance->driver->atm_start(instance, atm_dev)) < 0)) {
+		atm_err(instance, "%s: atm_start failed: %d!\n", __func__, ret);
+		goto fail;
+	}
+
+	usbatm_get_instance(instance);	/* dropped in usbatm_atm_dev_close */
+
+	/* ready for ATM callbacks */
+	mb();
+	atm_dev->dev_data = instance;
+
+	/* submit all rx URBs */
+	for (i = 0; i < num_rcv_urbs; i++)
+		usbatm_submit_urb(instance->urbs[i]);
+
+	return 0;
+
+ fail:
+	instance->atm_dev = NULL;
+	atm_dev_deregister(atm_dev); /* usbatm_atm_dev_close will eventually be called */
+	return ret;
+}
+
+
+/**********
+**  USB  **
+**********/
+
+static int usbatm_do_heavy_init(void *arg)
+{
+	struct usbatm_data *instance = arg;
+	int ret;
+
+	allow_signal(SIGTERM);
+	complete(&instance->thread_started);
+
+	ret = instance->driver->heavy_init(instance, instance->usb_intf);
+
+	if (!ret)
+		ret = usbatm_atm_init(instance);
+
+	mutex_lock(&instance->serialize);
+	instance->thread = NULL;
+	mutex_unlock(&instance->serialize);
+
+	complete_and_exit(&instance->thread_exited, ret);
+}
+
+static int usbatm_heavy_init(struct usbatm_data *instance)
+{
+	struct task_struct *t;
+
+	t = kthread_create(usbatm_do_heavy_init, instance, "%s",
+			instance->driver->driver_name);
+	if (IS_ERR(t)) {
+		usb_err(instance, "%s: failed to create kernel_thread (%ld)!\n",
+				__func__, PTR_ERR(t));
+		return PTR_ERR(t);
+	}
+
+	instance->thread = t;
+	wake_up_process(t);
+	wait_for_completion(&instance->thread_started);
+
+	return 0;
+}
+
+static void usbatm_tasklet_schedule(struct timer_list *t)
+{
+	struct usbatm_channel *channel = from_timer(channel, t, delay);
+
+	tasklet_schedule(&channel->tasklet);
+}
+
+static void usbatm_init_channel(struct usbatm_channel *channel)
+{
+	spin_lock_init(&channel->lock);
+	INIT_LIST_HEAD(&channel->list);
+	timer_setup(&channel->delay, usbatm_tasklet_schedule, 0);
+}
+
+int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id,
+		     struct usbatm_driver *driver)
+{
+	struct device *dev = &intf->dev;
+	struct usb_device *usb_dev = interface_to_usbdev(intf);
+	struct usbatm_data *instance;
+	char *buf;
+	int error = -ENOMEM;
+	int i, length;
+	unsigned int maxpacket, num_packets;
+
+	/* instance init */
+	instance = kzalloc(sizeof(*instance) + sizeof(struct urb *) * (num_rcv_urbs + num_snd_urbs), GFP_KERNEL);
+	if (!instance)
+		return -ENOMEM;
+
+	/* public fields */
+
+	instance->driver = driver;
+	strlcpy(instance->driver_name, driver->driver_name,
+		sizeof(instance->driver_name));
+
+	instance->usb_dev = usb_dev;
+	instance->usb_intf = intf;
+
+	buf = instance->description;
+	length = sizeof(instance->description);
+
+	if ((i = usb_string(usb_dev, usb_dev->descriptor.iProduct, buf, length)) < 0)
+		goto bind;
+
+	buf += i;
+	length -= i;
+
+	i = scnprintf(buf, length, " (");
+	buf += i;
+	length -= i;
+
+	if (length <= 0 || (i = usb_make_path(usb_dev, buf, length)) < 0)
+		goto bind;
+
+	buf += i;
+	length -= i;
+
+	snprintf(buf, length, ")");
+
+ bind:
+	if (driver->bind && (error = driver->bind(instance, intf, id)) < 0) {
+			dev_err(dev, "%s: bind failed: %d!\n", __func__, error);
+			goto fail_free;
+	}
+
+	/* private fields */
+
+	kref_init(&instance->refcount);		/* dropped in usbatm_usb_disconnect */
+	mutex_init(&instance->serialize);
+
+	instance->thread = NULL;
+	init_completion(&instance->thread_started);
+	init_completion(&instance->thread_exited);
+
+	INIT_LIST_HEAD(&instance->vcc_list);
+	skb_queue_head_init(&instance->sndqueue);
+
+	usbatm_init_channel(&instance->rx_channel);
+	usbatm_init_channel(&instance->tx_channel);
+	tasklet_init(&instance->rx_channel.tasklet, usbatm_rx_process, (unsigned long)instance);
+	tasklet_init(&instance->tx_channel.tasklet, usbatm_tx_process, (unsigned long)instance);
+	instance->rx_channel.stride = ATM_CELL_SIZE + driver->rx_padding;
+	instance->tx_channel.stride = ATM_CELL_SIZE + driver->tx_padding;
+	instance->rx_channel.usbatm = instance->tx_channel.usbatm = instance;
+
+	if ((instance->flags & UDSL_USE_ISOC) && driver->isoc_in)
+		instance->rx_channel.endpoint = usb_rcvisocpipe(usb_dev, driver->isoc_in);
+	else
+		instance->rx_channel.endpoint = usb_rcvbulkpipe(usb_dev, driver->bulk_in);
+
+	instance->tx_channel.endpoint = usb_sndbulkpipe(usb_dev, driver->bulk_out);
+
+	/* tx buffer size must be a positive multiple of the stride */
+	instance->tx_channel.buf_size = max(instance->tx_channel.stride,
+			snd_buf_bytes - (snd_buf_bytes % instance->tx_channel.stride));
+
+	/* rx buffer size must be a positive multiple of the endpoint maxpacket */
+	maxpacket = usb_maxpacket(usb_dev, instance->rx_channel.endpoint, 0);
+
+	if ((maxpacket < 1) || (maxpacket > UDSL_MAX_BUF_SIZE)) {
+		dev_err(dev, "%s: invalid endpoint %02x!\n", __func__,
+				usb_pipeendpoint(instance->rx_channel.endpoint));
+		error = -EINVAL;
+		goto fail_unbind;
+	}
+
+	num_packets = max(1U, (rcv_buf_bytes + maxpacket / 2) / maxpacket); /* round */
+
+	if (num_packets * maxpacket > UDSL_MAX_BUF_SIZE)
+		num_packets--;
+
+	instance->rx_channel.buf_size = num_packets * maxpacket;
+	instance->rx_channel.packet_size = maxpacket;
+
+	for (i = 0; i < 2; i++) {
+		struct usbatm_channel *channel = i ?
+			&instance->tx_channel : &instance->rx_channel;
+
+		dev_dbg(dev, "%s: using %d byte buffer for %s channel 0x%p\n",
+			__func__, channel->buf_size, i ? "tx" : "rx", channel);
+	}
+
+	/* initialize urbs */
+
+	for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) {
+		u8 *buffer;
+		struct usbatm_channel *channel = i < num_rcv_urbs ?
+			&instance->rx_channel : &instance->tx_channel;
+		struct urb *urb;
+		unsigned int iso_packets = usb_pipeisoc(channel->endpoint) ? channel->buf_size / channel->packet_size : 0;
+
+		urb = usb_alloc_urb(iso_packets, GFP_KERNEL);
+		if (!urb) {
+			error = -ENOMEM;
+			goto fail_unbind;
+		}
+
+		instance->urbs[i] = urb;
+
+		/* zero the tx padding to avoid leaking information */
+		buffer = kzalloc(channel->buf_size, GFP_KERNEL);
+		if (!buffer) {
+			error = -ENOMEM;
+			goto fail_unbind;
+		}
+
+		usb_fill_bulk_urb(urb, instance->usb_dev, channel->endpoint,
+				  buffer, channel->buf_size, usbatm_complete, channel);
+		if (iso_packets) {
+			int j;
+			urb->interval = 1;
+			urb->transfer_flags = URB_ISO_ASAP;
+			urb->number_of_packets = iso_packets;
+			for (j = 0; j < iso_packets; j++) {
+				urb->iso_frame_desc[j].offset = channel->packet_size * j;
+				urb->iso_frame_desc[j].length = channel->packet_size;
+			}
+		}
+
+		/* put all tx URBs on the list of spares */
+		if (i >= num_rcv_urbs)
+			list_add_tail(&urb->urb_list, &channel->list);
+
+		vdbg(&intf->dev, "%s: alloced buffer 0x%p buf size %u urb 0x%p",
+		     __func__, urb->transfer_buffer, urb->transfer_buffer_length, urb);
+	}
+
+	instance->cached_vpi = ATM_VPI_UNSPEC;
+	instance->cached_vci = ATM_VCI_UNSPEC;
+	instance->cell_buf = kmalloc(instance->rx_channel.stride, GFP_KERNEL);
+
+	if (!instance->cell_buf) {
+		error = -ENOMEM;
+		goto fail_unbind;
+	}
+
+	if (!(instance->flags & UDSL_SKIP_HEAVY_INIT) && driver->heavy_init) {
+		error = usbatm_heavy_init(instance);
+	} else {
+		complete(&instance->thread_exited);	/* pretend that heavy_init was run */
+		error = usbatm_atm_init(instance);
+	}
+
+	if (error < 0)
+		goto fail_unbind;
+
+	usb_get_dev(usb_dev);
+	usb_set_intfdata(intf, instance);
+
+	return 0;
+
+ fail_unbind:
+	if (instance->driver->unbind)
+		instance->driver->unbind(instance, intf);
+ fail_free:
+	kfree(instance->cell_buf);
+
+	for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) {
+		if (instance->urbs[i])
+			kfree(instance->urbs[i]->transfer_buffer);
+		usb_free_urb(instance->urbs[i]);
+	}
+
+	kfree(instance);
+
+	return error;
+}
+EXPORT_SYMBOL_GPL(usbatm_usb_probe);
+
+void usbatm_usb_disconnect(struct usb_interface *intf)
+{
+	struct device *dev = &intf->dev;
+	struct usbatm_data *instance = usb_get_intfdata(intf);
+	struct usbatm_vcc_data *vcc_data;
+	int i;
+
+	if (!instance) {
+		dev_dbg(dev, "%s: NULL instance!\n", __func__);
+		return;
+	}
+
+	usb_set_intfdata(intf, NULL);
+
+	mutex_lock(&instance->serialize);
+	instance->disconnected = 1;
+	if (instance->thread != NULL)
+		send_sig(SIGTERM, instance->thread, 1);
+	mutex_unlock(&instance->serialize);
+
+	wait_for_completion(&instance->thread_exited);
+
+	mutex_lock(&instance->serialize);
+	list_for_each_entry(vcc_data, &instance->vcc_list, list)
+		vcc_release_async(vcc_data->vcc, -EPIPE);
+	mutex_unlock(&instance->serialize);
+
+	tasklet_disable(&instance->rx_channel.tasklet);
+	tasklet_disable(&instance->tx_channel.tasklet);
+
+	for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++)
+		usb_kill_urb(instance->urbs[i]);
+
+	del_timer_sync(&instance->rx_channel.delay);
+	del_timer_sync(&instance->tx_channel.delay);
+
+	/* turn usbatm_[rt]x_process into something close to a no-op */
+	/* no need to take the spinlock */
+	INIT_LIST_HEAD(&instance->rx_channel.list);
+	INIT_LIST_HEAD(&instance->tx_channel.list);
+
+	tasklet_enable(&instance->rx_channel.tasklet);
+	tasklet_enable(&instance->tx_channel.tasklet);
+
+	if (instance->atm_dev && instance->driver->atm_stop)
+		instance->driver->atm_stop(instance, instance->atm_dev);
+
+	if (instance->driver->unbind)
+		instance->driver->unbind(instance, intf);
+
+	instance->driver_data = NULL;
+
+	for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) {
+		kfree(instance->urbs[i]->transfer_buffer);
+		usb_free_urb(instance->urbs[i]);
+	}
+
+	kfree(instance->cell_buf);
+
+	/* ATM finalize */
+	if (instance->atm_dev) {
+		atm_dev_deregister(instance->atm_dev);
+		instance->atm_dev = NULL;
+	}
+
+	usbatm_put_instance(instance);	/* taken in usbatm_usb_probe */
+}
+EXPORT_SYMBOL_GPL(usbatm_usb_disconnect);
+
+
+/***********
+**  init  **
+***********/
+
+static int __init usbatm_usb_init(void)
+{
+	if (sizeof(struct usbatm_control) > FIELD_SIZEOF(struct sk_buff, cb)) {
+		printk(KERN_ERR "%s unusable with this kernel!\n", usbatm_driver_name);
+		return -EIO;
+	}
+
+	if ((num_rcv_urbs > UDSL_MAX_RCV_URBS)
+	    || (num_snd_urbs > UDSL_MAX_SND_URBS)
+	    || (rcv_buf_bytes < 1)
+	    || (rcv_buf_bytes > UDSL_MAX_BUF_SIZE)
+	    || (snd_buf_bytes < 1)
+	    || (snd_buf_bytes > UDSL_MAX_BUF_SIZE))
+		return -EINVAL;
+
+	return 0;
+}
+module_init(usbatm_usb_init);
+
+static void __exit usbatm_usb_exit(void)
+{
+}
+module_exit(usbatm_usb_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/************
+**  debug  **
+************/
+
+#ifdef VERBOSE_DEBUG
+static int usbatm_print_packet(struct usbatm_data *instance,
+			       const unsigned char *data, int len)
+{
+	unsigned char buffer[256];
+	int i = 0, j = 0;
+
+	for (i = 0; i < len;) {
+		buffer[0] = '\0';
+		sprintf(buffer, "%.3d :", i);
+		for (j = 0; (j < 16) && (i < len); j++, i++)
+			sprintf(buffer, "%s %2.2x", buffer, data[i]);
+		dev_dbg(&instance->usb_intf->dev, "%s", buffer);
+	}
+	return i;
+}
+#endif
diff --git a/drivers/usb/atm/usbatm.h b/drivers/usb/atm/usbatm.h
new file mode 100644
index 0000000..d3bdc4c
--- /dev/null
+++ b/drivers/usb/atm/usbatm.h
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0+
+/******************************************************************************
+ *  usbatm.h - Generic USB xDSL driver core
+ *
+ *  Copyright (C) 2001, Alcatel
+ *  Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas
+ *  Copyright (C) 2004, David Woodhouse
+ ******************************************************************************/
+
+#ifndef	_USBATM_H_
+#define	_USBATM_H_
+
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/stringify.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/ratelimit.h>
+
+/*
+#define VERBOSE_DEBUG
+*/
+
+#define usb_err(instance, format, arg...)	\
+	dev_err(&(instance)->usb_intf->dev , format , ## arg)
+#define usb_info(instance, format, arg...)	\
+	dev_info(&(instance)->usb_intf->dev , format , ## arg)
+#define usb_warn(instance, format, arg...)	\
+	dev_warn(&(instance)->usb_intf->dev , format , ## arg)
+#define usb_dbg(instance, format, arg...)	\
+	dev_dbg(&(instance)->usb_intf->dev , format , ## arg)
+
+/* FIXME: move to dev_* once ATM is driver model aware */
+#define atm_printk(level, instance, format, arg...)	\
+	printk(level "ATM dev %d: " format ,		\
+	(instance)->atm_dev->number , ## arg)
+
+#define atm_err(instance, format, arg...)	\
+	atm_printk(KERN_ERR, instance , format , ## arg)
+#define atm_info(instance, format, arg...)	\
+	atm_printk(KERN_INFO, instance , format , ## arg)
+#define atm_warn(instance, format, arg...)	\
+	atm_printk(KERN_WARNING, instance , format , ## arg)
+#define atm_dbg(instance, format, ...)					\
+	pr_debug("ATM dev %d: " format,					\
+		 (instance)->atm_dev->number, ##__VA_ARGS__)
+#define atm_rldbg(instance, format, ...)				\
+	pr_debug_ratelimited("ATM dev %d: " format,			\
+			     (instance)->atm_dev->number, ##__VA_ARGS__)
+
+/* flags, set by mini-driver in bind() */
+
+#define UDSL_SKIP_HEAVY_INIT	(1<<0)
+#define UDSL_USE_ISOC		(1<<1)
+#define UDSL_IGNORE_EILSEQ	(1<<2)
+
+
+/* mini driver */
+
+struct usbatm_data;
+
+/*
+*  Assuming all methods exist and succeed, they are called in this order:
+*
+*	bind, heavy_init, atm_start, ..., atm_stop, unbind
+*/
+
+struct usbatm_driver {
+	const char *driver_name;
+
+	/* init device ... can sleep, or cause probe() failure */
+	int (*bind) (struct usbatm_data *, struct usb_interface *,
+		     const struct usb_device_id *id);
+
+	/* additional device initialization that is too slow to be done in probe() */
+	int (*heavy_init) (struct usbatm_data *, struct usb_interface *);
+
+	/* cleanup device ... can sleep, but can't fail */
+	void (*unbind) (struct usbatm_data *, struct usb_interface *);
+
+	/* init ATM device ... can sleep, or cause ATM initialization failure */
+	int (*atm_start) (struct usbatm_data *, struct atm_dev *);
+
+	/* cleanup ATM device ... can sleep, but can't fail */
+	void (*atm_stop) (struct usbatm_data *, struct atm_dev *);
+
+	int bulk_in;	/* bulk rx endpoint */
+	int isoc_in;	/* isochronous rx endpoint */
+	int bulk_out;	/* bulk tx endpoint */
+
+	unsigned rx_padding;
+	unsigned tx_padding;
+};
+
+extern int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id,
+		struct usbatm_driver *driver);
+extern void usbatm_usb_disconnect(struct usb_interface *intf);
+
+
+struct usbatm_channel {
+	int endpoint;			/* usb pipe */
+	unsigned int stride;		/* ATM cell size + padding */
+	unsigned int buf_size;		/* urb buffer size */
+	unsigned int packet_size;	/* endpoint maxpacket */
+	spinlock_t lock;
+	struct list_head list;
+	struct tasklet_struct tasklet;
+	struct timer_list delay;
+	struct usbatm_data *usbatm;
+};
+
+/* main driver data */
+
+struct usbatm_data {
+	/******************
+	*  public fields  *
+	******************/
+
+	/* mini driver */
+	struct usbatm_driver *driver;
+	void *driver_data;
+	char driver_name[16];
+	unsigned int flags; /* set by mini-driver in bind() */
+
+	/* USB device */
+	struct usb_device *usb_dev;
+	struct usb_interface *usb_intf;
+	char description[64];
+
+	/* ATM device */
+	struct atm_dev *atm_dev;
+
+	/********************************
+	*  private fields - do not use  *
+	********************************/
+
+	struct kref refcount;
+	struct mutex serialize;
+	int disconnected;
+
+	/* heavy init */
+	struct task_struct *thread;
+	struct completion thread_started;
+	struct completion thread_exited;
+
+	/* ATM device */
+	struct list_head vcc_list;
+
+	struct usbatm_channel rx_channel;
+	struct usbatm_channel tx_channel;
+
+	struct sk_buff_head sndqueue;
+	struct sk_buff *current_skb;	/* being emptied */
+
+	struct usbatm_vcc_data *cached_vcc;
+	int cached_vci;
+	short cached_vpi;
+
+	unsigned char *cell_buf;	/* holds partial rx cell */
+	unsigned int buf_usage;
+
+	struct urb *urbs[0];
+};
+
+static inline void *to_usbatm_driver_data(struct usb_interface *intf)
+{
+	struct usbatm_data *usbatm_instance;
+
+	if (intf == NULL)
+		return NULL;
+
+	usbatm_instance = usb_get_intfdata(intf);
+
+	if (usbatm_instance == NULL) /* set NULL before unbind() */
+		return NULL;
+
+	return usbatm_instance->driver_data; /* set NULL after unbind() */
+}
+
+#endif	/* _USBATM_H_ */
diff --git a/drivers/usb/atm/xusbatm.c b/drivers/usb/atm/xusbatm.c
new file mode 100644
index 0000000..ffc9810
--- /dev/null
+++ b/drivers/usb/atm/xusbatm.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0+
+/******************************************************************************
+ *  xusbatm.c -	dumb usbatm-based driver for modems initialized in userspace
+ *
+ *  Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru)
+ ******************************************************************************/
+
+#include <linux/module.h>
+#include <linux/etherdevice.h>		/* for eth_random_addr() */
+
+#include "usbatm.h"
+
+
+#define XUSBATM_DRIVERS_MAX	8
+
+#define XUSBATM_PARM(name, type, parmtype, desc) \
+	static type name[XUSBATM_DRIVERS_MAX]; \
+	static unsigned int num_##name; \
+	module_param_array(name, parmtype, &num_##name, 0444); \
+	MODULE_PARM_DESC(name, desc)
+
+XUSBATM_PARM(vendor, unsigned short, ushort, "USB device vendor");
+XUSBATM_PARM(product, unsigned short, ushort, "USB device product");
+
+XUSBATM_PARM(rx_endpoint, unsigned char, byte, "rx endpoint number");
+XUSBATM_PARM(tx_endpoint, unsigned char, byte, "tx endpoint number");
+XUSBATM_PARM(rx_padding, unsigned char, byte, "rx padding (default 0)");
+XUSBATM_PARM(tx_padding, unsigned char, byte, "tx padding (default 0)");
+XUSBATM_PARM(rx_altsetting, unsigned char, byte, "rx altsetting (default 0)");
+XUSBATM_PARM(tx_altsetting, unsigned char, byte, "rx altsetting (default 0)");
+
+static const char xusbatm_driver_name[] = "xusbatm";
+
+static struct usbatm_driver xusbatm_drivers[XUSBATM_DRIVERS_MAX];
+static struct usb_device_id xusbatm_usb_ids[XUSBATM_DRIVERS_MAX + 1];
+static struct usb_driver xusbatm_usb_driver;
+
+static struct usb_interface *xusbatm_find_intf(struct usb_device *usb_dev, int altsetting, u8 ep)
+{
+	struct usb_host_interface *alt;
+	struct usb_interface *intf;
+	int i, j;
+
+	for (i = 0; i < usb_dev->actconfig->desc.bNumInterfaces; i++)
+		if ((intf = usb_dev->actconfig->interface[i]) && (alt = usb_altnum_to_altsetting(intf, altsetting)))
+			for (j = 0; j < alt->desc.bNumEndpoints; j++)
+				if (alt->endpoint[j].desc.bEndpointAddress == ep)
+					return intf;
+	return NULL;
+}
+
+static int xusbatm_capture_intf(struct usbatm_data *usbatm, struct usb_device *usb_dev,
+		struct usb_interface *intf, int altsetting, int claim)
+{
+	int ifnum = intf->altsetting->desc.bInterfaceNumber;
+	int ret;
+
+	if (claim && (ret = usb_driver_claim_interface(&xusbatm_usb_driver, intf, usbatm))) {
+		usb_err(usbatm, "%s: failed to claim interface %2d (%d)!\n", __func__, ifnum, ret);
+		return ret;
+	}
+	ret = usb_set_interface(usb_dev, ifnum, altsetting);
+	if (ret) {
+		usb_err(usbatm, "%s: altsetting %2d for interface %2d failed (%d)!\n", __func__, altsetting, ifnum, ret);
+		return ret;
+	}
+	return 0;
+}
+
+static void xusbatm_release_intf(struct usb_device *usb_dev, struct usb_interface *intf, int claimed)
+{
+	if (claimed) {
+		usb_set_intfdata(intf, NULL);
+		usb_driver_release_interface(&xusbatm_usb_driver, intf);
+	}
+}
+
+static int xusbatm_bind(struct usbatm_data *usbatm,
+			struct usb_interface *intf, const struct usb_device_id *id)
+{
+	struct usb_device *usb_dev = interface_to_usbdev(intf);
+	int drv_ix = id - xusbatm_usb_ids;
+	int rx_alt = rx_altsetting[drv_ix];
+	int tx_alt = tx_altsetting[drv_ix];
+	struct usb_interface *rx_intf = xusbatm_find_intf(usb_dev, rx_alt, rx_endpoint[drv_ix]);
+	struct usb_interface *tx_intf = xusbatm_find_intf(usb_dev, tx_alt, tx_endpoint[drv_ix]);
+	int ret;
+
+	usb_dbg(usbatm, "%s: binding driver %d: vendor %04x product %04x"
+		" rx: ep %02x padd %d alt %2d tx: ep %02x padd %d alt %2d\n",
+		__func__, drv_ix, vendor[drv_ix], product[drv_ix],
+		rx_endpoint[drv_ix], rx_padding[drv_ix], rx_alt,
+		tx_endpoint[drv_ix], tx_padding[drv_ix], tx_alt);
+
+	if (!rx_intf || !tx_intf) {
+		if (!rx_intf)
+			usb_dbg(usbatm, "%s: no interface contains endpoint %02x in altsetting %2d\n",
+				__func__, rx_endpoint[drv_ix], rx_alt);
+		if (!tx_intf)
+			usb_dbg(usbatm, "%s: no interface contains endpoint %02x in altsetting %2d\n",
+				__func__, tx_endpoint[drv_ix], tx_alt);
+		return -ENODEV;
+	}
+
+	if ((rx_intf != intf) && (tx_intf != intf))
+		return -ENODEV;
+
+	if ((rx_intf == tx_intf) && (rx_alt != tx_alt)) {
+		usb_err(usbatm, "%s: altsettings clash on interface %2d (%2d vs %2d)!\n", __func__,
+				rx_intf->altsetting->desc.bInterfaceNumber, rx_alt, tx_alt);
+		return -EINVAL;
+	}
+
+	usb_dbg(usbatm, "%s: rx If#=%2d; tx If#=%2d\n", __func__,
+			rx_intf->altsetting->desc.bInterfaceNumber,
+			tx_intf->altsetting->desc.bInterfaceNumber);
+
+	ret = xusbatm_capture_intf(usbatm, usb_dev, rx_intf, rx_alt, rx_intf != intf);
+	if (ret)
+		return ret;
+
+	if ((tx_intf != rx_intf) && (ret = xusbatm_capture_intf(usbatm, usb_dev, tx_intf, tx_alt, tx_intf != intf))) {
+		xusbatm_release_intf(usb_dev, rx_intf, rx_intf != intf);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void xusbatm_unbind(struct usbatm_data *usbatm,
+			   struct usb_interface *intf)
+{
+	struct usb_device *usb_dev = interface_to_usbdev(intf);
+	int i;
+
+	usb_dbg(usbatm, "%s entered\n", __func__);
+
+	for (i = 0; i < usb_dev->actconfig->desc.bNumInterfaces; i++) {
+		struct usb_interface *cur_intf = usb_dev->actconfig->interface[i];
+
+		if (cur_intf && (usb_get_intfdata(cur_intf) == usbatm)) {
+			usb_set_intfdata(cur_intf, NULL);
+			usb_driver_release_interface(&xusbatm_usb_driver, cur_intf);
+		}
+	}
+}
+
+static int xusbatm_atm_start(struct usbatm_data *usbatm,
+			     struct atm_dev *atm_dev)
+{
+	atm_dbg(usbatm, "%s entered\n", __func__);
+
+	/* use random MAC as we've no way to get it from the device */
+	eth_random_addr(atm_dev->esi);
+
+	return 0;
+}
+
+
+static int xusbatm_usb_probe(struct usb_interface *intf,
+			     const struct usb_device_id *id)
+{
+	return usbatm_usb_probe(intf, id,
+				xusbatm_drivers + (id - xusbatm_usb_ids));
+}
+
+static struct usb_driver xusbatm_usb_driver = {
+	.name		= xusbatm_driver_name,
+	.probe		= xusbatm_usb_probe,
+	.disconnect	= usbatm_usb_disconnect,
+	.id_table	= xusbatm_usb_ids
+};
+
+static int __init xusbatm_init(void)
+{
+	int i;
+
+	if (!num_vendor ||
+	    num_vendor != num_product ||
+	    num_vendor != num_rx_endpoint ||
+	    num_vendor != num_tx_endpoint) {
+		printk(KERN_WARNING "xusbatm: malformed module parameters\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < num_vendor; i++) {
+		rx_endpoint[i] |= USB_DIR_IN;
+		tx_endpoint[i] &= USB_ENDPOINT_NUMBER_MASK;
+
+		xusbatm_usb_ids[i].match_flags	= USB_DEVICE_ID_MATCH_DEVICE;
+		xusbatm_usb_ids[i].idVendor	= vendor[i];
+		xusbatm_usb_ids[i].idProduct	= product[i];
+
+		xusbatm_drivers[i].driver_name	= xusbatm_driver_name;
+		xusbatm_drivers[i].bind		= xusbatm_bind;
+		xusbatm_drivers[i].unbind	= xusbatm_unbind;
+		xusbatm_drivers[i].atm_start	= xusbatm_atm_start;
+		xusbatm_drivers[i].bulk_in	= rx_endpoint[i];
+		xusbatm_drivers[i].bulk_out	= tx_endpoint[i];
+		xusbatm_drivers[i].rx_padding	= rx_padding[i];
+		xusbatm_drivers[i].tx_padding	= tx_padding[i];
+	}
+
+	return usb_register(&xusbatm_usb_driver);
+}
+module_init(xusbatm_init);
+
+static void __exit xusbatm_exit(void)
+{
+	usb_deregister(&xusbatm_usb_driver);
+}
+module_exit(xusbatm_exit);
+
+MODULE_AUTHOR("Roman Kagan, Duncan Sands");
+MODULE_DESCRIPTION("Driver for USB ADSL modems initialized in userspace");
+MODULE_LICENSE("GPL");