xref: /linux/drivers/usb/atm/cxacru.c (revision b274e2a4)
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 
183*b274e2a4Szuoqilin 	/* 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];
11341b0e6146SDuncan Sands 	int ret;
11351b0e6146SDuncan Sands 
11361b0e6146SDuncan Sands 	/* instance init */
11379a734efeSDuncan Sands 	instance = kzalloc(sizeof(*instance), GFP_KERNEL);
113804e75e49SWolfram Sang 	if (!instance)
11391b0e6146SDuncan Sands 		return -ENOMEM;
11401b0e6146SDuncan Sands 
11411b0e6146SDuncan Sands 	instance->usbatm = usbatm_instance;
11421b0e6146SDuncan Sands 	instance->modem_type = (struct cxacru_modem_type *) id->driver_info;
11431b0e6146SDuncan Sands 
11446a02c996SSimon Arlott 	mutex_init(&instance->poll_state_serialize);
11456a02c996SSimon Arlott 	instance->poll_state = CXPOLL_STOPPED;
11466a02c996SSimon Arlott 	instance->line_status = -1;
11476a02c996SSimon Arlott 	instance->adsl_status = -1;
11486a02c996SSimon Arlott 
11496a02c996SSimon Arlott 	mutex_init(&instance->adsl_state_serialize);
11506a02c996SSimon Arlott 
11511b0e6146SDuncan Sands 	instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL);
11521b0e6146SDuncan Sands 	if (!instance->rcv_buf) {
115377c9e125SGreg Kroah-Hartman 		usb_dbg(usbatm_instance, "cxacru_bind: no memory for rcv_buf\n");
11541b0e6146SDuncan Sands 		ret = -ENOMEM;
11551b0e6146SDuncan Sands 		goto fail;
11561b0e6146SDuncan Sands 	}
11571b0e6146SDuncan Sands 	instance->snd_buf = (u8 *) __get_free_page(GFP_KERNEL);
11581b0e6146SDuncan Sands 	if (!instance->snd_buf) {
115977c9e125SGreg Kroah-Hartman 		usb_dbg(usbatm_instance, "cxacru_bind: no memory for snd_buf\n");
11601b0e6146SDuncan Sands 		ret = -ENOMEM;
11611b0e6146SDuncan Sands 		goto fail;
11621b0e6146SDuncan Sands 	}
11631b0e6146SDuncan Sands 	instance->rcv_urb = usb_alloc_urb(0, GFP_KERNEL);
11641b0e6146SDuncan Sands 	if (!instance->rcv_urb) {
11651b0e6146SDuncan Sands 		ret = -ENOMEM;
11661b0e6146SDuncan Sands 		goto fail;
11671b0e6146SDuncan Sands 	}
11681b0e6146SDuncan Sands 	instance->snd_urb = usb_alloc_urb(0, GFP_KERNEL);
11691b0e6146SDuncan Sands 	if (!instance->snd_urb) {
11701b0e6146SDuncan Sands 		ret = -ENOMEM;
11711b0e6146SDuncan Sands 		goto fail;
11721b0e6146SDuncan Sands 	}
11731b0e6146SDuncan Sands 
1174902ffc3cSSimon Arlott 	if (!cmd_ep) {
117577c9e125SGreg Kroah-Hartman 		usb_dbg(usbatm_instance, "cxacru_bind: no command endpoint\n");
1176902ffc3cSSimon Arlott 		ret = -ENODEV;
1177902ffc3cSSimon Arlott 		goto fail;
1178902ffc3cSSimon Arlott 	}
1179902ffc3cSSimon Arlott 
1180902ffc3cSSimon Arlott 	if ((cmd_ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
1181902ffc3cSSimon Arlott 			== USB_ENDPOINT_XFER_INT) {
11821b0e6146SDuncan Sands 		usb_fill_int_urb(instance->rcv_urb,
11831b0e6146SDuncan Sands 			usb_dev, usb_rcvintpipe(usb_dev, CXACRU_EP_CMD),
11841b0e6146SDuncan Sands 			instance->rcv_buf, PAGE_SIZE,
11851b0e6146SDuncan Sands 			cxacru_blocking_completion, &instance->rcv_done, 1);
11861b0e6146SDuncan Sands 
11871b0e6146SDuncan Sands 		usb_fill_int_urb(instance->snd_urb,
11881b0e6146SDuncan Sands 			usb_dev, usb_sndintpipe(usb_dev, CXACRU_EP_CMD),
11891b0e6146SDuncan Sands 			instance->snd_buf, PAGE_SIZE,
11901b0e6146SDuncan Sands 			cxacru_blocking_completion, &instance->snd_done, 4);
1191902ffc3cSSimon Arlott 	} else {
1192902ffc3cSSimon Arlott 		usb_fill_bulk_urb(instance->rcv_urb,
1193902ffc3cSSimon Arlott 			usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_CMD),
1194902ffc3cSSimon Arlott 			instance->rcv_buf, PAGE_SIZE,
1195902ffc3cSSimon Arlott 			cxacru_blocking_completion, &instance->rcv_done);
1196902ffc3cSSimon Arlott 
1197902ffc3cSSimon Arlott 		usb_fill_bulk_urb(instance->snd_urb,
1198902ffc3cSSimon Arlott 			usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD),
1199902ffc3cSSimon Arlott 			instance->snd_buf, PAGE_SIZE,
1200902ffc3cSSimon Arlott 			cxacru_blocking_completion, &instance->snd_done);
1201902ffc3cSSimon Arlott 	}
12021b0e6146SDuncan Sands 
1203ab3c81ffSArjan van de Ven 	mutex_init(&instance->cm_serialize);
12041b0e6146SDuncan Sands 
1205c4028958SDavid Howells 	INIT_DELAYED_WORK(&instance->poll_work, cxacru_poll_status);
12061b0e6146SDuncan Sands 
12071b0e6146SDuncan Sands 	usbatm_instance->driver_data = instance;
12081b0e6146SDuncan Sands 
120935644b0cSDuncan Sands 	usbatm_instance->flags = (cxacru_card_status(instance) ? 0 : UDSL_SKIP_HEAVY_INIT);
12101b0e6146SDuncan Sands 
12111b0e6146SDuncan Sands 	return 0;
12121b0e6146SDuncan Sands 
12131b0e6146SDuncan Sands  fail:
12141b0e6146SDuncan Sands 	free_page((unsigned long) instance->snd_buf);
12151b0e6146SDuncan Sands 	free_page((unsigned long) instance->rcv_buf);
12161b0e6146SDuncan Sands 	usb_free_urb(instance->snd_urb);
12171b0e6146SDuncan Sands 	usb_free_urb(instance->rcv_urb);
12181b0e6146SDuncan Sands 	kfree(instance);
12191b0e6146SDuncan Sands 
12201b0e6146SDuncan Sands 	return ret;
12211b0e6146SDuncan Sands }
12221b0e6146SDuncan Sands 
cxacru_unbind(struct usbatm_data * usbatm_instance,struct usb_interface * intf)12231b0e6146SDuncan Sands static void cxacru_unbind(struct usbatm_data *usbatm_instance,
12241b0e6146SDuncan Sands 		struct usb_interface *intf)
12251b0e6146SDuncan Sands {
12261b0e6146SDuncan Sands 	struct cxacru_data *instance = usbatm_instance->driver_data;
12276a02c996SSimon Arlott 	int is_polling = 1;
12281b0e6146SDuncan Sands 
122977c9e125SGreg Kroah-Hartman 	usb_dbg(usbatm_instance, "cxacru_unbind entered\n");
12301b0e6146SDuncan Sands 
12311b0e6146SDuncan Sands 	if (!instance) {
123277c9e125SGreg Kroah-Hartman 		usb_dbg(usbatm_instance, "cxacru_unbind: NULL instance!\n");
12331b0e6146SDuncan Sands 		return;
12341b0e6146SDuncan Sands 	}
12351b0e6146SDuncan Sands 
12366a02c996SSimon Arlott 	mutex_lock(&instance->poll_state_serialize);
12376a02c996SSimon Arlott 	BUG_ON(instance->poll_state == CXPOLL_SHUTDOWN);
12386a02c996SSimon Arlott 
12396a02c996SSimon Arlott 	/* ensure that status polling continues unless
12406a02c996SSimon Arlott 	 * it has already stopped */
12416a02c996SSimon Arlott 	if (instance->poll_state == CXPOLL_STOPPED)
12426a02c996SSimon Arlott 		is_polling = 0;
12436a02c996SSimon Arlott 
12446a02c996SSimon Arlott 	/* stop polling from being stopped or started */
12456a02c996SSimon Arlott 	instance->poll_state = CXPOLL_SHUTDOWN;
12466a02c996SSimon Arlott 	mutex_unlock(&instance->poll_state_serialize);
12476a02c996SSimon Arlott 
12486a02c996SSimon Arlott 	if (is_polling)
1249afe2c511STejun Heo 		cancel_delayed_work_sync(&instance->poll_work);
12501b0e6146SDuncan Sands 
12511b0e6146SDuncan Sands 	usb_kill_urb(instance->snd_urb);
12521b0e6146SDuncan Sands 	usb_kill_urb(instance->rcv_urb);
12531b0e6146SDuncan Sands 	usb_free_urb(instance->snd_urb);
12541b0e6146SDuncan Sands 	usb_free_urb(instance->rcv_urb);
12551b0e6146SDuncan Sands 
12561b0e6146SDuncan Sands 	free_page((unsigned long) instance->snd_buf);
12571b0e6146SDuncan Sands 	free_page((unsigned long) instance->rcv_buf);
1258fa70fe44SSimon Arlott 
12591b0e6146SDuncan Sands 	kfree(instance);
12601b0e6146SDuncan Sands 
12611b0e6146SDuncan Sands 	usbatm_instance->driver_data = NULL;
12621b0e6146SDuncan Sands }
12631b0e6146SDuncan Sands 
12641b0e6146SDuncan Sands static const struct cxacru_modem_type cxacru_cafe = {
12651b0e6146SDuncan Sands 	.pll_f_clk = 0x02d874df,
12661b0e6146SDuncan Sands 	.pll_b_clk = 0x0196a51a,
12671b0e6146SDuncan Sands 	.boot_rom_patch = 1,
12681b0e6146SDuncan Sands };
12691b0e6146SDuncan Sands 
12701b0e6146SDuncan Sands static const struct cxacru_modem_type cxacru_cb00 = {
12711b0e6146SDuncan Sands 	.pll_f_clk = 0x5,
12721b0e6146SDuncan Sands 	.pll_b_clk = 0x3,
12731b0e6146SDuncan Sands 	.boot_rom_patch = 0,
12741b0e6146SDuncan Sands };
12751b0e6146SDuncan Sands 
12761b0e6146SDuncan Sands static const struct usb_device_id cxacru_usb_ids[] = {
12771b0e6146SDuncan Sands 	{ /* V = Conexant			P = ADSL modem (Euphrates project)	*/
12781b0e6146SDuncan Sands 		USB_DEVICE(0x0572, 0xcafe),	.driver_info = (unsigned long) &cxacru_cafe
12791b0e6146SDuncan Sands 	},
12801b0e6146SDuncan Sands 	{ /* V = Conexant			P = ADSL modem (Hasbani project)	*/
12811b0e6146SDuncan Sands 		USB_DEVICE(0x0572, 0xcb00),	.driver_info = (unsigned long) &cxacru_cb00
12821b0e6146SDuncan Sands 	},
12831b0e6146SDuncan Sands 	{ /* V = Conexant			P = ADSL modem				*/
12841b0e6146SDuncan Sands 		USB_DEVICE(0x0572, 0xcb01),	.driver_info = (unsigned long) &cxacru_cb00
12851b0e6146SDuncan Sands 	},
12860ec3c7e8SDuncan Sands 	{ /* V = Conexant			P = ADSL modem (Well PTI-800) */
12870ec3c7e8SDuncan Sands 		USB_DEVICE(0x0572, 0xcb02),	.driver_info = (unsigned long) &cxacru_cb00
12880ec3c7e8SDuncan Sands 	},
12891b0e6146SDuncan Sands 	{ /* V = Conexant			P = ADSL modem				*/
12901b0e6146SDuncan Sands 		USB_DEVICE(0x0572, 0xcb06),	.driver_info = (unsigned long) &cxacru_cb00
12911b0e6146SDuncan Sands 	},
129244960af1SDuncan Sands 	{ /* V = Conexant			P = ADSL modem (ZTE ZXDSL 852)		*/
129344960af1SDuncan Sands 		USB_DEVICE(0x0572, 0xcb07),	.driver_info = (unsigned long) &cxacru_cb00
129444960af1SDuncan Sands 	},
12951b0e6146SDuncan Sands 	{ /* V = Olitec				P = ADSL modem version 2		*/
12961b0e6146SDuncan Sands 		USB_DEVICE(0x08e3, 0x0100),	.driver_info = (unsigned long) &cxacru_cafe
12971b0e6146SDuncan Sands 	},
12981b0e6146SDuncan Sands 	{ /* V = Olitec				P = ADSL modem version 3		*/
12991b0e6146SDuncan Sands 		USB_DEVICE(0x08e3, 0x0102),	.driver_info = (unsigned long) &cxacru_cb00
13001b0e6146SDuncan Sands 	},
13011b0e6146SDuncan Sands 	{ /* V = Trust/Amigo Technology Co.	P = AMX-CA86U				*/
13021b0e6146SDuncan Sands 		USB_DEVICE(0x0eb0, 0x3457),	.driver_info = (unsigned long) &cxacru_cafe
13031b0e6146SDuncan Sands 	},
13041b0e6146SDuncan Sands 	{ /* V = Zoom				P = 5510				*/
13051b0e6146SDuncan Sands 		USB_DEVICE(0x1803, 0x5510),	.driver_info = (unsigned long) &cxacru_cb00
13061b0e6146SDuncan Sands 	},
13071b0e6146SDuncan Sands 	{ /* V = Draytek			P = Vigor 318				*/
13081b0e6146SDuncan Sands 		USB_DEVICE(0x0675, 0x0200),	.driver_info = (unsigned long) &cxacru_cb00
13091b0e6146SDuncan Sands 	},
13101b0e6146SDuncan Sands 	{ /* V = Zyxel				P = 630-C1 aka OMNI ADSL USB (Annex A)	*/
13111b0e6146SDuncan Sands 		USB_DEVICE(0x0586, 0x330a),	.driver_info = (unsigned long) &cxacru_cb00
13121b0e6146SDuncan Sands 	},
13131b0e6146SDuncan Sands 	{ /* V = Zyxel				P = 630-C3 aka OMNI ADSL USB (Annex B)	*/
13141b0e6146SDuncan Sands 		USB_DEVICE(0x0586, 0x330b),	.driver_info = (unsigned long) &cxacru_cb00
13151b0e6146SDuncan Sands 	},
13161b0e6146SDuncan Sands 	{ /* V = Aethra				P = Starmodem UM1020			*/
13171b0e6146SDuncan Sands 		USB_DEVICE(0x0659, 0x0020),	.driver_info = (unsigned long) &cxacru_cb00
13181b0e6146SDuncan Sands 	},
13191b0e6146SDuncan Sands 	{ /* V = Aztech Systems			P = ? AKA Pirelli AUA-010		*/
13201b0e6146SDuncan Sands 		USB_DEVICE(0x0509, 0x0812),	.driver_info = (unsigned long) &cxacru_cb00
13211b0e6146SDuncan Sands 	},
13221b0e6146SDuncan Sands 	{ /* V = Netopia			P = Cayman 3341(Annex A)/3351(Annex B)	*/
13231b0e6146SDuncan Sands 		USB_DEVICE(0x100d, 0xcb01),	.driver_info = (unsigned long) &cxacru_cb00
13241b0e6146SDuncan Sands 	},
13251b0e6146SDuncan Sands 	{ /* V = Netopia			P = Cayman 3342(Annex A)/3352(Annex B)	*/
13261b0e6146SDuncan Sands 		USB_DEVICE(0x100d, 0x3342),	.driver_info = (unsigned long) &cxacru_cb00
13271b0e6146SDuncan Sands 	},
13281b0e6146SDuncan Sands 	{}
13291b0e6146SDuncan Sands };
13301b0e6146SDuncan Sands 
13311b0e6146SDuncan Sands MODULE_DEVICE_TABLE(usb, cxacru_usb_ids);
13321b0e6146SDuncan Sands 
13331b0e6146SDuncan Sands static struct usbatm_driver cxacru_driver = {
13341b0e6146SDuncan Sands 	.driver_name	= cxacru_driver_name,
13351b0e6146SDuncan Sands 	.bind		= cxacru_bind,
13361b0e6146SDuncan Sands 	.heavy_init	= cxacru_heavy_init,
13371b0e6146SDuncan Sands 	.unbind		= cxacru_unbind,
13381b0e6146SDuncan Sands 	.atm_start	= cxacru_atm_start,
133980aae7a1SDuncan Sands 	.bulk_in	= CXACRU_EP_DATA,
134080aae7a1SDuncan Sands 	.bulk_out	= CXACRU_EP_DATA,
13411b0e6146SDuncan Sands 	.rx_padding	= 3,
13421b0e6146SDuncan Sands 	.tx_padding	= 11,
13431b0e6146SDuncan Sands };
13441b0e6146SDuncan Sands 
cxacru_usb_probe(struct usb_interface * intf,const struct usb_device_id * id)134592e32eaeSOndrej Zary static int cxacru_usb_probe(struct usb_interface *intf,
134692e32eaeSOndrej Zary 		const struct usb_device_id *id)
13471b0e6146SDuncan Sands {
134892e32eaeSOndrej Zary 	struct usb_device *usb_dev = interface_to_usbdev(intf);
134992e32eaeSOndrej Zary 	char buf[15];
135092e32eaeSOndrej Zary 
135192e32eaeSOndrej Zary 	/* Avoid ADSL routers (cx82310_eth).
135292e32eaeSOndrej Zary 	 * Abort if bDeviceClass is 0xff and iProduct is "USB NET CARD".
135392e32eaeSOndrej Zary 	 */
135492e32eaeSOndrej Zary 	if (usb_dev->descriptor.bDeviceClass == USB_CLASS_VENDOR_SPEC
135592e32eaeSOndrej Zary 			&& usb_string(usb_dev, usb_dev->descriptor.iProduct,
135692e32eaeSOndrej Zary 				buf, sizeof(buf)) > 0) {
135792e32eaeSOndrej Zary 		if (!strcmp(buf, "USB NET CARD")) {
135892e32eaeSOndrej Zary 			dev_info(&intf->dev, "ignoring cx82310_eth device\n");
135992e32eaeSOndrej Zary 			return -ENODEV;
136092e32eaeSOndrej Zary 		}
136192e32eaeSOndrej Zary 	}
136292e32eaeSOndrej Zary 
13631b0e6146SDuncan Sands 	return usbatm_usb_probe(intf, id, &cxacru_driver);
13641b0e6146SDuncan Sands }
13651b0e6146SDuncan Sands 
13661b0e6146SDuncan Sands static struct usb_driver cxacru_usb_driver = {
13671b0e6146SDuncan Sands 	.name		= cxacru_driver_name,
13681b0e6146SDuncan Sands 	.probe		= cxacru_usb_probe,
13691b0e6146SDuncan Sands 	.disconnect	= usbatm_usb_disconnect,
1370e605c309SGreg Kroah-Hartman 	.id_table	= cxacru_usb_ids,
1371e605c309SGreg Kroah-Hartman 	.dev_groups	= cxacru_groups,
13721b0e6146SDuncan Sands };
13731b0e6146SDuncan Sands 
137465db4305SGreg Kroah-Hartman module_usb_driver(cxacru_usb_driver);
13751b0e6146SDuncan Sands 
13761b0e6146SDuncan Sands MODULE_AUTHOR(DRIVER_AUTHOR);
13771b0e6146SDuncan Sands MODULE_DESCRIPTION(DRIVER_DESC);
13781b0e6146SDuncan Sands MODULE_LICENSE("GPL");
1379