15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0+
21b0e6146SDuncan Sands /******************************************************************************
31b0e6146SDuncan Sands * cxacru.c - driver for USB ADSL modems based on
41b0e6146SDuncan Sands * Conexant AccessRunner chipset
51b0e6146SDuncan Sands *
61b0e6146SDuncan Sands * Copyright (C) 2004 David Woodhouse, Duncan Sands, Roman Kagan
71b0e6146SDuncan Sands * Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru)
86a02c996SSimon Arlott * Copyright (C) 2007 Simon Arlott
930fa3d8eSSimon Arlott * Copyright (C) 2009 Simon Arlott
101b0e6146SDuncan Sands ******************************************************************************/
111b0e6146SDuncan Sands
121b0e6146SDuncan Sands /*
131b0e6146SDuncan Sands * Credit is due for Josep Comas, who created the original patch to speedtch.c
141b0e6146SDuncan Sands * to support the different padding used by the AccessRunner (now generalized
151b0e6146SDuncan Sands * into usbatm), and the userspace firmware loading utility.
161b0e6146SDuncan Sands */
171b0e6146SDuncan Sands
181b0e6146SDuncan Sands #include <linux/module.h>
191b0e6146SDuncan Sands #include <linux/moduleparam.h>
201b0e6146SDuncan Sands #include <linux/kernel.h>
211b0e6146SDuncan Sands #include <linux/timer.h>
221b0e6146SDuncan Sands #include <linux/errno.h>
231b0e6146SDuncan Sands #include <linux/slab.h>
24fa70fe44SSimon Arlott #include <linux/device.h>
251b0e6146SDuncan Sands #include <linux/firmware.h>
26ab3c81ffSArjan van de Ven #include <linux/mutex.h>
27fd05e720SAl Viro #include <asm/unaligned.h>
281b0e6146SDuncan Sands
291b0e6146SDuncan Sands #include "usbatm.h"
301b0e6146SDuncan Sands
31fa70fe44SSimon Arlott #define DRIVER_AUTHOR "Roman Kagan, David Woodhouse, Duncan Sands, Simon Arlott"
321b0e6146SDuncan Sands #define DRIVER_DESC "Conexant AccessRunner ADSL USB modem driver"
331b0e6146SDuncan Sands
341b0e6146SDuncan Sands static const char cxacru_driver_name[] = "cxacru";
351b0e6146SDuncan Sands
361b0e6146SDuncan Sands #define CXACRU_EP_CMD 0x01 /* Bulk/interrupt in/out */
371b0e6146SDuncan Sands #define CXACRU_EP_DATA 0x02 /* Bulk in/out */
381b0e6146SDuncan Sands
391b0e6146SDuncan Sands #define CMD_PACKET_SIZE 64 /* Should be maxpacket(ep)? */
404ac37208SSimon Arlott #define CMD_MAX_CONFIG ((CMD_PACKET_SIZE / 4 - 1) / 2)
411b0e6146SDuncan Sands
421b0e6146SDuncan Sands /* Addresses */
431b0e6146SDuncan Sands #define PLLFCLK_ADDR 0x00350068
441b0e6146SDuncan Sands #define PLLBCLK_ADDR 0x0035006c
451b0e6146SDuncan Sands #define SDRAMEN_ADDR 0x00350010
461b0e6146SDuncan Sands #define FW_ADDR 0x00801000
471b0e6146SDuncan Sands #define BR_ADDR 0x00180600
481b0e6146SDuncan Sands #define SIG_ADDR 0x00180500
491b0e6146SDuncan Sands #define BR_STACK_ADDR 0x00187f10
501b0e6146SDuncan Sands
511b0e6146SDuncan Sands /* Values */
521b0e6146SDuncan Sands #define SDRAM_ENA 0x1
531b0e6146SDuncan Sands
541b0e6146SDuncan Sands #define CMD_TIMEOUT 2000 /* msecs */
55fa70fe44SSimon Arlott #define POLL_INTERVAL 1 /* secs */
561b0e6146SDuncan Sands
571b0e6146SDuncan Sands /* commands for interaction with the modem through the control channel before
581b0e6146SDuncan Sands * firmware is loaded */
591b0e6146SDuncan Sands enum cxacru_fw_request {
601b0e6146SDuncan Sands FW_CMD_ERR,
611b0e6146SDuncan Sands FW_GET_VER,
621b0e6146SDuncan Sands FW_READ_MEM,
631b0e6146SDuncan Sands FW_WRITE_MEM,
641b0e6146SDuncan Sands FW_RMW_MEM,
651b0e6146SDuncan Sands FW_CHECKSUM_MEM,
661b0e6146SDuncan Sands FW_GOTO_MEM,
671b0e6146SDuncan Sands };
681b0e6146SDuncan Sands
691b0e6146SDuncan Sands /* commands for interaction with the modem through the control channel once
701b0e6146SDuncan Sands * firmware is loaded */
711b0e6146SDuncan Sands enum cxacru_cm_request {
721b0e6146SDuncan Sands CM_REQUEST_UNDEFINED = 0x80,
731b0e6146SDuncan Sands CM_REQUEST_TEST,
741b0e6146SDuncan Sands CM_REQUEST_CHIP_GET_MAC_ADDRESS,
751b0e6146SDuncan Sands CM_REQUEST_CHIP_GET_DP_VERSIONS,
761b0e6146SDuncan Sands CM_REQUEST_CHIP_ADSL_LINE_START,
771b0e6146SDuncan Sands CM_REQUEST_CHIP_ADSL_LINE_STOP,
781b0e6146SDuncan Sands CM_REQUEST_CHIP_ADSL_LINE_GET_STATUS,
791b0e6146SDuncan Sands CM_REQUEST_CHIP_ADSL_LINE_GET_SPEED,
801b0e6146SDuncan Sands CM_REQUEST_CARD_INFO_GET,
811b0e6146SDuncan Sands CM_REQUEST_CARD_DATA_GET,
821b0e6146SDuncan Sands CM_REQUEST_CARD_DATA_SET,
831b0e6146SDuncan Sands CM_REQUEST_COMMAND_HW_IO,
841b0e6146SDuncan Sands CM_REQUEST_INTERFACE_HW_IO,
851b0e6146SDuncan Sands CM_REQUEST_CARD_SERIAL_DATA_PATH_GET,
861b0e6146SDuncan Sands CM_REQUEST_CARD_SERIAL_DATA_PATH_SET,
871b0e6146SDuncan Sands CM_REQUEST_CARD_CONTROLLER_VERSION_GET,
881b0e6146SDuncan Sands CM_REQUEST_CARD_GET_STATUS,
891b0e6146SDuncan Sands CM_REQUEST_CARD_GET_MAC_ADDRESS,
901b0e6146SDuncan Sands CM_REQUEST_CARD_GET_DATA_LINK_STATUS,
911b0e6146SDuncan Sands CM_REQUEST_MAX,
921b0e6146SDuncan Sands };
931b0e6146SDuncan Sands
94c68bb0d7SSimon Arlott /* commands for interaction with the flash memory
95c68bb0d7SSimon Arlott *
96c68bb0d7SSimon Arlott * read: response is the contents of the first 60 bytes of flash memory
97c68bb0d7SSimon Arlott * write: request contains the 60 bytes of data to write to flash memory
98c68bb0d7SSimon Arlott * response is the contents of the first 60 bytes of flash memory
99c68bb0d7SSimon Arlott *
100c68bb0d7SSimon Arlott * layout: PP PP VV VV MM MM MM MM MM MM ?? ?? SS SS SS SS SS SS SS SS
101c68bb0d7SSimon Arlott * SS SS SS SS SS SS SS SS 00 00 00 00 00 00 00 00 00 00 00 00
102c68bb0d7SSimon Arlott * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
103c68bb0d7SSimon Arlott *
104c68bb0d7SSimon Arlott * P: le16 USB Product ID
105c68bb0d7SSimon Arlott * V: le16 USB Vendor ID
106c68bb0d7SSimon Arlott * M: be48 MAC Address
107c68bb0d7SSimon Arlott * S: le16 ASCII Serial Number
108c68bb0d7SSimon Arlott */
109c68bb0d7SSimon Arlott enum cxacru_cm_flash {
110c68bb0d7SSimon Arlott CM_FLASH_READ = 0xa1,
111c68bb0d7SSimon Arlott CM_FLASH_WRITE = 0xa2
112c68bb0d7SSimon Arlott };
113c68bb0d7SSimon Arlott
1141b0e6146SDuncan Sands /* reply codes to the commands above */
1151b0e6146SDuncan Sands enum cxacru_cm_status {
1161b0e6146SDuncan Sands CM_STATUS_UNDEFINED,
1171b0e6146SDuncan Sands CM_STATUS_SUCCESS,
1181b0e6146SDuncan Sands CM_STATUS_ERROR,
1191b0e6146SDuncan Sands CM_STATUS_UNSUPPORTED,
1201b0e6146SDuncan Sands CM_STATUS_UNIMPLEMENTED,
1211b0e6146SDuncan Sands CM_STATUS_PARAMETER_ERROR,
1221b0e6146SDuncan Sands CM_STATUS_DBG_LOOPBACK,
1231b0e6146SDuncan Sands CM_STATUS_MAX,
1241b0e6146SDuncan Sands };
1251b0e6146SDuncan Sands
1261b0e6146SDuncan Sands /* indices into CARD_INFO_GET return array */
1271b0e6146SDuncan Sands enum cxacru_info_idx {
1281b0e6146SDuncan Sands CXINF_DOWNSTREAM_RATE,
1291b0e6146SDuncan Sands CXINF_UPSTREAM_RATE,
1301b0e6146SDuncan Sands CXINF_LINK_STATUS,
1311b0e6146SDuncan Sands CXINF_LINE_STATUS,
1321b0e6146SDuncan Sands CXINF_MAC_ADDRESS_HIGH,
1331b0e6146SDuncan Sands CXINF_MAC_ADDRESS_LOW,
1341b0e6146SDuncan Sands CXINF_UPSTREAM_SNR_MARGIN,
1351b0e6146SDuncan Sands CXINF_DOWNSTREAM_SNR_MARGIN,
1361b0e6146SDuncan Sands CXINF_UPSTREAM_ATTENUATION,
1371b0e6146SDuncan Sands CXINF_DOWNSTREAM_ATTENUATION,
1381b0e6146SDuncan Sands CXINF_TRANSMITTER_POWER,
1391b0e6146SDuncan Sands CXINF_UPSTREAM_BITS_PER_FRAME,
1401b0e6146SDuncan Sands CXINF_DOWNSTREAM_BITS_PER_FRAME,
1411b0e6146SDuncan Sands CXINF_STARTUP_ATTEMPTS,
1421b0e6146SDuncan Sands CXINF_UPSTREAM_CRC_ERRORS,
1431b0e6146SDuncan Sands CXINF_DOWNSTREAM_CRC_ERRORS,
1441b0e6146SDuncan Sands CXINF_UPSTREAM_FEC_ERRORS,
1451b0e6146SDuncan Sands CXINF_DOWNSTREAM_FEC_ERRORS,
1461b0e6146SDuncan Sands CXINF_UPSTREAM_HEC_ERRORS,
1471b0e6146SDuncan Sands CXINF_DOWNSTREAM_HEC_ERRORS,
1481b0e6146SDuncan Sands CXINF_LINE_STARTABLE,
1491b0e6146SDuncan Sands CXINF_MODULATION,
1501b0e6146SDuncan Sands CXINF_ADSL_HEADEND,
1511b0e6146SDuncan Sands CXINF_ADSL_HEADEND_ENVIRONMENT,
1521b0e6146SDuncan Sands CXINF_CONTROLLER_VERSION,
1531b0e6146SDuncan Sands /* dunno what the missing two mean */
1541b0e6146SDuncan Sands CXINF_MAX = 0x1c,
1551b0e6146SDuncan Sands };
1561b0e6146SDuncan Sands
1576a02c996SSimon Arlott enum cxacru_poll_state {
1586a02c996SSimon Arlott CXPOLL_STOPPING,
1596a02c996SSimon Arlott CXPOLL_STOPPED,
1606a02c996SSimon Arlott CXPOLL_POLLING,
1616a02c996SSimon Arlott CXPOLL_SHUTDOWN
1626a02c996SSimon Arlott };
1636a02c996SSimon Arlott
1641b0e6146SDuncan Sands struct cxacru_modem_type {
1651b0e6146SDuncan Sands u32 pll_f_clk;
1661b0e6146SDuncan Sands u32 pll_b_clk;
1671b0e6146SDuncan Sands int boot_rom_patch;
1681b0e6146SDuncan Sands };
1691b0e6146SDuncan Sands
1701b0e6146SDuncan Sands struct cxacru_data {
1711b0e6146SDuncan Sands struct usbatm_data *usbatm;
1721b0e6146SDuncan Sands
1731b0e6146SDuncan Sands const struct cxacru_modem_type *modem_type;
1741b0e6146SDuncan Sands
1751b0e6146SDuncan Sands int line_status;
1766a02c996SSimon Arlott struct mutex adsl_state_serialize;
1776a02c996SSimon Arlott int adsl_status;
178c4028958SDavid Howells struct delayed_work poll_work;
179fa70fe44SSimon Arlott u32 card_info[CXINF_MAX];
1806a02c996SSimon Arlott struct mutex poll_state_serialize;
18187e71b47SSimon Arlott enum cxacru_poll_state poll_state;
1821b0e6146SDuncan Sands
183b274e2a4Szuoqilin /* control handles */
184ab3c81ffSArjan van de Ven struct mutex cm_serialize;
1851b0e6146SDuncan Sands u8 *rcv_buf;
1861b0e6146SDuncan Sands u8 *snd_buf;
1871b0e6146SDuncan Sands struct urb *rcv_urb;
1881b0e6146SDuncan Sands struct urb *snd_urb;
1891b0e6146SDuncan Sands struct completion rcv_done;
1901b0e6146SDuncan Sands struct completion snd_done;
1911b0e6146SDuncan Sands };
1921b0e6146SDuncan Sands
1936a02c996SSimon Arlott static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm,
1946a02c996SSimon Arlott u8 *wdata, int wsize, u8 *rdata, int rsize);
1956a02c996SSimon Arlott static void cxacru_poll_status(struct work_struct *work);
1966a02c996SSimon Arlott
197fa70fe44SSimon Arlott /* Card info exported through sysfs */
198fa70fe44SSimon Arlott #define CXACRU__ATTR_INIT(_name) \
1996453f53bSGreg Kroah-Hartman static DEVICE_ATTR_RO(_name)
200fa70fe44SSimon Arlott
2016a02c996SSimon Arlott #define CXACRU_CMD_INIT(_name) \
2026453f53bSGreg Kroah-Hartman static DEVICE_ATTR_RW(_name)
2036a02c996SSimon Arlott
2044ac37208SSimon Arlott #define CXACRU_SET_INIT(_name) \
2056453f53bSGreg Kroah-Hartman static DEVICE_ATTR_WO(_name)
2064ac37208SSimon Arlott
207fa70fe44SSimon Arlott #define CXACRU_ATTR_INIT(_value, _type, _name) \
2086453f53bSGreg Kroah-Hartman static ssize_t _name##_show(struct device *dev, \
209fa70fe44SSimon Arlott struct device_attribute *attr, char *buf) \
210fa70fe44SSimon Arlott { \
2119fc950d3SSimon Arlott struct cxacru_data *instance = to_usbatm_driver_data(\
2129fc950d3SSimon Arlott to_usb_interface(dev)); \
2139fc950d3SSimon Arlott \
2149fc950d3SSimon Arlott if (instance == NULL) \
2159fc950d3SSimon Arlott return -ENODEV; \
2169fc950d3SSimon Arlott \
217fa70fe44SSimon Arlott return cxacru_sysfs_showattr_##_type(instance->card_info[_value], buf); \
218fa70fe44SSimon Arlott } \
219fa70fe44SSimon Arlott CXACRU__ATTR_INIT(_name)
220fa70fe44SSimon Arlott
221fa70fe44SSimon Arlott #define CXACRU_ATTR_CREATE(_v, _t, _name) CXACRU_DEVICE_CREATE_FILE(_name)
2226a02c996SSimon Arlott #define CXACRU_CMD_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name)
2234ac37208SSimon Arlott #define CXACRU_SET_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name)
224fa70fe44SSimon Arlott #define CXACRU__ATTR_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name)
225fa70fe44SSimon Arlott
226fa70fe44SSimon Arlott #define CXACRU_ATTR_REMOVE(_v, _t, _name) CXACRU_DEVICE_REMOVE_FILE(_name)
2276a02c996SSimon Arlott #define CXACRU_CMD_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name)
2284ac37208SSimon Arlott #define CXACRU_SET_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name)
229fa70fe44SSimon Arlott #define CXACRU__ATTR_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name)
230fa70fe44SSimon Arlott
cxacru_sysfs_showattr_u32(u32 value,char * buf)231fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_u32(u32 value, char *buf)
232fa70fe44SSimon Arlott {
233cb06b385SAlex Dewar return sprintf(buf, "%u\n", value);
234fa70fe44SSimon Arlott }
235fa70fe44SSimon Arlott
cxacru_sysfs_showattr_s8(s8 value,char * buf)236fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_s8(s8 value, char *buf)
237fa70fe44SSimon Arlott {
238cb06b385SAlex Dewar return sprintf(buf, "%d\n", value);
239fa70fe44SSimon Arlott }
240fa70fe44SSimon Arlott
cxacru_sysfs_showattr_dB(s16 value,char * buf)241fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_dB(s16 value, char *buf)
242fa70fe44SSimon Arlott {
24310107bd0SSimon Arlott if (likely(value >= 0)) {
24410107bd0SSimon Arlott return snprintf(buf, PAGE_SIZE, "%u.%02u\n",
24510107bd0SSimon Arlott value / 100, value % 100);
24610107bd0SSimon Arlott } else {
24710107bd0SSimon Arlott value = -value;
24810107bd0SSimon Arlott return snprintf(buf, PAGE_SIZE, "-%u.%02u\n",
24910107bd0SSimon Arlott value / 100, value % 100);
25010107bd0SSimon Arlott }
251fa70fe44SSimon Arlott }
252fa70fe44SSimon Arlott
cxacru_sysfs_showattr_bool(u32 value,char * buf)253fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_bool(u32 value, char *buf)
254fa70fe44SSimon Arlott {
25587e71b47SSimon Arlott static char *str[] = { "no", "yes" };
256cd32fbadSAaron Raimist
25787e71b47SSimon Arlott if (unlikely(value >= ARRAY_SIZE(str)))
258cb06b385SAlex Dewar return sprintf(buf, "%u\n", value);
259cb06b385SAlex Dewar return sprintf(buf, "%s\n", str[value]);
260fa70fe44SSimon Arlott }
261fa70fe44SSimon Arlott
cxacru_sysfs_showattr_LINK(u32 value,char * buf)262fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_LINK(u32 value, char *buf)
263fa70fe44SSimon Arlott {
26487e71b47SSimon Arlott static char *str[] = { NULL, "not connected", "connected", "lost" };
265cd32fbadSAaron Raimist
26687e71b47SSimon Arlott if (unlikely(value >= ARRAY_SIZE(str) || str[value] == NULL))
267cb06b385SAlex Dewar return sprintf(buf, "%u\n", value);
268cb06b385SAlex Dewar return sprintf(buf, "%s\n", str[value]);
269fa70fe44SSimon Arlott }
270fa70fe44SSimon Arlott
cxacru_sysfs_showattr_LINE(u32 value,char * buf)271fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_LINE(u32 value, char *buf)
272fa70fe44SSimon Arlott {
27387e71b47SSimon Arlott static char *str[] = { "down", "attempting to activate",
27487e71b47SSimon Arlott "training", "channel analysis", "exchange", "up",
27587e71b47SSimon Arlott "waiting", "initialising"
27687e71b47SSimon Arlott };
27787e71b47SSimon Arlott if (unlikely(value >= ARRAY_SIZE(str)))
278cb06b385SAlex Dewar return sprintf(buf, "%u\n", value);
279cb06b385SAlex Dewar return sprintf(buf, "%s\n", str[value]);
280fa70fe44SSimon Arlott }
281fa70fe44SSimon Arlott
cxacru_sysfs_showattr_MODU(u32 value,char * buf)282fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_MODU(u32 value, char *buf)
283fa70fe44SSimon Arlott {
28487e71b47SSimon Arlott static char *str[] = {
2851bfbd283SSimon Arlott "",
28687e71b47SSimon Arlott "ANSI T1.413",
28787e71b47SSimon Arlott "ITU-T G.992.1 (G.DMT)",
28887e71b47SSimon Arlott "ITU-T G.992.2 (G.LITE)"
28987e71b47SSimon Arlott };
2901bfbd283SSimon Arlott if (unlikely(value >= ARRAY_SIZE(str)))
291cb06b385SAlex Dewar return sprintf(buf, "%u\n", value);
292cb06b385SAlex Dewar return sprintf(buf, "%s\n", str[value]);
293fa70fe44SSimon Arlott }
294fa70fe44SSimon Arlott
295fa70fe44SSimon Arlott /*
296fa70fe44SSimon Arlott * This could use MAC_ADDRESS_HIGH and MAC_ADDRESS_LOW, but since
297fa70fe44SSimon Arlott * this data is already in atm_dev there's no point.
298fa70fe44SSimon Arlott *
299fa70fe44SSimon Arlott * MAC_ADDRESS_HIGH = 0x????5544
300fa70fe44SSimon Arlott * MAC_ADDRESS_LOW = 0x33221100
301fa70fe44SSimon Arlott * Where 00-55 are bytes 0-5 of the MAC.
302fa70fe44SSimon Arlott */
mac_address_show(struct device * dev,struct device_attribute * attr,char * buf)3036453f53bSGreg Kroah-Hartman static ssize_t mac_address_show(struct device *dev,
304fa70fe44SSimon Arlott struct device_attribute *attr, char *buf)
305fa70fe44SSimon Arlott {
3069fc950d3SSimon Arlott struct cxacru_data *instance = to_usbatm_driver_data(
3079fc950d3SSimon Arlott to_usb_interface(dev));
308fa70fe44SSimon Arlott
3099fc950d3SSimon Arlott if (instance == NULL || instance->usbatm->atm_dev == NULL)
3109fc950d3SSimon Arlott return -ENODEV;
3119fc950d3SSimon Arlott
312cb06b385SAlex Dewar return sprintf(buf, "%pM\n", instance->usbatm->atm_dev->esi);
313fa70fe44SSimon Arlott }
314fa70fe44SSimon Arlott
adsl_state_show(struct device * dev,struct device_attribute * attr,char * buf)3156453f53bSGreg Kroah-Hartman static ssize_t adsl_state_show(struct device *dev,
3166a02c996SSimon Arlott struct device_attribute *attr, char *buf)
3176a02c996SSimon Arlott {
31887e71b47SSimon Arlott static char *str[] = { "running", "stopped" };
3199fc950d3SSimon Arlott struct cxacru_data *instance = to_usbatm_driver_data(
3209fc950d3SSimon Arlott to_usb_interface(dev));
3219fc950d3SSimon Arlott u32 value;
3229fc950d3SSimon Arlott
3239fc950d3SSimon Arlott if (instance == NULL)
3249fc950d3SSimon Arlott return -ENODEV;
3259fc950d3SSimon Arlott
3269fc950d3SSimon Arlott value = instance->card_info[CXINF_LINE_STARTABLE];
32787e71b47SSimon Arlott if (unlikely(value >= ARRAY_SIZE(str)))
328cb06b385SAlex Dewar return sprintf(buf, "%u\n", value);
329cb06b385SAlex Dewar return sprintf(buf, "%s\n", str[value]);
3306a02c996SSimon Arlott }
3316a02c996SSimon Arlott
adsl_state_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)3326453f53bSGreg Kroah-Hartman static ssize_t adsl_state_store(struct device *dev,
3336a02c996SSimon Arlott struct device_attribute *attr, const char *buf, size_t count)
3346a02c996SSimon Arlott {
3359fc950d3SSimon Arlott struct cxacru_data *instance = to_usbatm_driver_data(
3369fc950d3SSimon Arlott to_usb_interface(dev));
3376a02c996SSimon Arlott int ret;
3386a02c996SSimon Arlott int poll = -1;
3396a02c996SSimon Arlott char str_cmd[8];
3406a02c996SSimon Arlott int len = strlen(buf);
3416a02c996SSimon Arlott
3426a02c996SSimon Arlott if (!capable(CAP_NET_ADMIN))
3436a02c996SSimon Arlott return -EACCES;
3446a02c996SSimon Arlott
3456a02c996SSimon Arlott ret = sscanf(buf, "%7s", str_cmd);
3466a02c996SSimon Arlott if (ret != 1)
3476a02c996SSimon Arlott return -EINVAL;
3486a02c996SSimon Arlott ret = 0;
3496a02c996SSimon Arlott
3509fc950d3SSimon Arlott if (instance == NULL)
3519fc950d3SSimon Arlott return -ENODEV;
3529fc950d3SSimon Arlott
3536a02c996SSimon Arlott if (mutex_lock_interruptible(&instance->adsl_state_serialize))
3546a02c996SSimon Arlott return -ERESTARTSYS;
3556a02c996SSimon Arlott
3566a02c996SSimon Arlott if (!strcmp(str_cmd, "stop") || !strcmp(str_cmd, "restart")) {
3576a02c996SSimon Arlott ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_STOP, NULL, 0, NULL, 0);
3586a02c996SSimon Arlott if (ret < 0) {
3599fc950d3SSimon Arlott atm_err(instance->usbatm, "change adsl state:"
3606a02c996SSimon Arlott " CHIP_ADSL_LINE_STOP returned %d\n", ret);
3616a02c996SSimon Arlott
3626a02c996SSimon Arlott ret = -EIO;
3636a02c996SSimon Arlott } else {
3646a02c996SSimon Arlott ret = len;
3656a02c996SSimon Arlott poll = CXPOLL_STOPPED;
3666a02c996SSimon Arlott }
3676a02c996SSimon Arlott }
3686a02c996SSimon Arlott
3696a02c996SSimon Arlott /* Line status is only updated every second
3706a02c996SSimon Arlott * and the device appears to only react to
3716a02c996SSimon Arlott * START/STOP every second too. Wait 1.5s to
3726a02c996SSimon Arlott * be sure that restart will have an effect. */
3736a02c996SSimon Arlott if (!strcmp(str_cmd, "restart"))
3746a02c996SSimon Arlott msleep(1500);
3756a02c996SSimon Arlott
3766a02c996SSimon Arlott if (!strcmp(str_cmd, "start") || !strcmp(str_cmd, "restart")) {
3776a02c996SSimon Arlott ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0);
3786a02c996SSimon Arlott if (ret < 0) {
3799fc950d3SSimon Arlott atm_err(instance->usbatm, "change adsl state:"
3806a02c996SSimon Arlott " CHIP_ADSL_LINE_START returned %d\n", ret);
3816a02c996SSimon Arlott
3826a02c996SSimon Arlott ret = -EIO;
3836a02c996SSimon Arlott } else {
3846a02c996SSimon Arlott ret = len;
3856a02c996SSimon Arlott poll = CXPOLL_POLLING;
3866a02c996SSimon Arlott }
3876a02c996SSimon Arlott }
3886a02c996SSimon Arlott
3896a02c996SSimon Arlott if (!strcmp(str_cmd, "poll")) {
3906a02c996SSimon Arlott ret = len;
3916a02c996SSimon Arlott poll = CXPOLL_POLLING;
3926a02c996SSimon Arlott }
3936a02c996SSimon Arlott
3946a02c996SSimon Arlott if (ret == 0) {
3956a02c996SSimon Arlott ret = -EINVAL;
3966a02c996SSimon Arlott poll = -1;
3976a02c996SSimon Arlott }
3986a02c996SSimon Arlott
3996a02c996SSimon Arlott if (poll == CXPOLL_POLLING) {
4006a02c996SSimon Arlott mutex_lock(&instance->poll_state_serialize);
4016a02c996SSimon Arlott switch (instance->poll_state) {
4026a02c996SSimon Arlott case CXPOLL_STOPPED:
4036a02c996SSimon Arlott /* start polling */
4046a02c996SSimon Arlott instance->poll_state = CXPOLL_POLLING;
4056a02c996SSimon Arlott break;
4066a02c996SSimon Arlott
4076a02c996SSimon Arlott case CXPOLL_STOPPING:
4086a02c996SSimon Arlott /* abort stop request */
4096a02c996SSimon Arlott instance->poll_state = CXPOLL_POLLING;
4100d9b6d49SGustavo A. R. Silva fallthrough;
4116a02c996SSimon Arlott case CXPOLL_POLLING:
4126a02c996SSimon Arlott case CXPOLL_SHUTDOWN:
4136a02c996SSimon Arlott /* don't start polling */
4146a02c996SSimon Arlott poll = -1;
4156a02c996SSimon Arlott }
4166a02c996SSimon Arlott mutex_unlock(&instance->poll_state_serialize);
4176a02c996SSimon Arlott } else if (poll == CXPOLL_STOPPED) {
4186a02c996SSimon Arlott mutex_lock(&instance->poll_state_serialize);
4196a02c996SSimon Arlott /* request stop */
4206a02c996SSimon Arlott if (instance->poll_state == CXPOLL_POLLING)
4216a02c996SSimon Arlott instance->poll_state = CXPOLL_STOPPING;
4226a02c996SSimon Arlott mutex_unlock(&instance->poll_state_serialize);
4236a02c996SSimon Arlott }
4246a02c996SSimon Arlott
4256a02c996SSimon Arlott mutex_unlock(&instance->adsl_state_serialize);
4266a02c996SSimon Arlott
4276a02c996SSimon Arlott if (poll == CXPOLL_POLLING)
4286a02c996SSimon Arlott cxacru_poll_status(&instance->poll_work.work);
4296a02c996SSimon Arlott
4306a02c996SSimon Arlott return ret;
4316a02c996SSimon Arlott }
4326a02c996SSimon Arlott
4334ac37208SSimon Arlott /* CM_REQUEST_CARD_DATA_GET times out, so no show attribute */
4344ac37208SSimon Arlott
adsl_config_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)4356453f53bSGreg Kroah-Hartman static ssize_t adsl_config_store(struct device *dev,
4364ac37208SSimon Arlott struct device_attribute *attr, const char *buf, size_t count)
4374ac37208SSimon Arlott {
4384ac37208SSimon Arlott struct cxacru_data *instance = to_usbatm_driver_data(
4394ac37208SSimon Arlott to_usb_interface(dev));
4404ac37208SSimon Arlott int len = strlen(buf);
4414ac37208SSimon Arlott int ret, pos, num;
4424ac37208SSimon Arlott __le32 data[CMD_PACKET_SIZE / 4];
4434ac37208SSimon Arlott
4444ac37208SSimon Arlott if (!capable(CAP_NET_ADMIN))
4454ac37208SSimon Arlott return -EACCES;
4464ac37208SSimon Arlott
4474ac37208SSimon Arlott if (instance == NULL)
4484ac37208SSimon Arlott return -ENODEV;
4494ac37208SSimon Arlott
4504ac37208SSimon Arlott pos = 0;
4514ac37208SSimon Arlott num = 0;
4524ac37208SSimon Arlott while (pos < len) {
4534ac37208SSimon Arlott int tmp;
4544ac37208SSimon Arlott u32 index;
4554ac37208SSimon Arlott u32 value;
4564ac37208SSimon Arlott
4574ac37208SSimon Arlott ret = sscanf(buf + pos, "%x=%x%n", &index, &value, &tmp);
4584ac37208SSimon Arlott if (ret < 2)
4594ac37208SSimon Arlott return -EINVAL;
46078f74f75SGustavo A. R. Silva if (index > 0x7f)
4614ac37208SSimon Arlott return -EINVAL;
46246e3cafbSDan Carpenter if (tmp < 0 || tmp > len - pos)
46346e3cafbSDan Carpenter return -EINVAL;
4644ac37208SSimon Arlott pos += tmp;
4654ac37208SSimon Arlott
4664ac37208SSimon Arlott /* skip trailing newline */
4674ac37208SSimon Arlott if (buf[pos] == '\n' && pos == len-1)
4684ac37208SSimon Arlott pos++;
4694ac37208SSimon Arlott
4704ac37208SSimon Arlott data[num * 2 + 1] = cpu_to_le32(index);
4714ac37208SSimon Arlott data[num * 2 + 2] = cpu_to_le32(value);
4724ac37208SSimon Arlott num++;
4734ac37208SSimon Arlott
4744ac37208SSimon Arlott /* send config values when data buffer is full
4754ac37208SSimon Arlott * or no more data
4764ac37208SSimon Arlott */
4774ac37208SSimon Arlott if (pos >= len || num >= CMD_MAX_CONFIG) {
4784ac37208SSimon Arlott char log[CMD_MAX_CONFIG * 12 + 1]; /* %02x=%08x */
4794ac37208SSimon Arlott
4804ac37208SSimon Arlott data[0] = cpu_to_le32(num);
4814ac37208SSimon Arlott ret = cxacru_cm(instance, CM_REQUEST_CARD_DATA_SET,
4824ac37208SSimon Arlott (u8 *) data, 4 + num * 8, NULL, 0);
4834ac37208SSimon Arlott if (ret < 0) {
4844ac37208SSimon Arlott atm_err(instance->usbatm,
4854ac37208SSimon Arlott "set card data returned %d\n", ret);
4864ac37208SSimon Arlott return -EIO;
4874ac37208SSimon Arlott }
4884ac37208SSimon Arlott
4894ac37208SSimon Arlott for (tmp = 0; tmp < num; tmp++)
4904ac37208SSimon Arlott snprintf(log + tmp*12, 13, " %02x=%08x",
4914ac37208SSimon Arlott le32_to_cpu(data[tmp * 2 + 1]),
4924ac37208SSimon Arlott le32_to_cpu(data[tmp * 2 + 2]));
4934ac37208SSimon Arlott atm_info(instance->usbatm, "config%s\n", log);
4944ac37208SSimon Arlott num = 0;
4954ac37208SSimon Arlott }
4964ac37208SSimon Arlott }
4974ac37208SSimon Arlott
4984ac37208SSimon Arlott return len;
4994ac37208SSimon Arlott }
5004ac37208SSimon Arlott
501fa70fe44SSimon Arlott /*
502fa70fe44SSimon Arlott * All device attributes are included in CXACRU_ALL_FILES
503fa70fe44SSimon Arlott * so that the same list can be used multiple times:
504fa70fe44SSimon Arlott * INIT (define the device attributes)
505fa70fe44SSimon Arlott * CREATE (create all the device files)
506fa70fe44SSimon Arlott * REMOVE (remove all the device files)
507fa70fe44SSimon Arlott *
508fa70fe44SSimon Arlott * With the last two being defined as needed in the functions
509fa70fe44SSimon Arlott * they are used in before calling CXACRU_ALL_FILES()
510fa70fe44SSimon Arlott */
511fa70fe44SSimon Arlott #define CXACRU_ALL_FILES(_action) \
512fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_RATE, u32, downstream_rate); \
513fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_RATE, u32, upstream_rate); \
514fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_LINK_STATUS, LINK, link_status); \
515fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_LINE_STATUS, LINE, line_status); \
516fa70fe44SSimon Arlott CXACRU__ATTR_##_action( mac_address); \
517fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_SNR_MARGIN, dB, upstream_snr_margin); \
518fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_SNR_MARGIN, dB, downstream_snr_margin); \
519fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_ATTENUATION, dB, upstream_attenuation); \
520fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_ATTENUATION, dB, downstream_attenuation); \
521fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_TRANSMITTER_POWER, s8, transmitter_power); \
522fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_BITS_PER_FRAME, u32, upstream_bits_per_frame); \
523fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_BITS_PER_FRAME, u32, downstream_bits_per_frame); \
524fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_STARTUP_ATTEMPTS, u32, startup_attempts); \
525fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_CRC_ERRORS, u32, upstream_crc_errors); \
526fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_CRC_ERRORS, u32, downstream_crc_errors); \
527fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_FEC_ERRORS, u32, upstream_fec_errors); \
528fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_FEC_ERRORS, u32, downstream_fec_errors); \
529fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_HEC_ERRORS, u32, upstream_hec_errors); \
530fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_HEC_ERRORS, u32, downstream_hec_errors); \
531fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_LINE_STARTABLE, bool, line_startable); \
532fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_MODULATION, MODU, modulation); \
533fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND, u32, adsl_headend); \
534fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND_ENVIRONMENT, u32, adsl_headend_environment); \
5356a02c996SSimon Arlott CXACRU_ATTR_##_action(CXINF_CONTROLLER_VERSION, u32, adsl_controller_version); \
5364ac37208SSimon Arlott CXACRU_CMD_##_action( adsl_state); \
5374ac37208SSimon Arlott CXACRU_SET_##_action( adsl_config);
538fa70fe44SSimon Arlott
539fa70fe44SSimon Arlott CXACRU_ALL_FILES(INIT);
540fa70fe44SSimon Arlott
541e605c309SGreg Kroah-Hartman static struct attribute *cxacru_attrs[] = {
542e605c309SGreg Kroah-Hartman &dev_attr_adsl_config.attr,
543e605c309SGreg Kroah-Hartman &dev_attr_adsl_state.attr,
544e605c309SGreg Kroah-Hartman &dev_attr_adsl_controller_version.attr,
545e605c309SGreg Kroah-Hartman &dev_attr_adsl_headend_environment.attr,
546e605c309SGreg Kroah-Hartman &dev_attr_adsl_headend.attr,
547e605c309SGreg Kroah-Hartman &dev_attr_modulation.attr,
548e605c309SGreg Kroah-Hartman &dev_attr_line_startable.attr,
549e605c309SGreg Kroah-Hartman &dev_attr_downstream_hec_errors.attr,
550e605c309SGreg Kroah-Hartman &dev_attr_upstream_hec_errors.attr,
551e605c309SGreg Kroah-Hartman &dev_attr_downstream_fec_errors.attr,
552e605c309SGreg Kroah-Hartman &dev_attr_upstream_fec_errors.attr,
553e605c309SGreg Kroah-Hartman &dev_attr_downstream_crc_errors.attr,
554e605c309SGreg Kroah-Hartman &dev_attr_upstream_crc_errors.attr,
555e605c309SGreg Kroah-Hartman &dev_attr_startup_attempts.attr,
556e605c309SGreg Kroah-Hartman &dev_attr_downstream_bits_per_frame.attr,
557e605c309SGreg Kroah-Hartman &dev_attr_upstream_bits_per_frame.attr,
558e605c309SGreg Kroah-Hartman &dev_attr_transmitter_power.attr,
559e605c309SGreg Kroah-Hartman &dev_attr_downstream_attenuation.attr,
560e605c309SGreg Kroah-Hartman &dev_attr_upstream_attenuation.attr,
561e605c309SGreg Kroah-Hartman &dev_attr_downstream_snr_margin.attr,
562e605c309SGreg Kroah-Hartman &dev_attr_upstream_snr_margin.attr,
563e605c309SGreg Kroah-Hartman &dev_attr_mac_address.attr,
564e605c309SGreg Kroah-Hartman &dev_attr_line_status.attr,
565e605c309SGreg Kroah-Hartman &dev_attr_link_status.attr,
566e605c309SGreg Kroah-Hartman &dev_attr_upstream_rate.attr,
567e605c309SGreg Kroah-Hartman &dev_attr_downstream_rate.attr,
568e605c309SGreg Kroah-Hartman NULL,
569e605c309SGreg Kroah-Hartman };
570e605c309SGreg Kroah-Hartman ATTRIBUTE_GROUPS(cxacru);
571e605c309SGreg Kroah-Hartman
5721b0e6146SDuncan Sands /* the following three functions are stolen from drivers/usb/core/message.c */
cxacru_blocking_completion(struct urb * urb)5737d12e780SDavid Howells static void cxacru_blocking_completion(struct urb *urb)
5741b0e6146SDuncan Sands {
575cdc97792SMing Lei complete(urb->context);
5761b0e6146SDuncan Sands }
5771b0e6146SDuncan Sands
57872a9f9a4SKees Cook struct cxacru_timer {
57972a9f9a4SKees Cook struct timer_list timer;
58072a9f9a4SKees Cook struct urb *urb;
58172a9f9a4SKees Cook };
58272a9f9a4SKees Cook
cxacru_timeout_kill(struct timer_list * t)58372a9f9a4SKees Cook static void cxacru_timeout_kill(struct timer_list *t)
5841b0e6146SDuncan Sands {
58572a9f9a4SKees Cook struct cxacru_timer *timer = from_timer(timer, t, timer);
58672a9f9a4SKees Cook
58772a9f9a4SKees Cook usb_unlink_urb(timer->urb);
5881b0e6146SDuncan Sands }
5891b0e6146SDuncan Sands
cxacru_start_wait_urb(struct urb * urb,struct completion * done,int * actual_length)5901b0e6146SDuncan Sands static int cxacru_start_wait_urb(struct urb *urb, struct completion *done,
5911b0e6146SDuncan Sands int *actual_length)
5921b0e6146SDuncan Sands {
59372a9f9a4SKees Cook struct cxacru_timer timer = {
59472a9f9a4SKees Cook .urb = urb,
59572a9f9a4SKees Cook };
5961b0e6146SDuncan Sands
59772a9f9a4SKees Cook timer_setup_on_stack(&timer.timer, cxacru_timeout_kill, 0);
59872a9f9a4SKees Cook mod_timer(&timer.timer, jiffies + msecs_to_jiffies(CMD_TIMEOUT));
5991b0e6146SDuncan Sands wait_for_completion(done);
60072a9f9a4SKees Cook del_timer_sync(&timer.timer);
60172a9f9a4SKees Cook destroy_timer_on_stack(&timer.timer);
6021b0e6146SDuncan Sands
6031b0e6146SDuncan Sands if (actual_length)
6041b0e6146SDuncan Sands *actual_length = urb->actual_length;
6053b79cc26SOliver Neukum return urb->status; /* must read status after completion */
6061b0e6146SDuncan Sands }
6071b0e6146SDuncan Sands
cxacru_cm(struct cxacru_data * instance,enum cxacru_cm_request cm,u8 * wdata,int wsize,u8 * rdata,int rsize)6081b0e6146SDuncan Sands static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm,
6091b0e6146SDuncan Sands u8 *wdata, int wsize, u8 *rdata, int rsize)
6101b0e6146SDuncan Sands {
6111b0e6146SDuncan Sands int ret, actlen;
6121b0e6146SDuncan Sands int offb, offd;
6131b0e6146SDuncan Sands const int stride = CMD_PACKET_SIZE - 4;
6141b0e6146SDuncan Sands u8 *wbuf = instance->snd_buf;
6151b0e6146SDuncan Sands u8 *rbuf = instance->rcv_buf;
6161b0e6146SDuncan Sands int wbuflen = ((wsize - 1) / stride + 1) * CMD_PACKET_SIZE;
6171b0e6146SDuncan Sands int rbuflen = ((rsize - 1) / stride + 1) * CMD_PACKET_SIZE;
6181b0e6146SDuncan Sands
6191b0e6146SDuncan Sands if (wbuflen > PAGE_SIZE || rbuflen > PAGE_SIZE) {
6204ac0718eSSimon Arlott if (printk_ratelimit())
6214ac0718eSSimon Arlott usb_err(instance->usbatm, "requested transfer size too large (%d, %d)\n",
6224ac0718eSSimon Arlott wbuflen, rbuflen);
6231b0e6146SDuncan Sands ret = -ENOMEM;
624eeafa64bSJiri Slaby goto err;
6251b0e6146SDuncan Sands }
6261b0e6146SDuncan Sands
627ab3c81ffSArjan van de Ven mutex_lock(&instance->cm_serialize);
6281b0e6146SDuncan Sands
6291b0e6146SDuncan Sands /* submit reading urb before the writing one */
6301b0e6146SDuncan Sands init_completion(&instance->rcv_done);
6311b0e6146SDuncan Sands ret = usb_submit_urb(instance->rcv_urb, GFP_KERNEL);
6321b0e6146SDuncan Sands if (ret < 0) {
6334ac0718eSSimon Arlott if (printk_ratelimit())
6344ac0718eSSimon Arlott usb_err(instance->usbatm, "submit of read urb for cm %#x failed (%d)\n",
6354ac0718eSSimon Arlott cm, ret);
6361b0e6146SDuncan Sands goto fail;
6371b0e6146SDuncan Sands }
6381b0e6146SDuncan Sands
6391b0e6146SDuncan Sands memset(wbuf, 0, wbuflen);
6401b0e6146SDuncan Sands /* handle wsize == 0 */
6411b0e6146SDuncan Sands wbuf[0] = cm;
6421b0e6146SDuncan Sands for (offb = offd = 0; offd < wsize; offd += stride, offb += CMD_PACKET_SIZE) {
6431b0e6146SDuncan Sands wbuf[offb] = cm;
6441b0e6146SDuncan Sands memcpy(wbuf + offb + 4, wdata + offd, min_t(int, stride, wsize - offd));
6451b0e6146SDuncan Sands }
6461b0e6146SDuncan Sands
6471b0e6146SDuncan Sands instance->snd_urb->transfer_buffer_length = wbuflen;
6481b0e6146SDuncan Sands init_completion(&instance->snd_done);
6491b0e6146SDuncan Sands ret = usb_submit_urb(instance->snd_urb, GFP_KERNEL);
6501b0e6146SDuncan Sands if (ret < 0) {
6514ac0718eSSimon Arlott if (printk_ratelimit())
6524ac0718eSSimon Arlott usb_err(instance->usbatm, "submit of write urb for cm %#x failed (%d)\n",
6534ac0718eSSimon Arlott cm, ret);
6541b0e6146SDuncan Sands goto fail;
6551b0e6146SDuncan Sands }
6561b0e6146SDuncan Sands
6571b0e6146SDuncan Sands ret = cxacru_start_wait_urb(instance->snd_urb, &instance->snd_done, NULL);
6581b0e6146SDuncan Sands if (ret < 0) {
6594ac0718eSSimon Arlott if (printk_ratelimit())
6604ac0718eSSimon Arlott usb_err(instance->usbatm, "send of cm %#x failed (%d)\n", cm, ret);
6611b0e6146SDuncan Sands goto fail;
6621b0e6146SDuncan Sands }
6631b0e6146SDuncan Sands
6641b0e6146SDuncan Sands ret = cxacru_start_wait_urb(instance->rcv_urb, &instance->rcv_done, &actlen);
6651b0e6146SDuncan Sands if (ret < 0) {
6664ac0718eSSimon Arlott if (printk_ratelimit())
6674ac0718eSSimon Arlott usb_err(instance->usbatm, "receive of cm %#x failed (%d)\n", cm, ret);
6681b0e6146SDuncan Sands goto fail;
6691b0e6146SDuncan Sands }
6701b0e6146SDuncan Sands if (actlen % CMD_PACKET_SIZE || !actlen) {
6714ac0718eSSimon Arlott if (printk_ratelimit())
6724ac0718eSSimon Arlott usb_err(instance->usbatm, "invalid response length to cm %#x: %d\n",
6734ac0718eSSimon Arlott cm, actlen);
6741b0e6146SDuncan Sands ret = -EIO;
6751b0e6146SDuncan Sands goto fail;
6761b0e6146SDuncan Sands }
6771b0e6146SDuncan Sands
6781b0e6146SDuncan Sands /* check the return status and copy the data to the output buffer, if needed */
6791b0e6146SDuncan Sands for (offb = offd = 0; offd < rsize && offb < actlen; offb += CMD_PACKET_SIZE) {
6801b0e6146SDuncan Sands if (rbuf[offb] != cm) {
6814ac0718eSSimon Arlott if (printk_ratelimit())
6824ac0718eSSimon Arlott usb_err(instance->usbatm, "wrong cm %#x in response to cm %#x\n",
6834ac0718eSSimon Arlott rbuf[offb], cm);
6841b0e6146SDuncan Sands ret = -EIO;
6851b0e6146SDuncan Sands goto fail;
6861b0e6146SDuncan Sands }
6871b0e6146SDuncan Sands if (rbuf[offb + 1] != CM_STATUS_SUCCESS) {
6884ac0718eSSimon Arlott if (printk_ratelimit())
6894ac0718eSSimon Arlott usb_err(instance->usbatm, "response to cm %#x failed: %#x\n",
6904ac0718eSSimon Arlott cm, rbuf[offb + 1]);
6911b0e6146SDuncan Sands ret = -EIO;
6921b0e6146SDuncan Sands goto fail;
6931b0e6146SDuncan Sands }
6941b0e6146SDuncan Sands if (offd >= rsize)
6951b0e6146SDuncan Sands break;
6961b0e6146SDuncan Sands memcpy(rdata + offd, rbuf + offb + 4, min_t(int, stride, rsize - offd));
6971b0e6146SDuncan Sands offd += stride;
6981b0e6146SDuncan Sands }
6991b0e6146SDuncan Sands
7001b0e6146SDuncan Sands ret = offd;
70177c9e125SGreg Kroah-Hartman usb_dbg(instance->usbatm, "cm %#x\n", cm);
7021b0e6146SDuncan Sands fail:
703ab3c81ffSArjan van de Ven mutex_unlock(&instance->cm_serialize);
704eeafa64bSJiri Slaby err:
7051b0e6146SDuncan Sands return ret;
7061b0e6146SDuncan Sands }
7071b0e6146SDuncan Sands
cxacru_cm_get_array(struct cxacru_data * instance,enum cxacru_cm_request cm,u32 * data,int size)7081b0e6146SDuncan Sands static int cxacru_cm_get_array(struct cxacru_data *instance, enum cxacru_cm_request cm,
7091b0e6146SDuncan Sands u32 *data, int size)
7101b0e6146SDuncan Sands {
7111b0e6146SDuncan Sands int ret, len;
712fd05e720SAl Viro __le32 *buf;
7132a0ebf80SDan Carpenter int offb;
7142a0ebf80SDan Carpenter unsigned int offd;
7151b0e6146SDuncan Sands const int stride = CMD_PACKET_SIZE / (4 * 2) - 1;
7161b0e6146SDuncan Sands int buflen = ((size - 1) / stride + 1 + size * 2) * 4;
7171b0e6146SDuncan Sands
7181b0e6146SDuncan Sands buf = kmalloc(buflen, GFP_KERNEL);
7191b0e6146SDuncan Sands if (!buf)
7201b0e6146SDuncan Sands return -ENOMEM;
7211b0e6146SDuncan Sands
7221b0e6146SDuncan Sands ret = cxacru_cm(instance, cm, NULL, 0, (u8 *) buf, buflen);
7231b0e6146SDuncan Sands if (ret < 0)
7241b0e6146SDuncan Sands goto cleanup;
7251b0e6146SDuncan Sands
7261b0e6146SDuncan Sands /* len > 0 && len % 4 == 0 guaranteed by cxacru_cm() */
7271b0e6146SDuncan Sands len = ret / 4;
7281b0e6146SDuncan Sands for (offb = 0; offb < len; ) {
7291b0e6146SDuncan Sands int l = le32_to_cpu(buf[offb++]);
730cd32fbadSAaron Raimist
7315d0a9c79SSimon Arlott if (l < 0 || l > stride || l > (len - offb) / 2) {
7324ac0718eSSimon Arlott if (printk_ratelimit())
7334ac0718eSSimon Arlott usb_err(instance->usbatm, "invalid data length from cm %#x: %d\n",
7344ac0718eSSimon Arlott cm, l);
7351b0e6146SDuncan Sands ret = -EIO;
7361b0e6146SDuncan Sands goto cleanup;
7371b0e6146SDuncan Sands }
7381b0e6146SDuncan Sands while (l--) {
7391b0e6146SDuncan Sands offd = le32_to_cpu(buf[offb++]);
7401b0e6146SDuncan Sands if (offd >= size) {
7414ac0718eSSimon Arlott if (printk_ratelimit())
742230ffc75SSimon Arlott usb_err(instance->usbatm, "wrong index %#x in response to cm %#x\n",
7434ac0718eSSimon Arlott offd, cm);
7441b0e6146SDuncan Sands ret = -EIO;
7451b0e6146SDuncan Sands goto cleanup;
7461b0e6146SDuncan Sands }
7471b0e6146SDuncan Sands data[offd] = le32_to_cpu(buf[offb++]);
7481b0e6146SDuncan Sands }
7491b0e6146SDuncan Sands }
7501b0e6146SDuncan Sands
7511b0e6146SDuncan Sands ret = 0;
7521b0e6146SDuncan Sands
7531b0e6146SDuncan Sands cleanup:
7541b0e6146SDuncan Sands kfree(buf);
7551b0e6146SDuncan Sands return ret;
7561b0e6146SDuncan Sands }
7571b0e6146SDuncan Sands
cxacru_card_status(struct cxacru_data * instance)7581b0e6146SDuncan Sands static int cxacru_card_status(struct cxacru_data *instance)
7591b0e6146SDuncan Sands {
7601b0e6146SDuncan Sands int ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0);
761cd32fbadSAaron Raimist
7621b0e6146SDuncan Sands if (ret < 0) { /* firmware not loaded */
76377c9e125SGreg Kroah-Hartman usb_dbg(instance->usbatm, "cxacru_adsl_start: CARD_GET_STATUS returned %d\n", ret);
7641b0e6146SDuncan Sands return ret;
7651b0e6146SDuncan Sands }
7661b0e6146SDuncan Sands return 0;
7671b0e6146SDuncan Sands }
7681b0e6146SDuncan Sands
cxacru_atm_start(struct usbatm_data * usbatm_instance,struct atm_dev * atm_dev)7691b0e6146SDuncan Sands static int cxacru_atm_start(struct usbatm_data *usbatm_instance,
7701b0e6146SDuncan Sands struct atm_dev *atm_dev)
7711b0e6146SDuncan Sands {
7721b0e6146SDuncan Sands struct cxacru_data *instance = usbatm_instance->driver_data;
773da1f82b5SSimon Arlott struct usb_interface *intf = usbatm_instance->usb_intf;
7741b0e6146SDuncan Sands int ret;
7756a02c996SSimon Arlott int start_polling = 1;
7761b0e6146SDuncan Sands
77777c9e125SGreg Kroah-Hartman dev_dbg(&intf->dev, "%s\n", __func__);
7781b0e6146SDuncan Sands
7791b0e6146SDuncan Sands /* Read MAC address */
7801b0e6146SDuncan Sands ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_MAC_ADDRESS, NULL, 0,
7811b0e6146SDuncan Sands atm_dev->esi, sizeof(atm_dev->esi));
7821b0e6146SDuncan Sands if (ret < 0) {
7830ec3c7e8SDuncan Sands atm_err(usbatm_instance, "cxacru_atm_start: CARD_GET_MAC_ADDRESS returned %d\n", ret);
7841b0e6146SDuncan Sands return ret;
7851b0e6146SDuncan Sands }
7861b0e6146SDuncan Sands
7871b0e6146SDuncan Sands /* start ADSL */
7886a02c996SSimon Arlott mutex_lock(&instance->adsl_state_serialize);
7891b0e6146SDuncan Sands ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0);
790fd209e35SSimon Arlott if (ret < 0)
7910ec3c7e8SDuncan Sands atm_err(usbatm_instance, "cxacru_atm_start: CHIP_ADSL_LINE_START returned %d\n", ret);
7921b0e6146SDuncan Sands
7931b0e6146SDuncan Sands /* Start status polling */
7946a02c996SSimon Arlott mutex_lock(&instance->poll_state_serialize);
7956a02c996SSimon Arlott switch (instance->poll_state) {
7966a02c996SSimon Arlott case CXPOLL_STOPPED:
7976a02c996SSimon Arlott /* start polling */
7986a02c996SSimon Arlott instance->poll_state = CXPOLL_POLLING;
7996a02c996SSimon Arlott break;
8006a02c996SSimon Arlott
8016a02c996SSimon Arlott case CXPOLL_STOPPING:
8026a02c996SSimon Arlott /* abort stop request */
8036a02c996SSimon Arlott instance->poll_state = CXPOLL_POLLING;
8040d9b6d49SGustavo A. R. Silva fallthrough;
8056a02c996SSimon Arlott case CXPOLL_POLLING:
8066a02c996SSimon Arlott case CXPOLL_SHUTDOWN:
8076a02c996SSimon Arlott /* don't start polling */
8086a02c996SSimon Arlott start_polling = 0;
8096a02c996SSimon Arlott }
8106a02c996SSimon Arlott mutex_unlock(&instance->poll_state_serialize);
8116a02c996SSimon Arlott mutex_unlock(&instance->adsl_state_serialize);
8126a02c996SSimon Arlott
8136a02c996SSimon Arlott if (start_polling)
814c4028958SDavid Howells cxacru_poll_status(&instance->poll_work.work);
8151b0e6146SDuncan Sands return 0;
8161b0e6146SDuncan Sands }
8171b0e6146SDuncan Sands
cxacru_poll_status(struct work_struct * work)818c4028958SDavid Howells static void cxacru_poll_status(struct work_struct *work)
8191b0e6146SDuncan Sands {
820c4028958SDavid Howells struct cxacru_data *instance =
821c4028958SDavid Howells container_of(work, struct cxacru_data, poll_work.work);
8221b0e6146SDuncan Sands u32 buf[CXINF_MAX] = {};
8230ec3c7e8SDuncan Sands struct usbatm_data *usbatm = instance->usbatm;
8240ec3c7e8SDuncan Sands struct atm_dev *atm_dev = usbatm->atm_dev;
8256a02c996SSimon Arlott int keep_polling = 1;
8261b0e6146SDuncan Sands int ret;
8271b0e6146SDuncan Sands
8281b0e6146SDuncan Sands ret = cxacru_cm_get_array(instance, CM_REQUEST_CARD_INFO_GET, buf, CXINF_MAX);
8291b0e6146SDuncan Sands if (ret < 0) {
8306a02c996SSimon Arlott if (ret != -ESHUTDOWN)
8310ec3c7e8SDuncan Sands atm_warn(usbatm, "poll status: error %d\n", ret);
8326a02c996SSimon Arlott
8336a02c996SSimon Arlott mutex_lock(&instance->poll_state_serialize);
8346a02c996SSimon Arlott if (instance->poll_state != CXPOLL_SHUTDOWN) {
8356a02c996SSimon Arlott instance->poll_state = CXPOLL_STOPPED;
8366a02c996SSimon Arlott
8376a02c996SSimon Arlott if (ret != -ESHUTDOWN)
8386a02c996SSimon Arlott atm_warn(usbatm, "polling disabled, set adsl_state"
8396a02c996SSimon Arlott " to 'start' or 'poll' to resume\n");
8406a02c996SSimon Arlott }
8416a02c996SSimon Arlott mutex_unlock(&instance->poll_state_serialize);
8421b0e6146SDuncan Sands goto reschedule;
8431b0e6146SDuncan Sands }
8441b0e6146SDuncan Sands
845fa70fe44SSimon Arlott memcpy(instance->card_info, buf, sizeof(instance->card_info));
846fa70fe44SSimon Arlott
8476a02c996SSimon Arlott if (instance->adsl_status != buf[CXINF_LINE_STARTABLE]) {
8486a02c996SSimon Arlott instance->adsl_status = buf[CXINF_LINE_STARTABLE];
8496a02c996SSimon Arlott
8506a02c996SSimon Arlott switch (instance->adsl_status) {
8516a02c996SSimon Arlott case 0:
8526d4e3866SEnrico Weigelt, metux IT consult atm_info(usbatm, "ADSL state: running\n");
8536a02c996SSimon Arlott break;
8546a02c996SSimon Arlott
8556a02c996SSimon Arlott case 1:
8566d4e3866SEnrico Weigelt, metux IT consult atm_info(usbatm, "ADSL state: stopped\n");
8576a02c996SSimon Arlott break;
8586a02c996SSimon Arlott
8596a02c996SSimon Arlott default:
8606d4e3866SEnrico Weigelt, metux IT consult atm_info(usbatm, "Unknown adsl status %02x\n", instance->adsl_status);
8616a02c996SSimon Arlott break;
8626a02c996SSimon Arlott }
8636a02c996SSimon Arlott }
8646a02c996SSimon Arlott
8651b0e6146SDuncan Sands if (instance->line_status == buf[CXINF_LINE_STATUS])
8661b0e6146SDuncan Sands goto reschedule;
8671b0e6146SDuncan Sands
8681b0e6146SDuncan Sands instance->line_status = buf[CXINF_LINE_STATUS];
8691b0e6146SDuncan Sands switch (instance->line_status) {
8701b0e6146SDuncan Sands case 0:
871676f3d26SKarl Hiramoto atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
8720ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: down\n");
8731b0e6146SDuncan Sands break;
8741b0e6146SDuncan Sands
8751b0e6146SDuncan Sands case 1:
876676f3d26SKarl Hiramoto atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
8770ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: attempting to activate\n");
8781b0e6146SDuncan Sands break;
8791b0e6146SDuncan Sands
8801b0e6146SDuncan Sands case 2:
881676f3d26SKarl Hiramoto atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
8820ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: training\n");
8831b0e6146SDuncan Sands break;
8841b0e6146SDuncan Sands
8851b0e6146SDuncan Sands case 3:
886676f3d26SKarl Hiramoto atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
8870ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: channel analysis\n");
8881b0e6146SDuncan Sands break;
8891b0e6146SDuncan Sands
8901b0e6146SDuncan Sands case 4:
891676f3d26SKarl Hiramoto atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
8920ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: exchange\n");
8931b0e6146SDuncan Sands break;
8941b0e6146SDuncan Sands
8951b0e6146SDuncan Sands case 5:
8961b0e6146SDuncan Sands atm_dev->link_rate = buf[CXINF_DOWNSTREAM_RATE] * 1000 / 424;
897676f3d26SKarl Hiramoto atm_dev_signal_change(atm_dev, ATM_PHY_SIG_FOUND);
8981b0e6146SDuncan Sands
8990ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: up (%d kb/s down | %d kb/s up)\n",
9001b0e6146SDuncan Sands buf[CXINF_DOWNSTREAM_RATE], buf[CXINF_UPSTREAM_RATE]);
9011b0e6146SDuncan Sands break;
9021b0e6146SDuncan Sands
9031b0e6146SDuncan Sands case 6:
904676f3d26SKarl Hiramoto atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
9050ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: waiting\n");
9061b0e6146SDuncan Sands break;
9071b0e6146SDuncan Sands
9081b0e6146SDuncan Sands case 7:
909676f3d26SKarl Hiramoto atm_dev_signal_change(atm_dev, ATM_PHY_SIG_LOST);
9100ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: initializing\n");
9111b0e6146SDuncan Sands break;
9121b0e6146SDuncan Sands
9131b0e6146SDuncan Sands default:
914676f3d26SKarl Hiramoto atm_dev_signal_change(atm_dev, ATM_PHY_SIG_UNKNOWN);
9150ec3c7e8SDuncan Sands atm_info(usbatm, "Unknown line state %02x\n", instance->line_status);
9161b0e6146SDuncan Sands break;
9171b0e6146SDuncan Sands }
9181b0e6146SDuncan Sands reschedule:
9196a02c996SSimon Arlott
9206a02c996SSimon Arlott mutex_lock(&instance->poll_state_serialize);
9216a02c996SSimon Arlott if (instance->poll_state == CXPOLL_STOPPING &&
9226a02c996SSimon Arlott instance->adsl_status == 1 && /* stopped */
9236a02c996SSimon Arlott instance->line_status == 0) /* down */
9246a02c996SSimon Arlott instance->poll_state = CXPOLL_STOPPED;
9256a02c996SSimon Arlott
9266a02c996SSimon Arlott if (instance->poll_state == CXPOLL_STOPPED)
9276a02c996SSimon Arlott keep_polling = 0;
9286a02c996SSimon Arlott mutex_unlock(&instance->poll_state_serialize);
9296a02c996SSimon Arlott
9306a02c996SSimon Arlott if (keep_polling)
931fa70fe44SSimon Arlott schedule_delayed_work(&instance->poll_work,
9326a02c996SSimon Arlott round_jiffies_relative(POLL_INTERVAL*HZ));
9331b0e6146SDuncan Sands }
9341b0e6146SDuncan Sands
cxacru_fw(struct usb_device * usb_dev,enum cxacru_fw_request fw,u8 code1,u8 code2,u32 addr,const u8 * data,int size)9351b0e6146SDuncan Sands static int cxacru_fw(struct usb_device *usb_dev, enum cxacru_fw_request fw,
9363b216d18SDavid Woodhouse u8 code1, u8 code2, u32 addr, const u8 *data, int size)
9371b0e6146SDuncan Sands {
9381b0e6146SDuncan Sands int ret;
9391b0e6146SDuncan Sands u8 *buf;
9401b0e6146SDuncan Sands int offd, offb;
9411b0e6146SDuncan Sands const int stride = CMD_PACKET_SIZE - 8;
9421b0e6146SDuncan Sands
9431b0e6146SDuncan Sands buf = (u8 *) __get_free_page(GFP_KERNEL);
9441b0e6146SDuncan Sands if (!buf)
9451b0e6146SDuncan Sands return -ENOMEM;
9461b0e6146SDuncan Sands
9471b0e6146SDuncan Sands offb = offd = 0;
9481b0e6146SDuncan Sands do {
9491b0e6146SDuncan Sands int l = min_t(int, stride, size - offd);
950cd32fbadSAaron Raimist
9511b0e6146SDuncan Sands buf[offb++] = fw;
9521b0e6146SDuncan Sands buf[offb++] = l;
9531b0e6146SDuncan Sands buf[offb++] = code1;
9541b0e6146SDuncan Sands buf[offb++] = code2;
955fd05e720SAl Viro put_unaligned(cpu_to_le32(addr), (__le32 *)(buf + offb));
9561b0e6146SDuncan Sands offb += 4;
9571b0e6146SDuncan Sands addr += l;
9581b0e6146SDuncan Sands if (l)
9591b0e6146SDuncan Sands memcpy(buf + offb, data + offd, l);
9601b0e6146SDuncan Sands if (l < stride)
9611b0e6146SDuncan Sands memset(buf + offb + l, 0, stride - l);
9621b0e6146SDuncan Sands offb += stride;
9631b0e6146SDuncan Sands offd += stride;
9641b0e6146SDuncan Sands if ((offb >= PAGE_SIZE) || (offd >= size)) {
9651b0e6146SDuncan Sands ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD),
9661b0e6146SDuncan Sands buf, offb, NULL, CMD_TIMEOUT);
9671b0e6146SDuncan Sands if (ret < 0) {
96877c9e125SGreg Kroah-Hartman dev_dbg(&usb_dev->dev, "sending fw %#x failed\n", fw);
9691b0e6146SDuncan Sands goto cleanup;
9701b0e6146SDuncan Sands }
9711b0e6146SDuncan Sands offb = 0;
9721b0e6146SDuncan Sands }
9731b0e6146SDuncan Sands } while (offd < size);
97477c9e125SGreg Kroah-Hartman dev_dbg(&usb_dev->dev, "sent fw %#x\n", fw);
9751b0e6146SDuncan Sands
9761b0e6146SDuncan Sands ret = 0;
9771b0e6146SDuncan Sands
9781b0e6146SDuncan Sands cleanup:
9791b0e6146SDuncan Sands free_page((unsigned long) buf);
9801b0e6146SDuncan Sands return ret;
9811b0e6146SDuncan Sands }
9821b0e6146SDuncan Sands
cxacru_upload_firmware(struct cxacru_data * instance,const struct firmware * fw,const struct firmware * bp)9831b0e6146SDuncan Sands static void cxacru_upload_firmware(struct cxacru_data *instance,
9841b0e6146SDuncan Sands const struct firmware *fw,
985817db5b3SSimon Arlott const struct firmware *bp)
9861b0e6146SDuncan Sands {
9871b0e6146SDuncan Sands int ret;
9880ec3c7e8SDuncan Sands struct usbatm_data *usbatm = instance->usbatm;
9890ec3c7e8SDuncan Sands struct usb_device *usb_dev = usbatm->usb_dev;
990fd05e720SAl Viro __le16 signature[] = { usb_dev->descriptor.idVendor,
991fd05e720SAl Viro usb_dev->descriptor.idProduct };
992fd05e720SAl Viro __le32 val;
9931b0e6146SDuncan Sands
99477c9e125SGreg Kroah-Hartman usb_dbg(usbatm, "%s\n", __func__);
9951b0e6146SDuncan Sands
9961b0e6146SDuncan Sands /* FirmwarePllFClkValue */
9971b0e6146SDuncan Sands val = cpu_to_le32(instance->modem_type->pll_f_clk);
9981b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, PLLFCLK_ADDR, (u8 *) &val, 4);
9991b0e6146SDuncan Sands if (ret) {
10000ec3c7e8SDuncan Sands usb_err(usbatm, "FirmwarePllFClkValue failed: %d\n", ret);
10011b0e6146SDuncan Sands return;
10021b0e6146SDuncan Sands }
10031b0e6146SDuncan Sands
10041b0e6146SDuncan Sands /* FirmwarePllBClkValue */
10051b0e6146SDuncan Sands val = cpu_to_le32(instance->modem_type->pll_b_clk);
10061b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, PLLBCLK_ADDR, (u8 *) &val, 4);
10071b0e6146SDuncan Sands if (ret) {
10080ec3c7e8SDuncan Sands usb_err(usbatm, "FirmwarePllBClkValue failed: %d\n", ret);
10091b0e6146SDuncan Sands return;
10101b0e6146SDuncan Sands }
10111b0e6146SDuncan Sands
10121b0e6146SDuncan Sands /* Enable SDRAM */
10131b0e6146SDuncan Sands val = cpu_to_le32(SDRAM_ENA);
10141b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, SDRAMEN_ADDR, (u8 *) &val, 4);
10151b0e6146SDuncan Sands if (ret) {
10160ec3c7e8SDuncan Sands usb_err(usbatm, "Enable SDRAM failed: %d\n", ret);
10171b0e6146SDuncan Sands return;
10181b0e6146SDuncan Sands }
10191b0e6146SDuncan Sands
10201b0e6146SDuncan Sands /* Firmware */
1021885582c4SSimon Arlott usb_info(usbatm, "loading firmware\n");
10221b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, FW_ADDR, fw->data, fw->size);
10231b0e6146SDuncan Sands if (ret) {
10240ec3c7e8SDuncan Sands usb_err(usbatm, "Firmware upload failed: %d\n", ret);
10251b0e6146SDuncan Sands return;
10261b0e6146SDuncan Sands }
10271b0e6146SDuncan Sands
10281b0e6146SDuncan Sands /* Boot ROM patch */
10291b0e6146SDuncan Sands if (instance->modem_type->boot_rom_patch) {
1030885582c4SSimon Arlott usb_info(usbatm, "loading boot ROM patch\n");
10311b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, BR_ADDR, bp->data, bp->size);
10321b0e6146SDuncan Sands if (ret) {
10330ec3c7e8SDuncan Sands usb_err(usbatm, "Boot ROM patching failed: %d\n", ret);
10341b0e6146SDuncan Sands return;
10351b0e6146SDuncan Sands }
10361b0e6146SDuncan Sands }
10371b0e6146SDuncan Sands
10381b0e6146SDuncan Sands /* Signature */
10391b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, SIG_ADDR, (u8 *) signature, 4);
10401b0e6146SDuncan Sands if (ret) {
10410ec3c7e8SDuncan Sands usb_err(usbatm, "Signature storing failed: %d\n", ret);
10421b0e6146SDuncan Sands return;
10431b0e6146SDuncan Sands }
10441b0e6146SDuncan Sands
1045885582c4SSimon Arlott usb_info(usbatm, "starting device\n");
10461b0e6146SDuncan Sands if (instance->modem_type->boot_rom_patch) {
10471b0e6146SDuncan Sands val = cpu_to_le32(BR_ADDR);
10481b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, BR_STACK_ADDR, (u8 *) &val, 4);
104983a3ac86SNicolas Kaiser } else {
10501b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_GOTO_MEM, 0x0, 0x0, FW_ADDR, NULL, 0);
10511b0e6146SDuncan Sands }
10521b0e6146SDuncan Sands if (ret) {
10530ec3c7e8SDuncan Sands usb_err(usbatm, "Passing control to firmware failed: %d\n", ret);
10541b0e6146SDuncan Sands return;
10551b0e6146SDuncan Sands }
10561b0e6146SDuncan Sands
10571b0e6146SDuncan Sands /* Delay to allow firmware to start up. */
10581b0e6146SDuncan Sands msleep_interruptible(1000);
10591b0e6146SDuncan Sands
10601b0e6146SDuncan Sands usb_clear_halt(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD));
10611b0e6146SDuncan Sands usb_clear_halt(usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_CMD));
10621b0e6146SDuncan Sands usb_clear_halt(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_DATA));
10631b0e6146SDuncan Sands usb_clear_halt(usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_DATA));
10641b0e6146SDuncan Sands
10651b0e6146SDuncan Sands ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0);
10661b0e6146SDuncan Sands if (ret < 0) {
10670ec3c7e8SDuncan Sands usb_err(usbatm, "modem failed to initialize: %d\n", ret);
10681b0e6146SDuncan Sands return;
10691b0e6146SDuncan Sands }
10701b0e6146SDuncan Sands }
10711b0e6146SDuncan Sands
cxacru_find_firmware(struct cxacru_data * instance,char * phase,const struct firmware ** fw_p)10721b0e6146SDuncan Sands static int cxacru_find_firmware(struct cxacru_data *instance,
10731b0e6146SDuncan Sands char *phase, const struct firmware **fw_p)
10741b0e6146SDuncan Sands {
10750ec3c7e8SDuncan Sands struct usbatm_data *usbatm = instance->usbatm;
10760ec3c7e8SDuncan Sands struct device *dev = &usbatm->usb_intf->dev;
10771b0e6146SDuncan Sands char buf[16];
10781b0e6146SDuncan Sands
10791b0e6146SDuncan Sands sprintf(buf, "cxacru-%s.bin", phase);
108077c9e125SGreg Kroah-Hartman usb_dbg(usbatm, "cxacru_find_firmware: looking for %s\n", buf);
10811b0e6146SDuncan Sands
10821b0e6146SDuncan Sands if (request_firmware(fw_p, buf, dev)) {
10830ec3c7e8SDuncan Sands usb_dbg(usbatm, "no stage %s firmware found\n", phase);
10841b0e6146SDuncan Sands return -ENOENT;
10851b0e6146SDuncan Sands }
10861b0e6146SDuncan Sands
10870ec3c7e8SDuncan Sands usb_info(usbatm, "found firmware %s\n", buf);
10881b0e6146SDuncan Sands
10891b0e6146SDuncan Sands return 0;
10901b0e6146SDuncan Sands }
10911b0e6146SDuncan Sands
cxacru_heavy_init(struct usbatm_data * usbatm_instance,struct usb_interface * usb_intf)10921b0e6146SDuncan Sands static int cxacru_heavy_init(struct usbatm_data *usbatm_instance,
10931b0e6146SDuncan Sands struct usb_interface *usb_intf)
10941b0e6146SDuncan Sands {
1095817db5b3SSimon Arlott const struct firmware *fw, *bp;
10961b0e6146SDuncan Sands struct cxacru_data *instance = usbatm_instance->driver_data;
10971b0e6146SDuncan Sands int ret = cxacru_find_firmware(instance, "fw", &fw);
1098cd32fbadSAaron Raimist
10991b0e6146SDuncan Sands if (ret) {
11000ec3c7e8SDuncan Sands usb_warn(usbatm_instance, "firmware (cxacru-fw.bin) unavailable (system misconfigured?)\n");
11011b0e6146SDuncan Sands return ret;
11021b0e6146SDuncan Sands }
11031b0e6146SDuncan Sands
11041b0e6146SDuncan Sands if (instance->modem_type->boot_rom_patch) {
11051b0e6146SDuncan Sands ret = cxacru_find_firmware(instance, "bp", &bp);
11061b0e6146SDuncan Sands if (ret) {
11070ec3c7e8SDuncan Sands usb_warn(usbatm_instance, "boot ROM patch (cxacru-bp.bin) unavailable (system misconfigured?)\n");
11081b0e6146SDuncan Sands release_firmware(fw);
11091b0e6146SDuncan Sands return ret;
11101b0e6146SDuncan Sands }
11111b0e6146SDuncan Sands }
11121b0e6146SDuncan Sands
1113817db5b3SSimon Arlott cxacru_upload_firmware(instance, fw, bp);
11141b0e6146SDuncan Sands
11151b0e6146SDuncan Sands if (instance->modem_type->boot_rom_patch)
11161b0e6146SDuncan Sands release_firmware(bp);
11171b0e6146SDuncan Sands release_firmware(fw);
11181b0e6146SDuncan Sands
11191b0e6146SDuncan Sands ret = cxacru_card_status(instance);
11201b0e6146SDuncan Sands if (ret)
112177c9e125SGreg Kroah-Hartman usb_dbg(usbatm_instance, "modem initialisation failed\n");
11221b0e6146SDuncan Sands else
112377c9e125SGreg Kroah-Hartman usb_dbg(usbatm_instance, "done setting up the modem\n");
11241b0e6146SDuncan Sands
11251b0e6146SDuncan Sands return ret;
11261b0e6146SDuncan Sands }
11271b0e6146SDuncan Sands
cxacru_bind(struct usbatm_data * usbatm_instance,struct usb_interface * intf,const struct usb_device_id * id)11281b0e6146SDuncan Sands static int cxacru_bind(struct usbatm_data *usbatm_instance,
112935644b0cSDuncan Sands struct usb_interface *intf, const struct usb_device_id *id)
11301b0e6146SDuncan Sands {
11311b0e6146SDuncan Sands struct cxacru_data *instance;
11321b0e6146SDuncan Sands struct usb_device *usb_dev = interface_to_usbdev(intf);
1133902ffc3cSSimon Arlott struct usb_host_endpoint *cmd_ep = usb_dev->ep_in[CXACRU_EP_CMD];
1134*a0475a88SNikita Zhandarovich static const u8 ep_addrs[] = {
1135*a0475a88SNikita Zhandarovich CXACRU_EP_CMD + USB_DIR_IN,
1136*a0475a88SNikita Zhandarovich CXACRU_EP_CMD + USB_DIR_OUT,
1137*a0475a88SNikita Zhandarovich 0};
11381b0e6146SDuncan Sands int ret;
11391b0e6146SDuncan Sands
11401b0e6146SDuncan Sands /* instance init */
11419a734efeSDuncan Sands instance = kzalloc(sizeof(*instance), GFP_KERNEL);
114204e75e49SWolfram Sang if (!instance)
11431b0e6146SDuncan Sands return -ENOMEM;
11441b0e6146SDuncan Sands
11451b0e6146SDuncan Sands instance->usbatm = usbatm_instance;
11461b0e6146SDuncan Sands instance->modem_type = (struct cxacru_modem_type *) id->driver_info;
11471b0e6146SDuncan Sands
11486a02c996SSimon Arlott mutex_init(&instance->poll_state_serialize);
11496a02c996SSimon Arlott instance->poll_state = CXPOLL_STOPPED;
11506a02c996SSimon Arlott instance->line_status = -1;
11516a02c996SSimon Arlott instance->adsl_status = -1;
11526a02c996SSimon Arlott
11536a02c996SSimon Arlott mutex_init(&instance->adsl_state_serialize);
11546a02c996SSimon Arlott
11551b0e6146SDuncan Sands instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL);
11561b0e6146SDuncan Sands if (!instance->rcv_buf) {
115777c9e125SGreg Kroah-Hartman usb_dbg(usbatm_instance, "cxacru_bind: no memory for rcv_buf\n");
11581b0e6146SDuncan Sands ret = -ENOMEM;
11591b0e6146SDuncan Sands goto fail;
11601b0e6146SDuncan Sands }
11611b0e6146SDuncan Sands instance->snd_buf = (u8 *) __get_free_page(GFP_KERNEL);
11621b0e6146SDuncan Sands if (!instance->snd_buf) {
116377c9e125SGreg Kroah-Hartman usb_dbg(usbatm_instance, "cxacru_bind: no memory for snd_buf\n");
11641b0e6146SDuncan Sands ret = -ENOMEM;
11651b0e6146SDuncan Sands goto fail;
11661b0e6146SDuncan Sands }
11671b0e6146SDuncan Sands instance->rcv_urb = usb_alloc_urb(0, GFP_KERNEL);
11681b0e6146SDuncan Sands if (!instance->rcv_urb) {
11691b0e6146SDuncan Sands ret = -ENOMEM;
11701b0e6146SDuncan Sands goto fail;
11711b0e6146SDuncan Sands }
11721b0e6146SDuncan Sands instance->snd_urb = usb_alloc_urb(0, GFP_KERNEL);
11731b0e6146SDuncan Sands if (!instance->snd_urb) {
11741b0e6146SDuncan Sands ret = -ENOMEM;
11751b0e6146SDuncan Sands goto fail;
11761b0e6146SDuncan Sands }
11771b0e6146SDuncan Sands
1178902ffc3cSSimon Arlott if (!cmd_ep) {
117977c9e125SGreg Kroah-Hartman usb_dbg(usbatm_instance, "cxacru_bind: no command endpoint\n");
1180902ffc3cSSimon Arlott ret = -ENODEV;
1181902ffc3cSSimon Arlott goto fail;
1182902ffc3cSSimon Arlott }
1183902ffc3cSSimon Arlott
1184f536f09eSNikita Zhandarovich if (usb_endpoint_xfer_int(&cmd_ep->desc))
1185*a0475a88SNikita Zhandarovich ret = usb_check_int_endpoints(intf, ep_addrs);
1186f536f09eSNikita Zhandarovich else
1187*a0475a88SNikita Zhandarovich ret = usb_check_bulk_endpoints(intf, ep_addrs);
1188f536f09eSNikita Zhandarovich
1189*a0475a88SNikita Zhandarovich if (!ret) {
1190f536f09eSNikita Zhandarovich usb_err(usbatm_instance, "cxacru_bind: interface has incorrect endpoints\n");
1191f536f09eSNikita Zhandarovich ret = -ENODEV;
1192f536f09eSNikita Zhandarovich goto fail;
1193f536f09eSNikita Zhandarovich }
1194f536f09eSNikita Zhandarovich
1195902ffc3cSSimon Arlott if ((cmd_ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
1196902ffc3cSSimon Arlott == USB_ENDPOINT_XFER_INT) {
11971b0e6146SDuncan Sands usb_fill_int_urb(instance->rcv_urb,
11981b0e6146SDuncan Sands usb_dev, usb_rcvintpipe(usb_dev, CXACRU_EP_CMD),
11991b0e6146SDuncan Sands instance->rcv_buf, PAGE_SIZE,
12001b0e6146SDuncan Sands cxacru_blocking_completion, &instance->rcv_done, 1);
12011b0e6146SDuncan Sands
12021b0e6146SDuncan Sands usb_fill_int_urb(instance->snd_urb,
12031b0e6146SDuncan Sands usb_dev, usb_sndintpipe(usb_dev, CXACRU_EP_CMD),
12041b0e6146SDuncan Sands instance->snd_buf, PAGE_SIZE,
12051b0e6146SDuncan Sands cxacru_blocking_completion, &instance->snd_done, 4);
1206902ffc3cSSimon Arlott } else {
1207902ffc3cSSimon Arlott usb_fill_bulk_urb(instance->rcv_urb,
1208902ffc3cSSimon Arlott usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_CMD),
1209902ffc3cSSimon Arlott instance->rcv_buf, PAGE_SIZE,
1210902ffc3cSSimon Arlott cxacru_blocking_completion, &instance->rcv_done);
1211902ffc3cSSimon Arlott
1212902ffc3cSSimon Arlott usb_fill_bulk_urb(instance->snd_urb,
1213902ffc3cSSimon Arlott usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD),
1214902ffc3cSSimon Arlott instance->snd_buf, PAGE_SIZE,
1215902ffc3cSSimon Arlott cxacru_blocking_completion, &instance->snd_done);
1216902ffc3cSSimon Arlott }
12171b0e6146SDuncan Sands
1218ab3c81ffSArjan van de Ven mutex_init(&instance->cm_serialize);
12191b0e6146SDuncan Sands
1220c4028958SDavid Howells INIT_DELAYED_WORK(&instance->poll_work, cxacru_poll_status);
12211b0e6146SDuncan Sands
12221b0e6146SDuncan Sands usbatm_instance->driver_data = instance;
12231b0e6146SDuncan Sands
122435644b0cSDuncan Sands usbatm_instance->flags = (cxacru_card_status(instance) ? 0 : UDSL_SKIP_HEAVY_INIT);
12251b0e6146SDuncan Sands
12261b0e6146SDuncan Sands return 0;
12271b0e6146SDuncan Sands
12281b0e6146SDuncan Sands fail:
12291b0e6146SDuncan Sands free_page((unsigned long) instance->snd_buf);
12301b0e6146SDuncan Sands free_page((unsigned long) instance->rcv_buf);
12311b0e6146SDuncan Sands usb_free_urb(instance->snd_urb);
12321b0e6146SDuncan Sands usb_free_urb(instance->rcv_urb);
12331b0e6146SDuncan Sands kfree(instance);
12341b0e6146SDuncan Sands
12351b0e6146SDuncan Sands return ret;
12361b0e6146SDuncan Sands }
12371b0e6146SDuncan Sands
cxacru_unbind(struct usbatm_data * usbatm_instance,struct usb_interface * intf)12381b0e6146SDuncan Sands static void cxacru_unbind(struct usbatm_data *usbatm_instance,
12391b0e6146SDuncan Sands struct usb_interface *intf)
12401b0e6146SDuncan Sands {
12411b0e6146SDuncan Sands struct cxacru_data *instance = usbatm_instance->driver_data;
12426a02c996SSimon Arlott int is_polling = 1;
12431b0e6146SDuncan Sands
124477c9e125SGreg Kroah-Hartman usb_dbg(usbatm_instance, "cxacru_unbind entered\n");
12451b0e6146SDuncan Sands
12461b0e6146SDuncan Sands if (!instance) {
124777c9e125SGreg Kroah-Hartman usb_dbg(usbatm_instance, "cxacru_unbind: NULL instance!\n");
12481b0e6146SDuncan Sands return;
12491b0e6146SDuncan Sands }
12501b0e6146SDuncan Sands
12516a02c996SSimon Arlott mutex_lock(&instance->poll_state_serialize);
12526a02c996SSimon Arlott BUG_ON(instance->poll_state == CXPOLL_SHUTDOWN);
12536a02c996SSimon Arlott
12546a02c996SSimon Arlott /* ensure that status polling continues unless
12556a02c996SSimon Arlott * it has already stopped */
12566a02c996SSimon Arlott if (instance->poll_state == CXPOLL_STOPPED)
12576a02c996SSimon Arlott is_polling = 0;
12586a02c996SSimon Arlott
12596a02c996SSimon Arlott /* stop polling from being stopped or started */
12606a02c996SSimon Arlott instance->poll_state = CXPOLL_SHUTDOWN;
12616a02c996SSimon Arlott mutex_unlock(&instance->poll_state_serialize);
12626a02c996SSimon Arlott
12636a02c996SSimon Arlott if (is_polling)
1264afe2c511STejun Heo cancel_delayed_work_sync(&instance->poll_work);
12651b0e6146SDuncan Sands
12661b0e6146SDuncan Sands usb_kill_urb(instance->snd_urb);
12671b0e6146SDuncan Sands usb_kill_urb(instance->rcv_urb);
12681b0e6146SDuncan Sands usb_free_urb(instance->snd_urb);
12691b0e6146SDuncan Sands usb_free_urb(instance->rcv_urb);
12701b0e6146SDuncan Sands
12711b0e6146SDuncan Sands free_page((unsigned long) instance->snd_buf);
12721b0e6146SDuncan Sands free_page((unsigned long) instance->rcv_buf);
1273fa70fe44SSimon Arlott
12741b0e6146SDuncan Sands kfree(instance);
12751b0e6146SDuncan Sands
12761b0e6146SDuncan Sands usbatm_instance->driver_data = NULL;
12771b0e6146SDuncan Sands }
12781b0e6146SDuncan Sands
12791b0e6146SDuncan Sands static const struct cxacru_modem_type cxacru_cafe = {
12801b0e6146SDuncan Sands .pll_f_clk = 0x02d874df,
12811b0e6146SDuncan Sands .pll_b_clk = 0x0196a51a,
12821b0e6146SDuncan Sands .boot_rom_patch = 1,
12831b0e6146SDuncan Sands };
12841b0e6146SDuncan Sands
12851b0e6146SDuncan Sands static const struct cxacru_modem_type cxacru_cb00 = {
12861b0e6146SDuncan Sands .pll_f_clk = 0x5,
12871b0e6146SDuncan Sands .pll_b_clk = 0x3,
12881b0e6146SDuncan Sands .boot_rom_patch = 0,
12891b0e6146SDuncan Sands };
12901b0e6146SDuncan Sands
12911b0e6146SDuncan Sands static const struct usb_device_id cxacru_usb_ids[] = {
12921b0e6146SDuncan Sands { /* V = Conexant P = ADSL modem (Euphrates project) */
12931b0e6146SDuncan Sands USB_DEVICE(0x0572, 0xcafe), .driver_info = (unsigned long) &cxacru_cafe
12941b0e6146SDuncan Sands },
12951b0e6146SDuncan Sands { /* V = Conexant P = ADSL modem (Hasbani project) */
12961b0e6146SDuncan Sands USB_DEVICE(0x0572, 0xcb00), .driver_info = (unsigned long) &cxacru_cb00
12971b0e6146SDuncan Sands },
12981b0e6146SDuncan Sands { /* V = Conexant P = ADSL modem */
12991b0e6146SDuncan Sands USB_DEVICE(0x0572, 0xcb01), .driver_info = (unsigned long) &cxacru_cb00
13001b0e6146SDuncan Sands },
13010ec3c7e8SDuncan Sands { /* V = Conexant P = ADSL modem (Well PTI-800) */
13020ec3c7e8SDuncan Sands USB_DEVICE(0x0572, 0xcb02), .driver_info = (unsigned long) &cxacru_cb00
13030ec3c7e8SDuncan Sands },
13041b0e6146SDuncan Sands { /* V = Conexant P = ADSL modem */
13051b0e6146SDuncan Sands USB_DEVICE(0x0572, 0xcb06), .driver_info = (unsigned long) &cxacru_cb00
13061b0e6146SDuncan Sands },
130744960af1SDuncan Sands { /* V = Conexant P = ADSL modem (ZTE ZXDSL 852) */
130844960af1SDuncan Sands USB_DEVICE(0x0572, 0xcb07), .driver_info = (unsigned long) &cxacru_cb00
130944960af1SDuncan Sands },
13101b0e6146SDuncan Sands { /* V = Olitec P = ADSL modem version 2 */
13111b0e6146SDuncan Sands USB_DEVICE(0x08e3, 0x0100), .driver_info = (unsigned long) &cxacru_cafe
13121b0e6146SDuncan Sands },
13131b0e6146SDuncan Sands { /* V = Olitec P = ADSL modem version 3 */
13141b0e6146SDuncan Sands USB_DEVICE(0x08e3, 0x0102), .driver_info = (unsigned long) &cxacru_cb00
13151b0e6146SDuncan Sands },
13161b0e6146SDuncan Sands { /* V = Trust/Amigo Technology Co. P = AMX-CA86U */
13171b0e6146SDuncan Sands USB_DEVICE(0x0eb0, 0x3457), .driver_info = (unsigned long) &cxacru_cafe
13181b0e6146SDuncan Sands },
13191b0e6146SDuncan Sands { /* V = Zoom P = 5510 */
13201b0e6146SDuncan Sands USB_DEVICE(0x1803, 0x5510), .driver_info = (unsigned long) &cxacru_cb00
13211b0e6146SDuncan Sands },
13221b0e6146SDuncan Sands { /* V = Draytek P = Vigor 318 */
13231b0e6146SDuncan Sands USB_DEVICE(0x0675, 0x0200), .driver_info = (unsigned long) &cxacru_cb00
13241b0e6146SDuncan Sands },
13251b0e6146SDuncan Sands { /* V = Zyxel P = 630-C1 aka OMNI ADSL USB (Annex A) */
13261b0e6146SDuncan Sands USB_DEVICE(0x0586, 0x330a), .driver_info = (unsigned long) &cxacru_cb00
13271b0e6146SDuncan Sands },
13281b0e6146SDuncan Sands { /* V = Zyxel P = 630-C3 aka OMNI ADSL USB (Annex B) */
13291b0e6146SDuncan Sands USB_DEVICE(0x0586, 0x330b), .driver_info = (unsigned long) &cxacru_cb00
13301b0e6146SDuncan Sands },
13311b0e6146SDuncan Sands { /* V = Aethra P = Starmodem UM1020 */
13321b0e6146SDuncan Sands USB_DEVICE(0x0659, 0x0020), .driver_info = (unsigned long) &cxacru_cb00
13331b0e6146SDuncan Sands },
13341b0e6146SDuncan Sands { /* V = Aztech Systems P = ? AKA Pirelli AUA-010 */
13351b0e6146SDuncan Sands USB_DEVICE(0x0509, 0x0812), .driver_info = (unsigned long) &cxacru_cb00
13361b0e6146SDuncan Sands },
13371b0e6146SDuncan Sands { /* V = Netopia P = Cayman 3341(Annex A)/3351(Annex B) */
13381b0e6146SDuncan Sands USB_DEVICE(0x100d, 0xcb01), .driver_info = (unsigned long) &cxacru_cb00
13391b0e6146SDuncan Sands },
13401b0e6146SDuncan Sands { /* V = Netopia P = Cayman 3342(Annex A)/3352(Annex B) */
13411b0e6146SDuncan Sands USB_DEVICE(0x100d, 0x3342), .driver_info = (unsigned long) &cxacru_cb00
13421b0e6146SDuncan Sands },
13431b0e6146SDuncan Sands {}
13441b0e6146SDuncan Sands };
13451b0e6146SDuncan Sands
13461b0e6146SDuncan Sands MODULE_DEVICE_TABLE(usb, cxacru_usb_ids);
13471b0e6146SDuncan Sands
13481b0e6146SDuncan Sands static struct usbatm_driver cxacru_driver = {
13491b0e6146SDuncan Sands .driver_name = cxacru_driver_name,
13501b0e6146SDuncan Sands .bind = cxacru_bind,
13511b0e6146SDuncan Sands .heavy_init = cxacru_heavy_init,
13521b0e6146SDuncan Sands .unbind = cxacru_unbind,
13531b0e6146SDuncan Sands .atm_start = cxacru_atm_start,
135480aae7a1SDuncan Sands .bulk_in = CXACRU_EP_DATA,
135580aae7a1SDuncan Sands .bulk_out = CXACRU_EP_DATA,
13561b0e6146SDuncan Sands .rx_padding = 3,
13571b0e6146SDuncan Sands .tx_padding = 11,
13581b0e6146SDuncan Sands };
13591b0e6146SDuncan Sands
cxacru_usb_probe(struct usb_interface * intf,const struct usb_device_id * id)136092e32eaeSOndrej Zary static int cxacru_usb_probe(struct usb_interface *intf,
136192e32eaeSOndrej Zary const struct usb_device_id *id)
13621b0e6146SDuncan Sands {
136392e32eaeSOndrej Zary struct usb_device *usb_dev = interface_to_usbdev(intf);
136492e32eaeSOndrej Zary char buf[15];
136592e32eaeSOndrej Zary
136692e32eaeSOndrej Zary /* Avoid ADSL routers (cx82310_eth).
136792e32eaeSOndrej Zary * Abort if bDeviceClass is 0xff and iProduct is "USB NET CARD".
136892e32eaeSOndrej Zary */
136992e32eaeSOndrej Zary if (usb_dev->descriptor.bDeviceClass == USB_CLASS_VENDOR_SPEC
137092e32eaeSOndrej Zary && usb_string(usb_dev, usb_dev->descriptor.iProduct,
137192e32eaeSOndrej Zary buf, sizeof(buf)) > 0) {
137292e32eaeSOndrej Zary if (!strcmp(buf, "USB NET CARD")) {
137392e32eaeSOndrej Zary dev_info(&intf->dev, "ignoring cx82310_eth device\n");
137492e32eaeSOndrej Zary return -ENODEV;
137592e32eaeSOndrej Zary }
137692e32eaeSOndrej Zary }
137792e32eaeSOndrej Zary
13781b0e6146SDuncan Sands return usbatm_usb_probe(intf, id, &cxacru_driver);
13791b0e6146SDuncan Sands }
13801b0e6146SDuncan Sands
13811b0e6146SDuncan Sands static struct usb_driver cxacru_usb_driver = {
13821b0e6146SDuncan Sands .name = cxacru_driver_name,
13831b0e6146SDuncan Sands .probe = cxacru_usb_probe,
13841b0e6146SDuncan Sands .disconnect = usbatm_usb_disconnect,
1385e605c309SGreg Kroah-Hartman .id_table = cxacru_usb_ids,
1386e605c309SGreg Kroah-Hartman .dev_groups = cxacru_groups,
13871b0e6146SDuncan Sands };
13881b0e6146SDuncan Sands
138965db4305SGreg Kroah-Hartman module_usb_driver(cxacru_usb_driver);
13901b0e6146SDuncan Sands
13911b0e6146SDuncan Sands MODULE_AUTHOR(DRIVER_AUTHOR);
13921b0e6146SDuncan Sands MODULE_DESCRIPTION(DRIVER_DESC);
13931b0e6146SDuncan Sands MODULE_LICENSE("GPL");
1394