summaryrefslogtreecommitdiffstats
path: root/kernel/drivers/usb/atm
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/drivers/usb/atm')
-rw-r--r--kernel/drivers/usb/atm/Kconfig68
-rw-r--r--kernel/drivers/usb/atm/Makefile8
-rw-r--r--kernel/drivers/usb/atm/cxacru.c1380
-rw-r--r--kernel/drivers/usb/atm/speedtch.c960
-rw-r--r--kernel/drivers/usb/atm/ueagle-atm.c2807
-rw-r--r--kernel/drivers/usb/atm/usbatm.c1344
-rw-r--r--kernel/drivers/usb/atm/usbatm.h199
-rw-r--r--kernel/drivers/usb/atm/xusbatm.c229
8 files changed, 6995 insertions, 0 deletions
diff --git a/kernel/drivers/usb/atm/Kconfig b/kernel/drivers/usb/atm/Kconfig
new file mode 100644
index 000000000..0f922942a
--- /dev/null
+++ b/kernel/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/kernel/drivers/usb/atm/Makefile b/kernel/drivers/usb/atm/Makefile
new file mode 100644
index 000000000..ac278946b
--- /dev/null
+++ b/kernel/drivers/usb/atm/Makefile
@@ -0,0 +1,8 @@
+#
+# 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/kernel/drivers/usb/atm/cxacru.c b/kernel/drivers/usb/atm/cxacru.c
new file mode 100644
index 000000000..813d4d3a5
--- /dev/null
+++ b/kernel/drivers/usb/atm/cxacru.c
@@ -0,0 +1,1380 @@
+/******************************************************************************
+ * 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
+ *
+ * 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.
+ *
+ ******************************************************************************/
+
+/*
+ * 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_VERSION "0.4"
+#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(_name, S_IRUGO, cxacru_sysfs_show_##_name, NULL)
+
+#define CXACRU_CMD_INIT(_name) \
+static DEVICE_ATTR(_name, S_IWUSR | S_IRUGO, \
+ cxacru_sysfs_show_##_name, cxacru_sysfs_store_##_name)
+
+#define CXACRU_SET_INIT(_name) \
+static DEVICE_ATTR(_name, S_IWUSR, \
+ NULL, cxacru_sysfs_store_##_name)
+
+#define CXACRU_ATTR_INIT(_value, _type, _name) \
+static ssize_t cxacru_sysfs_show_##_name(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 cxacru_sysfs_show_mac_address(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 cxacru_sysfs_show_adsl_state(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 cxacru_sysfs_store_adsl_state(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;
+ 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 cxacru_sysfs_store_adsl_config(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 < 0 || index > 0x7f)
+ 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);
+}
+
+static void cxacru_timeout_kill(unsigned long data)
+{
+ usb_unlink_urb((struct urb *) data);
+}
+
+static int cxacru_start_wait_urb(struct urb *urb, struct completion *done,
+ int *actual_length)
+{
+ struct timer_list timer;
+
+ init_timer(&timer);
+ timer.expires = jiffies + msecs_to_jiffies(CMD_TIMEOUT);
+ timer.data = (unsigned long) urb;
+ timer.function = cxacru_timeout_kill;
+ add_timer(&timer);
+ wait_for_completion(done);
+ del_timer_sync(&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;
+ 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) {
+ usb_dbg(usbatm_instance, "cxacru_bind: no memory for instance data\n");
+ 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) {
+ usb_dbg(usbatm_instance, "cxacru_bind: no memory for rcv_urb\n");
+ ret = -ENOMEM;
+ goto fail;
+ }
+ instance->snd_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!instance->snd_urb) {
+ usb_dbg(usbatm_instance, "cxacru_bind: no memory for snd_urb\n");
+ 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");
+MODULE_VERSION(DRIVER_VERSION);
diff --git a/kernel/drivers/usb/atm/speedtch.c b/kernel/drivers/usb/atm/speedtch.c
new file mode 100644
index 000000000..0dc8c06a7
--- /dev/null
+++ b/kernel/drivers/usb/atm/speedtch.c
@@ -0,0 +1,960 @@
+/******************************************************************************
+ * 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
+ *
+ * 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.
+ *
+ ******************************************************************************/
+
+#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_VERSION "1.10"
+#define DRIVER_DESC "Alcatel SpeedTouch USB driver version " DRIVER_VERSION
+
+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__);
+
+ if (!(buffer = (unsigned char *)__get_free_page(GFP_KERNEL))) {
+ 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(unsigned long data)
+{
+ struct speedtch_instance_data *instance = (void *)data;
+
+ 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(unsigned long data)
+{
+ struct speedtch_instance_data *instance = (void *)data;
+ 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;
+ }
+
+ if ((int_urb = instance->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:
+ if ((int_urb = instance->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 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++)
+ if ((cur_intf = usb_ifnum_to_if(usb_dev, i))) {
+ 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;
+ }
+
+ if (!(data_intf = usb_ifnum_to_if(usb_dev, INTERFACE_DATA))) {
+ 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) {
+ usb_err(usbatm, "%s: no memory for instance data!\n", __func__);
+ 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);
+ init_timer(&instance->status_check_timer);
+
+ instance->status_check_timer.function = speedtch_status_poll;
+ instance->status_check_timer.data = (unsigned long)instance;
+ instance->last_status = 0xff;
+ instance->poll_delay = MIN_POLL_DELAY;
+
+ init_timer(&instance->resubmit_timer);
+ instance->resubmit_timer.function = speedtch_resubmit_int;
+ instance->resubmit_timer.data = (unsigned long)instance;
+
+ 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");
+MODULE_VERSION(DRIVER_VERSION);
diff --git a/kernel/drivers/usb/atm/ueagle-atm.c b/kernel/drivers/usb/atm/ueagle-atm.c
new file mode 100644
index 000000000..888998a7f
--- /dev/null
+++ b/kernel/drivers/usb/atm/ueagle-atm.c
@@ -0,0 +1,2807 @@
+/*-
+ * 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;
+
+ kparam_block_sysfs_write(cmv_file);
+ /* 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);
+ kparam_unblock_sysfs_write(cmv_file);
+}
+
+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) {
+ uea_err(INS_TO_USBDEV(sc),
+ "cannot allocate interrupt package\n");
+ goto err0;
+ }
+
+ sc->urb_int = usb_alloc_urb(0, GFP_KERNEL);
+ if (!sc->urb_int) {
+ uea_err(INS_TO_USBDEV(sc), "cannot allocate interrupt URB\n");
+ 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 submition 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 read_status(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 reboot(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(stat_status, S_IWUSR | S_IRUGO, read_status, reboot);
+
+static ssize_t read_human_status(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(stat_human_status, S_IRUGO, read_human_status, NULL);
+
+static ssize_t read_delin(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(stat_delin, S_IRUGO, read_delin, NULL);
+
+#define UEA_ATTR(name, reset) \
+ \
+static ssize_t read_##name(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(stat_##name, S_IRUGO, read_##name, NULL)
+
+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 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) {
+ uea_err(usb, "uea_init: not enough memory !\n");
+ 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/kernel/drivers/usb/atm/usbatm.c b/kernel/drivers/usb/atm/usbatm.c
new file mode 100644
index 000000000..dada0146c
--- /dev/null
+++ b/kernel/drivers/usb/atm/usbatm.c
@@ -0,0 +1,1344 @@
+/******************************************************************************
+ * 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
+ *
+ * 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.
+ *
+ ******************************************************************************/
+
+/*
+ * 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 <asm/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.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_VERSION "1.10"
+#define DRIVER_DESC "Generic USB ATM/DSL I/O, version " DRIVER_VERSION
+
+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 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);
+
+ if (!(skb = dev_alloc_skb(length))) {
+ 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;
+ }
+
+ if (!(new = kzalloc(sizeof(struct usbatm_vcc_data), GFP_KERNEL))) {
+ atm_err(instance, "%s: no memory for vcc_data!\n", __func__);
+ 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(unsigned long data)
+{
+ tasklet_schedule((struct tasklet_struct *) data);
+}
+
+static void usbatm_init_channel(struct usbatm_channel *channel)
+{
+ spin_lock_init(&channel->lock);
+ INIT_LIST_HEAD(&channel->list);
+ channel->delay.function = usbatm_tasklet_schedule;
+ channel->delay.data = (unsigned long) &channel->tasklet;
+ init_timer(&channel->delay);
+}
+
+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) {
+ dev_err(dev, "%s: no memory for instance data!\n", __func__);
+ 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) {
+ dev_err(dev, "%s: no memory for urb %d!\n", __func__, i);
+ 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) {
+ dev_err(dev, "%s: no memory for buffer %d!\n", __func__, i);
+ 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) {
+ dev_err(dev, "%s: no memory for cell buffer!\n", __func__);
+ 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");
+MODULE_VERSION(DRIVER_VERSION);
+
+/************
+** 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/kernel/drivers/usb/atm/usbatm.h b/kernel/drivers/usb/atm/usbatm.h
new file mode 100644
index 000000000..f3eecd967
--- /dev/null
+++ b/kernel/drivers/usb/atm/usbatm.h
@@ -0,0 +1,199 @@
+/******************************************************************************
+ * usbatm.h - Generic USB xDSL driver core
+ *
+ * Copyright (C) 2001, Alcatel
+ * Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas
+ * Copyright (C) 2004, David Woodhouse
+ *
+ * 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.
+ *
+ ******************************************************************************/
+
+#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/kernel/drivers/usb/atm/xusbatm.c b/kernel/drivers/usb/atm/xusbatm.c
new file mode 100644
index 000000000..b3b1bb78b
--- /dev/null
+++ b/kernel/drivers/usb/atm/xusbatm.c
@@ -0,0 +1,229 @@
+/******************************************************************************
+ * xusbatm.c - dumb usbatm-based driver for modems initialized in userspace
+ *
+ * Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru)
+ *
+ * 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.
+ *
+ ******************************************************************************/
+
+#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;
+ }
+ if ((ret = usb_set_interface(usb_dev, ifnum, altsetting))) {
+ 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);
+
+ if ((ret = xusbatm_capture_intf(usbatm, usb_dev, rx_intf, rx_alt, rx_intf != intf)))
+ 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");
+MODULE_VERSION("0.1");