11b0e6146SDuncan Sands /****************************************************************************** 21b0e6146SDuncan Sands * cxacru.c - driver for USB ADSL modems based on 31b0e6146SDuncan Sands * Conexant AccessRunner chipset 41b0e6146SDuncan Sands * 51b0e6146SDuncan Sands * Copyright (C) 2004 David Woodhouse, Duncan Sands, Roman Kagan 61b0e6146SDuncan Sands * Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru) 76a02c996SSimon Arlott * Copyright (C) 2007 Simon Arlott 81b0e6146SDuncan Sands * 91b0e6146SDuncan Sands * This program is free software; you can redistribute it and/or modify it 101b0e6146SDuncan Sands * under the terms of the GNU General Public License as published by the Free 111b0e6146SDuncan Sands * Software Foundation; either version 2 of the License, or (at your option) 121b0e6146SDuncan Sands * any later version. 131b0e6146SDuncan Sands * 141b0e6146SDuncan Sands * This program is distributed in the hope that it will be useful, but WITHOUT 151b0e6146SDuncan Sands * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 161b0e6146SDuncan Sands * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 171b0e6146SDuncan Sands * more details. 181b0e6146SDuncan Sands * 191b0e6146SDuncan Sands * You should have received a copy of the GNU General Public License along with 201b0e6146SDuncan Sands * this program; if not, write to the Free Software Foundation, Inc., 59 211b0e6146SDuncan Sands * Temple Place - Suite 330, Boston, MA 02111-1307, USA. 221b0e6146SDuncan Sands * 231b0e6146SDuncan Sands ******************************************************************************/ 241b0e6146SDuncan Sands 251b0e6146SDuncan Sands /* 261b0e6146SDuncan Sands * Credit is due for Josep Comas, who created the original patch to speedtch.c 271b0e6146SDuncan Sands * to support the different padding used by the AccessRunner (now generalized 281b0e6146SDuncan Sands * into usbatm), and the userspace firmware loading utility. 291b0e6146SDuncan Sands */ 301b0e6146SDuncan Sands 311b0e6146SDuncan Sands #include <linux/module.h> 321b0e6146SDuncan Sands #include <linux/moduleparam.h> 331b0e6146SDuncan Sands #include <linux/kernel.h> 341b0e6146SDuncan Sands #include <linux/timer.h> 351b0e6146SDuncan Sands #include <linux/errno.h> 361b0e6146SDuncan Sands #include <linux/slab.h> 371b0e6146SDuncan Sands #include <linux/init.h> 38fa70fe44SSimon Arlott #include <linux/device.h> 391b0e6146SDuncan Sands #include <linux/firmware.h> 40ab3c81ffSArjan van de Ven #include <linux/mutex.h> 411b0e6146SDuncan Sands 421b0e6146SDuncan Sands #include "usbatm.h" 431b0e6146SDuncan Sands 44fa70fe44SSimon Arlott #define DRIVER_AUTHOR "Roman Kagan, David Woodhouse, Duncan Sands, Simon Arlott" 45fa70fe44SSimon Arlott #define DRIVER_VERSION "0.3" 461b0e6146SDuncan Sands #define DRIVER_DESC "Conexant AccessRunner ADSL USB modem driver" 471b0e6146SDuncan Sands 481b0e6146SDuncan Sands static const char cxacru_driver_name[] = "cxacru"; 491b0e6146SDuncan Sands 501b0e6146SDuncan Sands #define CXACRU_EP_CMD 0x01 /* Bulk/interrupt in/out */ 511b0e6146SDuncan Sands #define CXACRU_EP_DATA 0x02 /* Bulk in/out */ 521b0e6146SDuncan Sands 531b0e6146SDuncan Sands #define CMD_PACKET_SIZE 64 /* Should be maxpacket(ep)? */ 541b0e6146SDuncan Sands 551b0e6146SDuncan Sands /* Addresses */ 561b0e6146SDuncan Sands #define PLLFCLK_ADDR 0x00350068 571b0e6146SDuncan Sands #define PLLBCLK_ADDR 0x0035006c 581b0e6146SDuncan Sands #define SDRAMEN_ADDR 0x00350010 591b0e6146SDuncan Sands #define FW_ADDR 0x00801000 601b0e6146SDuncan Sands #define BR_ADDR 0x00180600 611b0e6146SDuncan Sands #define SIG_ADDR 0x00180500 621b0e6146SDuncan Sands #define BR_STACK_ADDR 0x00187f10 631b0e6146SDuncan Sands 641b0e6146SDuncan Sands /* Values */ 651b0e6146SDuncan Sands #define SDRAM_ENA 0x1 661b0e6146SDuncan Sands 671b0e6146SDuncan Sands #define CMD_TIMEOUT 2000 /* msecs */ 68fa70fe44SSimon Arlott #define POLL_INTERVAL 1 /* secs */ 691b0e6146SDuncan Sands 701b0e6146SDuncan Sands /* commands for interaction with the modem through the control channel before 711b0e6146SDuncan Sands * firmware is loaded */ 721b0e6146SDuncan Sands enum cxacru_fw_request { 731b0e6146SDuncan Sands FW_CMD_ERR, 741b0e6146SDuncan Sands FW_GET_VER, 751b0e6146SDuncan Sands FW_READ_MEM, 761b0e6146SDuncan Sands FW_WRITE_MEM, 771b0e6146SDuncan Sands FW_RMW_MEM, 781b0e6146SDuncan Sands FW_CHECKSUM_MEM, 791b0e6146SDuncan Sands FW_GOTO_MEM, 801b0e6146SDuncan Sands }; 811b0e6146SDuncan Sands 821b0e6146SDuncan Sands /* commands for interaction with the modem through the control channel once 831b0e6146SDuncan Sands * firmware is loaded */ 841b0e6146SDuncan Sands enum cxacru_cm_request { 851b0e6146SDuncan Sands CM_REQUEST_UNDEFINED = 0x80, 861b0e6146SDuncan Sands CM_REQUEST_TEST, 871b0e6146SDuncan Sands CM_REQUEST_CHIP_GET_MAC_ADDRESS, 881b0e6146SDuncan Sands CM_REQUEST_CHIP_GET_DP_VERSIONS, 891b0e6146SDuncan Sands CM_REQUEST_CHIP_ADSL_LINE_START, 901b0e6146SDuncan Sands CM_REQUEST_CHIP_ADSL_LINE_STOP, 911b0e6146SDuncan Sands CM_REQUEST_CHIP_ADSL_LINE_GET_STATUS, 921b0e6146SDuncan Sands CM_REQUEST_CHIP_ADSL_LINE_GET_SPEED, 931b0e6146SDuncan Sands CM_REQUEST_CARD_INFO_GET, 941b0e6146SDuncan Sands CM_REQUEST_CARD_DATA_GET, 951b0e6146SDuncan Sands CM_REQUEST_CARD_DATA_SET, 961b0e6146SDuncan Sands CM_REQUEST_COMMAND_HW_IO, 971b0e6146SDuncan Sands CM_REQUEST_INTERFACE_HW_IO, 981b0e6146SDuncan Sands CM_REQUEST_CARD_SERIAL_DATA_PATH_GET, 991b0e6146SDuncan Sands CM_REQUEST_CARD_SERIAL_DATA_PATH_SET, 1001b0e6146SDuncan Sands CM_REQUEST_CARD_CONTROLLER_VERSION_GET, 1011b0e6146SDuncan Sands CM_REQUEST_CARD_GET_STATUS, 1021b0e6146SDuncan Sands CM_REQUEST_CARD_GET_MAC_ADDRESS, 1031b0e6146SDuncan Sands CM_REQUEST_CARD_GET_DATA_LINK_STATUS, 1041b0e6146SDuncan Sands CM_REQUEST_MAX, 1051b0e6146SDuncan Sands }; 1061b0e6146SDuncan Sands 1071b0e6146SDuncan Sands /* reply codes to the commands above */ 1081b0e6146SDuncan Sands enum cxacru_cm_status { 1091b0e6146SDuncan Sands CM_STATUS_UNDEFINED, 1101b0e6146SDuncan Sands CM_STATUS_SUCCESS, 1111b0e6146SDuncan Sands CM_STATUS_ERROR, 1121b0e6146SDuncan Sands CM_STATUS_UNSUPPORTED, 1131b0e6146SDuncan Sands CM_STATUS_UNIMPLEMENTED, 1141b0e6146SDuncan Sands CM_STATUS_PARAMETER_ERROR, 1151b0e6146SDuncan Sands CM_STATUS_DBG_LOOPBACK, 1161b0e6146SDuncan Sands CM_STATUS_MAX, 1171b0e6146SDuncan Sands }; 1181b0e6146SDuncan Sands 1191b0e6146SDuncan Sands /* indices into CARD_INFO_GET return array */ 1201b0e6146SDuncan Sands enum cxacru_info_idx { 1211b0e6146SDuncan Sands CXINF_DOWNSTREAM_RATE, 1221b0e6146SDuncan Sands CXINF_UPSTREAM_RATE, 1231b0e6146SDuncan Sands CXINF_LINK_STATUS, 1241b0e6146SDuncan Sands CXINF_LINE_STATUS, 1251b0e6146SDuncan Sands CXINF_MAC_ADDRESS_HIGH, 1261b0e6146SDuncan Sands CXINF_MAC_ADDRESS_LOW, 1271b0e6146SDuncan Sands CXINF_UPSTREAM_SNR_MARGIN, 1281b0e6146SDuncan Sands CXINF_DOWNSTREAM_SNR_MARGIN, 1291b0e6146SDuncan Sands CXINF_UPSTREAM_ATTENUATION, 1301b0e6146SDuncan Sands CXINF_DOWNSTREAM_ATTENUATION, 1311b0e6146SDuncan Sands CXINF_TRANSMITTER_POWER, 1321b0e6146SDuncan Sands CXINF_UPSTREAM_BITS_PER_FRAME, 1331b0e6146SDuncan Sands CXINF_DOWNSTREAM_BITS_PER_FRAME, 1341b0e6146SDuncan Sands CXINF_STARTUP_ATTEMPTS, 1351b0e6146SDuncan Sands CXINF_UPSTREAM_CRC_ERRORS, 1361b0e6146SDuncan Sands CXINF_DOWNSTREAM_CRC_ERRORS, 1371b0e6146SDuncan Sands CXINF_UPSTREAM_FEC_ERRORS, 1381b0e6146SDuncan Sands CXINF_DOWNSTREAM_FEC_ERRORS, 1391b0e6146SDuncan Sands CXINF_UPSTREAM_HEC_ERRORS, 1401b0e6146SDuncan Sands CXINF_DOWNSTREAM_HEC_ERRORS, 1411b0e6146SDuncan Sands CXINF_LINE_STARTABLE, 1421b0e6146SDuncan Sands CXINF_MODULATION, 1431b0e6146SDuncan Sands CXINF_ADSL_HEADEND, 1441b0e6146SDuncan Sands CXINF_ADSL_HEADEND_ENVIRONMENT, 1451b0e6146SDuncan Sands CXINF_CONTROLLER_VERSION, 1461b0e6146SDuncan Sands /* dunno what the missing two mean */ 1471b0e6146SDuncan Sands CXINF_MAX = 0x1c, 1481b0e6146SDuncan Sands }; 1491b0e6146SDuncan Sands 1506a02c996SSimon Arlott enum cxacru_poll_state { 1516a02c996SSimon Arlott CXPOLL_STOPPING, 1526a02c996SSimon Arlott CXPOLL_STOPPED, 1536a02c996SSimon Arlott CXPOLL_POLLING, 1546a02c996SSimon Arlott CXPOLL_SHUTDOWN 1556a02c996SSimon Arlott }; 1566a02c996SSimon Arlott 1571b0e6146SDuncan Sands struct cxacru_modem_type { 1581b0e6146SDuncan Sands u32 pll_f_clk; 1591b0e6146SDuncan Sands u32 pll_b_clk; 1601b0e6146SDuncan Sands int boot_rom_patch; 1611b0e6146SDuncan Sands }; 1621b0e6146SDuncan Sands 1631b0e6146SDuncan Sands struct cxacru_data { 1641b0e6146SDuncan Sands struct usbatm_data *usbatm; 1651b0e6146SDuncan Sands 1661b0e6146SDuncan Sands const struct cxacru_modem_type *modem_type; 1671b0e6146SDuncan Sands 1681b0e6146SDuncan Sands int line_status; 1696a02c996SSimon Arlott struct mutex adsl_state_serialize; 1706a02c996SSimon Arlott int adsl_status; 171c4028958SDavid Howells struct delayed_work poll_work; 172fa70fe44SSimon Arlott u32 card_info[CXINF_MAX]; 1736a02c996SSimon Arlott struct mutex poll_state_serialize; 17487e71b47SSimon Arlott enum cxacru_poll_state poll_state; 1751b0e6146SDuncan Sands 1761b0e6146SDuncan Sands /* contol handles */ 177ab3c81ffSArjan van de Ven struct mutex cm_serialize; 1781b0e6146SDuncan Sands u8 *rcv_buf; 1791b0e6146SDuncan Sands u8 *snd_buf; 1801b0e6146SDuncan Sands struct urb *rcv_urb; 1811b0e6146SDuncan Sands struct urb *snd_urb; 1821b0e6146SDuncan Sands struct completion rcv_done; 1831b0e6146SDuncan Sands struct completion snd_done; 1841b0e6146SDuncan Sands }; 1851b0e6146SDuncan Sands 1866a02c996SSimon Arlott static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm, 1876a02c996SSimon Arlott u8 *wdata, int wsize, u8 *rdata, int rsize); 1886a02c996SSimon Arlott static void cxacru_poll_status(struct work_struct *work); 1896a02c996SSimon Arlott 190fa70fe44SSimon Arlott /* Card info exported through sysfs */ 191fa70fe44SSimon Arlott #define CXACRU__ATTR_INIT(_name) \ 192fa70fe44SSimon Arlott static DEVICE_ATTR(_name, S_IRUGO, cxacru_sysfs_show_##_name, NULL) 193fa70fe44SSimon Arlott 1946a02c996SSimon Arlott #define CXACRU_CMD_INIT(_name) \ 1956a02c996SSimon Arlott static DEVICE_ATTR(_name, S_IWUSR | S_IRUGO, \ 1966a02c996SSimon Arlott cxacru_sysfs_show_##_name, cxacru_sysfs_store_##_name) 1976a02c996SSimon Arlott 198fa70fe44SSimon Arlott #define CXACRU_ATTR_INIT(_value, _type, _name) \ 199fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_show_##_name(struct device *dev, \ 200fa70fe44SSimon Arlott struct device_attribute *attr, char *buf) \ 201fa70fe44SSimon Arlott { \ 202fa70fe44SSimon Arlott struct usb_interface *intf = to_usb_interface(dev); \ 203fa70fe44SSimon Arlott struct usbatm_data *usbatm_instance = usb_get_intfdata(intf); \ 204fa70fe44SSimon Arlott struct cxacru_data *instance = usbatm_instance->driver_data; \ 205fa70fe44SSimon Arlott return cxacru_sysfs_showattr_##_type(instance->card_info[_value], buf); \ 206fa70fe44SSimon Arlott } \ 207fa70fe44SSimon Arlott CXACRU__ATTR_INIT(_name) 208fa70fe44SSimon Arlott 209fa70fe44SSimon Arlott #define CXACRU_ATTR_CREATE(_v, _t, _name) CXACRU_DEVICE_CREATE_FILE(_name) 2106a02c996SSimon Arlott #define CXACRU_CMD_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) 211fa70fe44SSimon Arlott #define CXACRU__ATTR_CREATE(_name) CXACRU_DEVICE_CREATE_FILE(_name) 212fa70fe44SSimon Arlott 213fa70fe44SSimon Arlott #define CXACRU_ATTR_REMOVE(_v, _t, _name) CXACRU_DEVICE_REMOVE_FILE(_name) 2146a02c996SSimon Arlott #define CXACRU_CMD_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) 215fa70fe44SSimon Arlott #define CXACRU__ATTR_REMOVE(_name) CXACRU_DEVICE_REMOVE_FILE(_name) 216fa70fe44SSimon Arlott 217fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_u32(u32 value, char *buf) 218fa70fe44SSimon Arlott { 219fa70fe44SSimon Arlott return snprintf(buf, PAGE_SIZE, "%u\n", value); 220fa70fe44SSimon Arlott } 221fa70fe44SSimon Arlott 222fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_s8(s8 value, char *buf) 223fa70fe44SSimon Arlott { 224fa70fe44SSimon Arlott return snprintf(buf, PAGE_SIZE, "%d\n", value); 225fa70fe44SSimon Arlott } 226fa70fe44SSimon Arlott 227fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_dB(s16 value, char *buf) 228fa70fe44SSimon Arlott { 229fa70fe44SSimon Arlott return snprintf(buf, PAGE_SIZE, "%d.%02u\n", 23087e71b47SSimon Arlott value / 100, abs(value) % 100); 231fa70fe44SSimon Arlott } 232fa70fe44SSimon Arlott 233fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_bool(u32 value, char *buf) 234fa70fe44SSimon Arlott { 23587e71b47SSimon Arlott static char *str[] = { "no", "yes" }; 23687e71b47SSimon Arlott if (unlikely(value >= ARRAY_SIZE(str))) 23787e71b47SSimon Arlott return snprintf(buf, PAGE_SIZE, "%u\n", value); 23887e71b47SSimon Arlott return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); 239fa70fe44SSimon Arlott } 240fa70fe44SSimon Arlott 241fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_LINK(u32 value, char *buf) 242fa70fe44SSimon Arlott { 24387e71b47SSimon Arlott static char *str[] = { NULL, "not connected", "connected", "lost" }; 24487e71b47SSimon Arlott if (unlikely(value >= ARRAY_SIZE(str) || str[value] == NULL)) 24587e71b47SSimon Arlott return snprintf(buf, PAGE_SIZE, "%u\n", value); 24687e71b47SSimon Arlott return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); 247fa70fe44SSimon Arlott } 248fa70fe44SSimon Arlott 249fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_LINE(u32 value, char *buf) 250fa70fe44SSimon Arlott { 25187e71b47SSimon Arlott static char *str[] = { "down", "attempting to activate", 25287e71b47SSimon Arlott "training", "channel analysis", "exchange", "up", 25387e71b47SSimon Arlott "waiting", "initialising" 25487e71b47SSimon Arlott }; 25587e71b47SSimon Arlott if (unlikely(value >= ARRAY_SIZE(str))) 25687e71b47SSimon Arlott return snprintf(buf, PAGE_SIZE, "%u\n", value); 25787e71b47SSimon Arlott return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); 258fa70fe44SSimon Arlott } 259fa70fe44SSimon Arlott 260fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_showattr_MODU(u32 value, char *buf) 261fa70fe44SSimon Arlott { 26287e71b47SSimon Arlott static char *str[] = { 26387e71b47SSimon Arlott NULL, 26487e71b47SSimon Arlott "ANSI T1.413", 26587e71b47SSimon Arlott "ITU-T G.992.1 (G.DMT)", 26687e71b47SSimon Arlott "ITU-T G.992.2 (G.LITE)" 26787e71b47SSimon Arlott }; 26887e71b47SSimon Arlott if (unlikely(value >= ARRAY_SIZE(str) || str[value] == NULL)) 26987e71b47SSimon Arlott return snprintf(buf, PAGE_SIZE, "%u\n", value); 27087e71b47SSimon Arlott return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); 271fa70fe44SSimon Arlott } 272fa70fe44SSimon Arlott 273fa70fe44SSimon Arlott /* 274fa70fe44SSimon Arlott * This could use MAC_ADDRESS_HIGH and MAC_ADDRESS_LOW, but since 275fa70fe44SSimon Arlott * this data is already in atm_dev there's no point. 276fa70fe44SSimon Arlott * 277fa70fe44SSimon Arlott * MAC_ADDRESS_HIGH = 0x????5544 278fa70fe44SSimon Arlott * MAC_ADDRESS_LOW = 0x33221100 279fa70fe44SSimon Arlott * Where 00-55 are bytes 0-5 of the MAC. 280fa70fe44SSimon Arlott */ 281fa70fe44SSimon Arlott static ssize_t cxacru_sysfs_show_mac_address(struct device *dev, 282fa70fe44SSimon Arlott struct device_attribute *attr, char *buf) 283fa70fe44SSimon Arlott { 284fa70fe44SSimon Arlott struct usb_interface *intf = to_usb_interface(dev); 285fa70fe44SSimon Arlott struct usbatm_data *usbatm_instance = usb_get_intfdata(intf); 286fa70fe44SSimon Arlott struct atm_dev *atm_dev = usbatm_instance->atm_dev; 287fa70fe44SSimon Arlott 288fa70fe44SSimon Arlott return snprintf(buf, PAGE_SIZE, "%02x:%02x:%02x:%02x:%02x:%02x\n", 289fa70fe44SSimon Arlott atm_dev->esi[0], atm_dev->esi[1], atm_dev->esi[2], 290fa70fe44SSimon Arlott atm_dev->esi[3], atm_dev->esi[4], atm_dev->esi[5]); 291fa70fe44SSimon Arlott } 292fa70fe44SSimon Arlott 2936a02c996SSimon Arlott static ssize_t cxacru_sysfs_show_adsl_state(struct device *dev, 2946a02c996SSimon Arlott struct device_attribute *attr, char *buf) 2956a02c996SSimon Arlott { 2966a02c996SSimon Arlott struct usb_interface *intf = to_usb_interface(dev); 2976a02c996SSimon Arlott struct usbatm_data *usbatm_instance = usb_get_intfdata(intf); 2986a02c996SSimon Arlott struct cxacru_data *instance = usbatm_instance->driver_data; 2996a02c996SSimon Arlott u32 value = instance->card_info[CXINF_LINE_STARTABLE]; 3006a02c996SSimon Arlott 30187e71b47SSimon Arlott static char *str[] = { "running", "stopped" }; 30287e71b47SSimon Arlott if (unlikely(value >= ARRAY_SIZE(str))) 30387e71b47SSimon Arlott return snprintf(buf, PAGE_SIZE, "%u\n", value); 30487e71b47SSimon Arlott return snprintf(buf, PAGE_SIZE, "%s\n", str[value]); 3056a02c996SSimon Arlott } 3066a02c996SSimon Arlott 3076a02c996SSimon Arlott static ssize_t cxacru_sysfs_store_adsl_state(struct device *dev, 3086a02c996SSimon Arlott struct device_attribute *attr, const char *buf, size_t count) 3096a02c996SSimon Arlott { 3106a02c996SSimon Arlott struct usb_interface *intf = to_usb_interface(dev); 3116a02c996SSimon Arlott struct usbatm_data *usbatm_instance = usb_get_intfdata(intf); 3126a02c996SSimon Arlott struct cxacru_data *instance = usbatm_instance->driver_data; 3136a02c996SSimon Arlott int ret; 3146a02c996SSimon Arlott int poll = -1; 3156a02c996SSimon Arlott char str_cmd[8]; 3166a02c996SSimon Arlott int len = strlen(buf); 3176a02c996SSimon Arlott 3186a02c996SSimon Arlott if (!capable(CAP_NET_ADMIN)) 3196a02c996SSimon Arlott return -EACCES; 3206a02c996SSimon Arlott 3216a02c996SSimon Arlott ret = sscanf(buf, "%7s", str_cmd); 3226a02c996SSimon Arlott if (ret != 1) 3236a02c996SSimon Arlott return -EINVAL; 3246a02c996SSimon Arlott ret = 0; 3256a02c996SSimon Arlott 3266a02c996SSimon Arlott if (mutex_lock_interruptible(&instance->adsl_state_serialize)) 3276a02c996SSimon Arlott return -ERESTARTSYS; 3286a02c996SSimon Arlott 3296a02c996SSimon Arlott if (!strcmp(str_cmd, "stop") || !strcmp(str_cmd, "restart")) { 3306a02c996SSimon Arlott ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_STOP, NULL, 0, NULL, 0); 3316a02c996SSimon Arlott if (ret < 0) { 3326a02c996SSimon Arlott atm_err(usbatm_instance, "change adsl state:" 3336a02c996SSimon Arlott " CHIP_ADSL_LINE_STOP returned %d\n", ret); 3346a02c996SSimon Arlott 3356a02c996SSimon Arlott ret = -EIO; 3366a02c996SSimon Arlott } else { 3376a02c996SSimon Arlott ret = len; 3386a02c996SSimon Arlott poll = CXPOLL_STOPPED; 3396a02c996SSimon Arlott } 3406a02c996SSimon Arlott } 3416a02c996SSimon Arlott 3426a02c996SSimon Arlott /* Line status is only updated every second 3436a02c996SSimon Arlott * and the device appears to only react to 3446a02c996SSimon Arlott * START/STOP every second too. Wait 1.5s to 3456a02c996SSimon Arlott * be sure that restart will have an effect. */ 3466a02c996SSimon Arlott if (!strcmp(str_cmd, "restart")) 3476a02c996SSimon Arlott msleep(1500); 3486a02c996SSimon Arlott 3496a02c996SSimon Arlott if (!strcmp(str_cmd, "start") || !strcmp(str_cmd, "restart")) { 3506a02c996SSimon Arlott ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0); 3516a02c996SSimon Arlott if (ret < 0) { 3526a02c996SSimon Arlott atm_err(usbatm_instance, "change adsl state:" 3536a02c996SSimon Arlott " CHIP_ADSL_LINE_START returned %d\n", ret); 3546a02c996SSimon Arlott 3556a02c996SSimon Arlott ret = -EIO; 3566a02c996SSimon Arlott } else { 3576a02c996SSimon Arlott ret = len; 3586a02c996SSimon Arlott poll = CXPOLL_POLLING; 3596a02c996SSimon Arlott } 3606a02c996SSimon Arlott } 3616a02c996SSimon Arlott 3626a02c996SSimon Arlott if (!strcmp(str_cmd, "poll")) { 3636a02c996SSimon Arlott ret = len; 3646a02c996SSimon Arlott poll = CXPOLL_POLLING; 3656a02c996SSimon Arlott } 3666a02c996SSimon Arlott 3676a02c996SSimon Arlott if (ret == 0) { 3686a02c996SSimon Arlott ret = -EINVAL; 3696a02c996SSimon Arlott poll = -1; 3706a02c996SSimon Arlott } 3716a02c996SSimon Arlott 3726a02c996SSimon Arlott if (poll == CXPOLL_POLLING) { 3736a02c996SSimon Arlott mutex_lock(&instance->poll_state_serialize); 3746a02c996SSimon Arlott switch (instance->poll_state) { 3756a02c996SSimon Arlott case CXPOLL_STOPPED: 3766a02c996SSimon Arlott /* start polling */ 3776a02c996SSimon Arlott instance->poll_state = CXPOLL_POLLING; 3786a02c996SSimon Arlott break; 3796a02c996SSimon Arlott 3806a02c996SSimon Arlott case CXPOLL_STOPPING: 3816a02c996SSimon Arlott /* abort stop request */ 3826a02c996SSimon Arlott instance->poll_state = CXPOLL_POLLING; 3836a02c996SSimon Arlott case CXPOLL_POLLING: 3846a02c996SSimon Arlott case CXPOLL_SHUTDOWN: 3856a02c996SSimon Arlott /* don't start polling */ 3866a02c996SSimon Arlott poll = -1; 3876a02c996SSimon Arlott } 3886a02c996SSimon Arlott mutex_unlock(&instance->poll_state_serialize); 3896a02c996SSimon Arlott } else if (poll == CXPOLL_STOPPED) { 3906a02c996SSimon Arlott mutex_lock(&instance->poll_state_serialize); 3916a02c996SSimon Arlott /* request stop */ 3926a02c996SSimon Arlott if (instance->poll_state == CXPOLL_POLLING) 3936a02c996SSimon Arlott instance->poll_state = CXPOLL_STOPPING; 3946a02c996SSimon Arlott mutex_unlock(&instance->poll_state_serialize); 3956a02c996SSimon Arlott } 3966a02c996SSimon Arlott 3976a02c996SSimon Arlott mutex_unlock(&instance->adsl_state_serialize); 3986a02c996SSimon Arlott 3996a02c996SSimon Arlott if (poll == CXPOLL_POLLING) 4006a02c996SSimon Arlott cxacru_poll_status(&instance->poll_work.work); 4016a02c996SSimon Arlott 4026a02c996SSimon Arlott return ret; 4036a02c996SSimon Arlott } 4046a02c996SSimon Arlott 405fa70fe44SSimon Arlott /* 406fa70fe44SSimon Arlott * All device attributes are included in CXACRU_ALL_FILES 407fa70fe44SSimon Arlott * so that the same list can be used multiple times: 408fa70fe44SSimon Arlott * INIT (define the device attributes) 409fa70fe44SSimon Arlott * CREATE (create all the device files) 410fa70fe44SSimon Arlott * REMOVE (remove all the device files) 411fa70fe44SSimon Arlott * 412fa70fe44SSimon Arlott * With the last two being defined as needed in the functions 413fa70fe44SSimon Arlott * they are used in before calling CXACRU_ALL_FILES() 414fa70fe44SSimon Arlott */ 415fa70fe44SSimon Arlott #define CXACRU_ALL_FILES(_action) \ 416fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_RATE, u32, downstream_rate); \ 417fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_RATE, u32, upstream_rate); \ 418fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_LINK_STATUS, LINK, link_status); \ 419fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_LINE_STATUS, LINE, line_status); \ 420fa70fe44SSimon Arlott CXACRU__ATTR_##_action( mac_address); \ 421fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_SNR_MARGIN, dB, upstream_snr_margin); \ 422fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_SNR_MARGIN, dB, downstream_snr_margin); \ 423fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_ATTENUATION, dB, upstream_attenuation); \ 424fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_ATTENUATION, dB, downstream_attenuation); \ 425fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_TRANSMITTER_POWER, s8, transmitter_power); \ 426fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_BITS_PER_FRAME, u32, upstream_bits_per_frame); \ 427fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_BITS_PER_FRAME, u32, downstream_bits_per_frame); \ 428fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_STARTUP_ATTEMPTS, u32, startup_attempts); \ 429fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_CRC_ERRORS, u32, upstream_crc_errors); \ 430fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_CRC_ERRORS, u32, downstream_crc_errors); \ 431fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_FEC_ERRORS, u32, upstream_fec_errors); \ 432fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_FEC_ERRORS, u32, downstream_fec_errors); \ 433fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_UPSTREAM_HEC_ERRORS, u32, upstream_hec_errors); \ 434fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_HEC_ERRORS, u32, downstream_hec_errors); \ 435fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_LINE_STARTABLE, bool, line_startable); \ 436fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_MODULATION, MODU, modulation); \ 437fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND, u32, adsl_headend); \ 438fa70fe44SSimon Arlott CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND_ENVIRONMENT, u32, adsl_headend_environment); \ 4396a02c996SSimon Arlott CXACRU_ATTR_##_action(CXINF_CONTROLLER_VERSION, u32, adsl_controller_version); \ 4406a02c996SSimon Arlott CXACRU_CMD_##_action( adsl_state); 441fa70fe44SSimon Arlott 442fa70fe44SSimon Arlott CXACRU_ALL_FILES(INIT); 443fa70fe44SSimon Arlott 4441b0e6146SDuncan Sands /* the following three functions are stolen from drivers/usb/core/message.c */ 4457d12e780SDavid Howells static void cxacru_blocking_completion(struct urb *urb) 4461b0e6146SDuncan Sands { 4471b0e6146SDuncan Sands complete((struct completion *)urb->context); 4481b0e6146SDuncan Sands } 4491b0e6146SDuncan Sands 4501b0e6146SDuncan Sands static void cxacru_timeout_kill(unsigned long data) 4511b0e6146SDuncan Sands { 4521b0e6146SDuncan Sands usb_unlink_urb((struct urb *) data); 4531b0e6146SDuncan Sands } 4541b0e6146SDuncan Sands 4551b0e6146SDuncan Sands static int cxacru_start_wait_urb(struct urb *urb, struct completion *done, 4561b0e6146SDuncan Sands int* actual_length) 4571b0e6146SDuncan Sands { 4581b0e6146SDuncan Sands struct timer_list timer; 4591b0e6146SDuncan Sands 4601b0e6146SDuncan Sands init_timer(&timer); 4611b0e6146SDuncan Sands timer.expires = jiffies + msecs_to_jiffies(CMD_TIMEOUT); 4621b0e6146SDuncan Sands timer.data = (unsigned long) urb; 4631b0e6146SDuncan Sands timer.function = cxacru_timeout_kill; 4641b0e6146SDuncan Sands add_timer(&timer); 4651b0e6146SDuncan Sands wait_for_completion(done); 4661b0e6146SDuncan Sands del_timer_sync(&timer); 4671b0e6146SDuncan Sands 4681b0e6146SDuncan Sands if (actual_length) 4691b0e6146SDuncan Sands *actual_length = urb->actual_length; 4703b79cc26SOliver Neukum return urb->status; /* must read status after completion */ 4711b0e6146SDuncan Sands } 4721b0e6146SDuncan Sands 4731b0e6146SDuncan Sands static int cxacru_cm(struct cxacru_data *instance, enum cxacru_cm_request cm, 4741b0e6146SDuncan Sands u8 *wdata, int wsize, u8 *rdata, int rsize) 4751b0e6146SDuncan Sands { 4761b0e6146SDuncan Sands int ret, actlen; 4771b0e6146SDuncan Sands int offb, offd; 4781b0e6146SDuncan Sands const int stride = CMD_PACKET_SIZE - 4; 4791b0e6146SDuncan Sands u8 *wbuf = instance->snd_buf; 4801b0e6146SDuncan Sands u8 *rbuf = instance->rcv_buf; 4811b0e6146SDuncan Sands int wbuflen = ((wsize - 1) / stride + 1) * CMD_PACKET_SIZE; 4821b0e6146SDuncan Sands int rbuflen = ((rsize - 1) / stride + 1) * CMD_PACKET_SIZE; 4831b0e6146SDuncan Sands 4841b0e6146SDuncan Sands if (wbuflen > PAGE_SIZE || rbuflen > PAGE_SIZE) { 485*4ac0718eSSimon Arlott if (printk_ratelimit()) 486*4ac0718eSSimon Arlott usb_err(instance->usbatm, "requested transfer size too large (%d, %d)\n", 487*4ac0718eSSimon Arlott wbuflen, rbuflen); 4881b0e6146SDuncan Sands ret = -ENOMEM; 4891b0e6146SDuncan Sands goto fail; 4901b0e6146SDuncan Sands } 4911b0e6146SDuncan Sands 492ab3c81ffSArjan van de Ven mutex_lock(&instance->cm_serialize); 4931b0e6146SDuncan Sands 4941b0e6146SDuncan Sands /* submit reading urb before the writing one */ 4951b0e6146SDuncan Sands init_completion(&instance->rcv_done); 4961b0e6146SDuncan Sands ret = usb_submit_urb(instance->rcv_urb, GFP_KERNEL); 4971b0e6146SDuncan Sands if (ret < 0) { 498*4ac0718eSSimon Arlott if (printk_ratelimit()) 499*4ac0718eSSimon Arlott usb_err(instance->usbatm, "submit of read urb for cm %#x failed (%d)\n", 500*4ac0718eSSimon Arlott cm, ret); 5011b0e6146SDuncan Sands goto fail; 5021b0e6146SDuncan Sands } 5031b0e6146SDuncan Sands 5041b0e6146SDuncan Sands memset(wbuf, 0, wbuflen); 5051b0e6146SDuncan Sands /* handle wsize == 0 */ 5061b0e6146SDuncan Sands wbuf[0] = cm; 5071b0e6146SDuncan Sands for (offb = offd = 0; offd < wsize; offd += stride, offb += CMD_PACKET_SIZE) { 5081b0e6146SDuncan Sands wbuf[offb] = cm; 5091b0e6146SDuncan Sands memcpy(wbuf + offb + 4, wdata + offd, min_t(int, stride, wsize - offd)); 5101b0e6146SDuncan Sands } 5111b0e6146SDuncan Sands 5121b0e6146SDuncan Sands instance->snd_urb->transfer_buffer_length = wbuflen; 5131b0e6146SDuncan Sands init_completion(&instance->snd_done); 5141b0e6146SDuncan Sands ret = usb_submit_urb(instance->snd_urb, GFP_KERNEL); 5151b0e6146SDuncan Sands if (ret < 0) { 516*4ac0718eSSimon Arlott if (printk_ratelimit()) 517*4ac0718eSSimon Arlott usb_err(instance->usbatm, "submit of write urb for cm %#x failed (%d)\n", 518*4ac0718eSSimon Arlott cm, ret); 5191b0e6146SDuncan Sands goto fail; 5201b0e6146SDuncan Sands } 5211b0e6146SDuncan Sands 5221b0e6146SDuncan Sands ret = cxacru_start_wait_urb(instance->snd_urb, &instance->snd_done, NULL); 5231b0e6146SDuncan Sands if (ret < 0) { 524*4ac0718eSSimon Arlott if (printk_ratelimit()) 525*4ac0718eSSimon Arlott usb_err(instance->usbatm, "send of cm %#x failed (%d)\n", cm, ret); 5261b0e6146SDuncan Sands goto fail; 5271b0e6146SDuncan Sands } 5281b0e6146SDuncan Sands 5291b0e6146SDuncan Sands ret = cxacru_start_wait_urb(instance->rcv_urb, &instance->rcv_done, &actlen); 5301b0e6146SDuncan Sands if (ret < 0) { 531*4ac0718eSSimon Arlott if (printk_ratelimit()) 532*4ac0718eSSimon Arlott usb_err(instance->usbatm, "receive of cm %#x failed (%d)\n", cm, ret); 5331b0e6146SDuncan Sands goto fail; 5341b0e6146SDuncan Sands } 5351b0e6146SDuncan Sands if (actlen % CMD_PACKET_SIZE || !actlen) { 536*4ac0718eSSimon Arlott if (printk_ratelimit()) 537*4ac0718eSSimon Arlott usb_err(instance->usbatm, "invalid response length to cm %#x: %d\n", 538*4ac0718eSSimon Arlott cm, actlen); 5391b0e6146SDuncan Sands ret = -EIO; 5401b0e6146SDuncan Sands goto fail; 5411b0e6146SDuncan Sands } 5421b0e6146SDuncan Sands 5431b0e6146SDuncan Sands /* check the return status and copy the data to the output buffer, if needed */ 5441b0e6146SDuncan Sands for (offb = offd = 0; offd < rsize && offb < actlen; offb += CMD_PACKET_SIZE) { 5451b0e6146SDuncan Sands if (rbuf[offb] != cm) { 546*4ac0718eSSimon Arlott if (printk_ratelimit()) 547*4ac0718eSSimon Arlott usb_err(instance->usbatm, "wrong cm %#x in response to cm %#x\n", 548*4ac0718eSSimon Arlott rbuf[offb], cm); 5491b0e6146SDuncan Sands ret = -EIO; 5501b0e6146SDuncan Sands goto fail; 5511b0e6146SDuncan Sands } 5521b0e6146SDuncan Sands if (rbuf[offb + 1] != CM_STATUS_SUCCESS) { 553*4ac0718eSSimon Arlott if (printk_ratelimit()) 554*4ac0718eSSimon Arlott usb_err(instance->usbatm, "response to cm %#x failed: %#x\n", 555*4ac0718eSSimon Arlott cm, rbuf[offb + 1]); 5561b0e6146SDuncan Sands ret = -EIO; 5571b0e6146SDuncan Sands goto fail; 5581b0e6146SDuncan Sands } 5591b0e6146SDuncan Sands if (offd >= rsize) 5601b0e6146SDuncan Sands break; 5611b0e6146SDuncan Sands memcpy(rdata + offd, rbuf + offb + 4, min_t(int, stride, rsize - offd)); 5621b0e6146SDuncan Sands offd += stride; 5631b0e6146SDuncan Sands } 5641b0e6146SDuncan Sands 5651b0e6146SDuncan Sands ret = offd; 5661b0e6146SDuncan Sands dbg("cm %#x", cm); 5671b0e6146SDuncan Sands fail: 568ab3c81ffSArjan van de Ven mutex_unlock(&instance->cm_serialize); 5691b0e6146SDuncan Sands return ret; 5701b0e6146SDuncan Sands } 5711b0e6146SDuncan Sands 5721b0e6146SDuncan Sands static int cxacru_cm_get_array(struct cxacru_data *instance, enum cxacru_cm_request cm, 5731b0e6146SDuncan Sands u32 *data, int size) 5741b0e6146SDuncan Sands { 5751b0e6146SDuncan Sands int ret, len; 5761b0e6146SDuncan Sands u32 *buf; 5771b0e6146SDuncan Sands int offb, offd; 5781b0e6146SDuncan Sands const int stride = CMD_PACKET_SIZE / (4 * 2) - 1; 5791b0e6146SDuncan Sands int buflen = ((size - 1) / stride + 1 + size * 2) * 4; 5801b0e6146SDuncan Sands 5811b0e6146SDuncan Sands buf = kmalloc(buflen, GFP_KERNEL); 5821b0e6146SDuncan Sands if (!buf) 5831b0e6146SDuncan Sands return -ENOMEM; 5841b0e6146SDuncan Sands 5851b0e6146SDuncan Sands ret = cxacru_cm(instance, cm, NULL, 0, (u8 *) buf, buflen); 5861b0e6146SDuncan Sands if (ret < 0) 5871b0e6146SDuncan Sands goto cleanup; 5881b0e6146SDuncan Sands 5891b0e6146SDuncan Sands /* len > 0 && len % 4 == 0 guaranteed by cxacru_cm() */ 5901b0e6146SDuncan Sands len = ret / 4; 5911b0e6146SDuncan Sands for (offb = 0; offb < len; ) { 5921b0e6146SDuncan Sands int l = le32_to_cpu(buf[offb++]); 5931b0e6146SDuncan Sands if (l > stride || l > (len - offb) / 2) { 594*4ac0718eSSimon Arlott if (printk_ratelimit()) 595*4ac0718eSSimon Arlott usb_err(instance->usbatm, "invalid data length from cm %#x: %d\n", 596*4ac0718eSSimon Arlott cm, l); 5971b0e6146SDuncan Sands ret = -EIO; 5981b0e6146SDuncan Sands goto cleanup; 5991b0e6146SDuncan Sands } 6001b0e6146SDuncan Sands while (l--) { 6011b0e6146SDuncan Sands offd = le32_to_cpu(buf[offb++]); 6021b0e6146SDuncan Sands if (offd >= size) { 603*4ac0718eSSimon Arlott if (printk_ratelimit()) 604*4ac0718eSSimon Arlott usb_err(instance->usbatm, "wrong index #%x in response to cm #%x\n", 605*4ac0718eSSimon Arlott offd, cm); 6061b0e6146SDuncan Sands ret = -EIO; 6071b0e6146SDuncan Sands goto cleanup; 6081b0e6146SDuncan Sands } 6091b0e6146SDuncan Sands data[offd] = le32_to_cpu(buf[offb++]); 6101b0e6146SDuncan Sands } 6111b0e6146SDuncan Sands } 6121b0e6146SDuncan Sands 6131b0e6146SDuncan Sands ret = 0; 6141b0e6146SDuncan Sands 6151b0e6146SDuncan Sands cleanup: 6161b0e6146SDuncan Sands kfree(buf); 6171b0e6146SDuncan Sands return ret; 6181b0e6146SDuncan Sands } 6191b0e6146SDuncan Sands 6201b0e6146SDuncan Sands static int cxacru_card_status(struct cxacru_data *instance) 6211b0e6146SDuncan Sands { 6221b0e6146SDuncan Sands int ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0); 6231b0e6146SDuncan Sands if (ret < 0) { /* firmware not loaded */ 6241b0e6146SDuncan Sands dbg("cxacru_adsl_start: CARD_GET_STATUS returned %d", ret); 6251b0e6146SDuncan Sands return ret; 6261b0e6146SDuncan Sands } 6271b0e6146SDuncan Sands return 0; 6281b0e6146SDuncan Sands } 6291b0e6146SDuncan Sands 630da1f82b5SSimon Arlott static void cxacru_remove_device_files(struct usbatm_data *usbatm_instance, 631da1f82b5SSimon Arlott struct atm_dev *atm_dev) 632da1f82b5SSimon Arlott { 633da1f82b5SSimon Arlott struct usb_interface *intf = usbatm_instance->usb_intf; 634da1f82b5SSimon Arlott 635da1f82b5SSimon Arlott #define CXACRU_DEVICE_REMOVE_FILE(_name) \ 636da1f82b5SSimon Arlott device_remove_file(&intf->dev, &dev_attr_##_name); 637da1f82b5SSimon Arlott CXACRU_ALL_FILES(REMOVE); 638da1f82b5SSimon Arlott #undef CXACRU_DEVICE_REMOVE_FILE 639da1f82b5SSimon Arlott } 640da1f82b5SSimon Arlott 6411b0e6146SDuncan Sands static int cxacru_atm_start(struct usbatm_data *usbatm_instance, 6421b0e6146SDuncan Sands struct atm_dev *atm_dev) 6431b0e6146SDuncan Sands { 6441b0e6146SDuncan Sands struct cxacru_data *instance = usbatm_instance->driver_data; 645da1f82b5SSimon Arlott struct usb_interface *intf = usbatm_instance->usb_intf; 6461b0e6146SDuncan Sands /* 6471b0e6146SDuncan Sands struct atm_dev *atm_dev = usbatm_instance->atm_dev; 6481b0e6146SDuncan Sands */ 6491b0e6146SDuncan Sands int ret; 6506a02c996SSimon Arlott int start_polling = 1; 6511b0e6146SDuncan Sands 6521b0e6146SDuncan Sands dbg("cxacru_atm_start"); 6531b0e6146SDuncan Sands 6541b0e6146SDuncan Sands /* Read MAC address */ 6551b0e6146SDuncan Sands ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_MAC_ADDRESS, NULL, 0, 6561b0e6146SDuncan Sands atm_dev->esi, sizeof(atm_dev->esi)); 6571b0e6146SDuncan Sands if (ret < 0) { 6580ec3c7e8SDuncan Sands atm_err(usbatm_instance, "cxacru_atm_start: CARD_GET_MAC_ADDRESS returned %d\n", ret); 6591b0e6146SDuncan Sands return ret; 6601b0e6146SDuncan Sands } 6611b0e6146SDuncan Sands 662da1f82b5SSimon Arlott #define CXACRU_DEVICE_CREATE_FILE(_name) \ 663da1f82b5SSimon Arlott ret = device_create_file(&intf->dev, &dev_attr_##_name); \ 664da1f82b5SSimon Arlott if (unlikely(ret)) \ 665da1f82b5SSimon Arlott goto fail_sysfs; 666da1f82b5SSimon Arlott CXACRU_ALL_FILES(CREATE); 667da1f82b5SSimon Arlott #undef CXACRU_DEVICE_CREATE_FILE 668da1f82b5SSimon Arlott 6691b0e6146SDuncan Sands /* start ADSL */ 6706a02c996SSimon Arlott mutex_lock(&instance->adsl_state_serialize); 6711b0e6146SDuncan Sands ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0); 672fd209e35SSimon Arlott if (ret < 0) 6730ec3c7e8SDuncan Sands atm_err(usbatm_instance, "cxacru_atm_start: CHIP_ADSL_LINE_START returned %d\n", ret); 6741b0e6146SDuncan Sands 6751b0e6146SDuncan Sands /* Start status polling */ 6766a02c996SSimon Arlott mutex_lock(&instance->poll_state_serialize); 6776a02c996SSimon Arlott switch (instance->poll_state) { 6786a02c996SSimon Arlott case CXPOLL_STOPPED: 6796a02c996SSimon Arlott /* start polling */ 6806a02c996SSimon Arlott instance->poll_state = CXPOLL_POLLING; 6816a02c996SSimon Arlott break; 6826a02c996SSimon Arlott 6836a02c996SSimon Arlott case CXPOLL_STOPPING: 6846a02c996SSimon Arlott /* abort stop request */ 6856a02c996SSimon Arlott instance->poll_state = CXPOLL_POLLING; 6866a02c996SSimon Arlott case CXPOLL_POLLING: 6876a02c996SSimon Arlott case CXPOLL_SHUTDOWN: 6886a02c996SSimon Arlott /* don't start polling */ 6896a02c996SSimon Arlott start_polling = 0; 6906a02c996SSimon Arlott } 6916a02c996SSimon Arlott mutex_unlock(&instance->poll_state_serialize); 6926a02c996SSimon Arlott mutex_unlock(&instance->adsl_state_serialize); 6936a02c996SSimon Arlott 6946a02c996SSimon Arlott if (start_polling) 695c4028958SDavid Howells cxacru_poll_status(&instance->poll_work.work); 6961b0e6146SDuncan Sands return 0; 697da1f82b5SSimon Arlott 698da1f82b5SSimon Arlott fail_sysfs: 699da1f82b5SSimon Arlott usb_err(usbatm_instance, "cxacru_atm_start: device_create_file failed (%d)\n", ret); 700da1f82b5SSimon Arlott cxacru_remove_device_files(usbatm_instance, atm_dev); 701da1f82b5SSimon Arlott return ret; 7021b0e6146SDuncan Sands } 7031b0e6146SDuncan Sands 704c4028958SDavid Howells static void cxacru_poll_status(struct work_struct *work) 7051b0e6146SDuncan Sands { 706c4028958SDavid Howells struct cxacru_data *instance = 707c4028958SDavid Howells container_of(work, struct cxacru_data, poll_work.work); 7081b0e6146SDuncan Sands u32 buf[CXINF_MAX] = {}; 7090ec3c7e8SDuncan Sands struct usbatm_data *usbatm = instance->usbatm; 7100ec3c7e8SDuncan Sands struct atm_dev *atm_dev = usbatm->atm_dev; 7116a02c996SSimon Arlott int keep_polling = 1; 7121b0e6146SDuncan Sands int ret; 7131b0e6146SDuncan Sands 7141b0e6146SDuncan Sands ret = cxacru_cm_get_array(instance, CM_REQUEST_CARD_INFO_GET, buf, CXINF_MAX); 7151b0e6146SDuncan Sands if (ret < 0) { 7166a02c996SSimon Arlott if (ret != -ESHUTDOWN) 7170ec3c7e8SDuncan Sands atm_warn(usbatm, "poll status: error %d\n", ret); 7186a02c996SSimon Arlott 7196a02c996SSimon Arlott mutex_lock(&instance->poll_state_serialize); 7206a02c996SSimon Arlott if (instance->poll_state != CXPOLL_SHUTDOWN) { 7216a02c996SSimon Arlott instance->poll_state = CXPOLL_STOPPED; 7226a02c996SSimon Arlott 7236a02c996SSimon Arlott if (ret != -ESHUTDOWN) 7246a02c996SSimon Arlott atm_warn(usbatm, "polling disabled, set adsl_state" 7256a02c996SSimon Arlott " to 'start' or 'poll' to resume\n"); 7266a02c996SSimon Arlott } 7276a02c996SSimon Arlott mutex_unlock(&instance->poll_state_serialize); 7281b0e6146SDuncan Sands goto reschedule; 7291b0e6146SDuncan Sands } 7301b0e6146SDuncan Sands 731fa70fe44SSimon Arlott memcpy(instance->card_info, buf, sizeof(instance->card_info)); 732fa70fe44SSimon Arlott 7336a02c996SSimon Arlott if (instance->adsl_status != buf[CXINF_LINE_STARTABLE]) { 7346a02c996SSimon Arlott instance->adsl_status = buf[CXINF_LINE_STARTABLE]; 7356a02c996SSimon Arlott 7366a02c996SSimon Arlott switch (instance->adsl_status) { 7376a02c996SSimon Arlott case 0: 7386a02c996SSimon Arlott atm_printk(KERN_INFO, usbatm, "ADSL state: running\n"); 7396a02c996SSimon Arlott break; 7406a02c996SSimon Arlott 7416a02c996SSimon Arlott case 1: 7426a02c996SSimon Arlott atm_printk(KERN_INFO, usbatm, "ADSL state: stopped\n"); 7436a02c996SSimon Arlott break; 7446a02c996SSimon Arlott 7456a02c996SSimon Arlott default: 7466a02c996SSimon Arlott atm_printk(KERN_INFO, usbatm, "Unknown adsl status %02x\n", instance->adsl_status); 7476a02c996SSimon Arlott break; 7486a02c996SSimon Arlott } 7496a02c996SSimon Arlott } 7506a02c996SSimon Arlott 7511b0e6146SDuncan Sands if (instance->line_status == buf[CXINF_LINE_STATUS]) 7521b0e6146SDuncan Sands goto reschedule; 7531b0e6146SDuncan Sands 7541b0e6146SDuncan Sands instance->line_status = buf[CXINF_LINE_STATUS]; 7551b0e6146SDuncan Sands switch (instance->line_status) { 7561b0e6146SDuncan Sands case 0: 7571b0e6146SDuncan Sands atm_dev->signal = ATM_PHY_SIG_LOST; 7580ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: down\n"); 7591b0e6146SDuncan Sands break; 7601b0e6146SDuncan Sands 7611b0e6146SDuncan Sands case 1: 7621b0e6146SDuncan Sands atm_dev->signal = ATM_PHY_SIG_LOST; 7630ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: attempting to activate\n"); 7641b0e6146SDuncan Sands break; 7651b0e6146SDuncan Sands 7661b0e6146SDuncan Sands case 2: 7671b0e6146SDuncan Sands atm_dev->signal = ATM_PHY_SIG_LOST; 7680ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: training\n"); 7691b0e6146SDuncan Sands break; 7701b0e6146SDuncan Sands 7711b0e6146SDuncan Sands case 3: 7721b0e6146SDuncan Sands atm_dev->signal = ATM_PHY_SIG_LOST; 7730ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: channel analysis\n"); 7741b0e6146SDuncan Sands break; 7751b0e6146SDuncan Sands 7761b0e6146SDuncan Sands case 4: 7771b0e6146SDuncan Sands atm_dev->signal = ATM_PHY_SIG_LOST; 7780ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: exchange\n"); 7791b0e6146SDuncan Sands break; 7801b0e6146SDuncan Sands 7811b0e6146SDuncan Sands case 5: 7821b0e6146SDuncan Sands atm_dev->link_rate = buf[CXINF_DOWNSTREAM_RATE] * 1000 / 424; 7831b0e6146SDuncan Sands atm_dev->signal = ATM_PHY_SIG_FOUND; 7841b0e6146SDuncan Sands 7850ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: up (%d kb/s down | %d kb/s up)\n", 7861b0e6146SDuncan Sands buf[CXINF_DOWNSTREAM_RATE], buf[CXINF_UPSTREAM_RATE]); 7871b0e6146SDuncan Sands break; 7881b0e6146SDuncan Sands 7891b0e6146SDuncan Sands case 6: 7901b0e6146SDuncan Sands atm_dev->signal = ATM_PHY_SIG_LOST; 7910ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: waiting\n"); 7921b0e6146SDuncan Sands break; 7931b0e6146SDuncan Sands 7941b0e6146SDuncan Sands case 7: 7951b0e6146SDuncan Sands atm_dev->signal = ATM_PHY_SIG_LOST; 7960ec3c7e8SDuncan Sands atm_info(usbatm, "ADSL line: initializing\n"); 7971b0e6146SDuncan Sands break; 7981b0e6146SDuncan Sands 7991b0e6146SDuncan Sands default: 8001b0e6146SDuncan Sands atm_dev->signal = ATM_PHY_SIG_UNKNOWN; 8010ec3c7e8SDuncan Sands atm_info(usbatm, "Unknown line state %02x\n", instance->line_status); 8021b0e6146SDuncan Sands break; 8031b0e6146SDuncan Sands } 8041b0e6146SDuncan Sands reschedule: 8056a02c996SSimon Arlott 8066a02c996SSimon Arlott mutex_lock(&instance->poll_state_serialize); 8076a02c996SSimon Arlott if (instance->poll_state == CXPOLL_STOPPING && 8086a02c996SSimon Arlott instance->adsl_status == 1 && /* stopped */ 8096a02c996SSimon Arlott instance->line_status == 0) /* down */ 8106a02c996SSimon Arlott instance->poll_state = CXPOLL_STOPPED; 8116a02c996SSimon Arlott 8126a02c996SSimon Arlott if (instance->poll_state == CXPOLL_STOPPED) 8136a02c996SSimon Arlott keep_polling = 0; 8146a02c996SSimon Arlott mutex_unlock(&instance->poll_state_serialize); 8156a02c996SSimon Arlott 8166a02c996SSimon Arlott if (keep_polling) 817fa70fe44SSimon Arlott schedule_delayed_work(&instance->poll_work, 8186a02c996SSimon Arlott round_jiffies_relative(POLL_INTERVAL*HZ)); 8191b0e6146SDuncan Sands } 8201b0e6146SDuncan Sands 8211b0e6146SDuncan Sands static int cxacru_fw(struct usb_device *usb_dev, enum cxacru_fw_request fw, 8221b0e6146SDuncan Sands u8 code1, u8 code2, u32 addr, u8 *data, int size) 8231b0e6146SDuncan Sands { 8241b0e6146SDuncan Sands int ret; 8251b0e6146SDuncan Sands u8 *buf; 8261b0e6146SDuncan Sands int offd, offb; 8271b0e6146SDuncan Sands const int stride = CMD_PACKET_SIZE - 8; 8281b0e6146SDuncan Sands 8291b0e6146SDuncan Sands buf = (u8 *) __get_free_page(GFP_KERNEL); 8301b0e6146SDuncan Sands if (!buf) 8311b0e6146SDuncan Sands return -ENOMEM; 8321b0e6146SDuncan Sands 8331b0e6146SDuncan Sands offb = offd = 0; 8341b0e6146SDuncan Sands do { 8351b0e6146SDuncan Sands int l = min_t(int, stride, size - offd); 8361b0e6146SDuncan Sands buf[offb++] = fw; 8371b0e6146SDuncan Sands buf[offb++] = l; 8381b0e6146SDuncan Sands buf[offb++] = code1; 8391b0e6146SDuncan Sands buf[offb++] = code2; 8401b0e6146SDuncan Sands *((u32 *) (buf + offb)) = cpu_to_le32(addr); 8411b0e6146SDuncan Sands offb += 4; 8421b0e6146SDuncan Sands addr += l; 8431b0e6146SDuncan Sands if(l) 8441b0e6146SDuncan Sands memcpy(buf + offb, data + offd, l); 8451b0e6146SDuncan Sands if (l < stride) 8461b0e6146SDuncan Sands memset(buf + offb + l, 0, stride - l); 8471b0e6146SDuncan Sands offb += stride; 8481b0e6146SDuncan Sands offd += stride; 8491b0e6146SDuncan Sands if ((offb >= PAGE_SIZE) || (offd >= size)) { 8501b0e6146SDuncan Sands ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD), 8511b0e6146SDuncan Sands buf, offb, NULL, CMD_TIMEOUT); 8521b0e6146SDuncan Sands if (ret < 0) { 8531b0e6146SDuncan Sands dbg("sending fw %#x failed", fw); 8541b0e6146SDuncan Sands goto cleanup; 8551b0e6146SDuncan Sands } 8561b0e6146SDuncan Sands offb = 0; 8571b0e6146SDuncan Sands } 8581b0e6146SDuncan Sands } while(offd < size); 8591b0e6146SDuncan Sands dbg("sent fw %#x", fw); 8601b0e6146SDuncan Sands 8611b0e6146SDuncan Sands ret = 0; 8621b0e6146SDuncan Sands 8631b0e6146SDuncan Sands cleanup: 8641b0e6146SDuncan Sands free_page((unsigned long) buf); 8651b0e6146SDuncan Sands return ret; 8661b0e6146SDuncan Sands } 8671b0e6146SDuncan Sands 8681b0e6146SDuncan Sands static void cxacru_upload_firmware(struct cxacru_data *instance, 8691b0e6146SDuncan Sands const struct firmware *fw, 8701b0e6146SDuncan Sands const struct firmware *bp, 8711b0e6146SDuncan Sands const struct firmware *cf) 8721b0e6146SDuncan Sands { 8731b0e6146SDuncan Sands int ret; 8741b0e6146SDuncan Sands int off; 8750ec3c7e8SDuncan Sands struct usbatm_data *usbatm = instance->usbatm; 8760ec3c7e8SDuncan Sands struct usb_device *usb_dev = usbatm->usb_dev; 8771b0e6146SDuncan Sands u16 signature[] = { usb_dev->descriptor.idVendor, usb_dev->descriptor.idProduct }; 8781b0e6146SDuncan Sands u32 val; 8791b0e6146SDuncan Sands 8801b0e6146SDuncan Sands dbg("cxacru_upload_firmware"); 8811b0e6146SDuncan Sands 8821b0e6146SDuncan Sands /* FirmwarePllFClkValue */ 8831b0e6146SDuncan Sands val = cpu_to_le32(instance->modem_type->pll_f_clk); 8841b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, PLLFCLK_ADDR, (u8 *) &val, 4); 8851b0e6146SDuncan Sands if (ret) { 8860ec3c7e8SDuncan Sands usb_err(usbatm, "FirmwarePllFClkValue failed: %d\n", ret); 8871b0e6146SDuncan Sands return; 8881b0e6146SDuncan Sands } 8891b0e6146SDuncan Sands 8901b0e6146SDuncan Sands /* FirmwarePllBClkValue */ 8911b0e6146SDuncan Sands val = cpu_to_le32(instance->modem_type->pll_b_clk); 8921b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, PLLBCLK_ADDR, (u8 *) &val, 4); 8931b0e6146SDuncan Sands if (ret) { 8940ec3c7e8SDuncan Sands usb_err(usbatm, "FirmwarePllBClkValue failed: %d\n", ret); 8951b0e6146SDuncan Sands return; 8961b0e6146SDuncan Sands } 8971b0e6146SDuncan Sands 8981b0e6146SDuncan Sands /* Enable SDRAM */ 8991b0e6146SDuncan Sands val = cpu_to_le32(SDRAM_ENA); 9001b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, SDRAMEN_ADDR, (u8 *) &val, 4); 9011b0e6146SDuncan Sands if (ret) { 9020ec3c7e8SDuncan Sands usb_err(usbatm, "Enable SDRAM failed: %d\n", ret); 9031b0e6146SDuncan Sands return; 9041b0e6146SDuncan Sands } 9051b0e6146SDuncan Sands 9061b0e6146SDuncan Sands /* Firmware */ 9071b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, FW_ADDR, fw->data, fw->size); 9081b0e6146SDuncan Sands if (ret) { 9090ec3c7e8SDuncan Sands usb_err(usbatm, "Firmware upload failed: %d\n", ret); 9101b0e6146SDuncan Sands return; 9111b0e6146SDuncan Sands } 9121b0e6146SDuncan Sands 9131b0e6146SDuncan Sands /* Boot ROM patch */ 9141b0e6146SDuncan Sands if (instance->modem_type->boot_rom_patch) { 9151b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, BR_ADDR, bp->data, bp->size); 9161b0e6146SDuncan Sands if (ret) { 9170ec3c7e8SDuncan Sands usb_err(usbatm, "Boot ROM patching failed: %d\n", ret); 9181b0e6146SDuncan Sands return; 9191b0e6146SDuncan Sands } 9201b0e6146SDuncan Sands } 9211b0e6146SDuncan Sands 9221b0e6146SDuncan Sands /* Signature */ 9231b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, SIG_ADDR, (u8 *) signature, 4); 9241b0e6146SDuncan Sands if (ret) { 9250ec3c7e8SDuncan Sands usb_err(usbatm, "Signature storing failed: %d\n", ret); 9261b0e6146SDuncan Sands return; 9271b0e6146SDuncan Sands } 9281b0e6146SDuncan Sands 9291b0e6146SDuncan Sands if (instance->modem_type->boot_rom_patch) { 9301b0e6146SDuncan Sands val = cpu_to_le32(BR_ADDR); 9311b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_WRITE_MEM, 0x2, 0x0, BR_STACK_ADDR, (u8 *) &val, 4); 9321b0e6146SDuncan Sands } 9331b0e6146SDuncan Sands else { 9341b0e6146SDuncan Sands ret = cxacru_fw(usb_dev, FW_GOTO_MEM, 0x0, 0x0, FW_ADDR, NULL, 0); 9351b0e6146SDuncan Sands } 9361b0e6146SDuncan Sands if (ret) { 9370ec3c7e8SDuncan Sands usb_err(usbatm, "Passing control to firmware failed: %d\n", ret); 9381b0e6146SDuncan Sands return; 9391b0e6146SDuncan Sands } 9401b0e6146SDuncan Sands 9411b0e6146SDuncan Sands /* Delay to allow firmware to start up. */ 9421b0e6146SDuncan Sands msleep_interruptible(1000); 9431b0e6146SDuncan Sands 9441b0e6146SDuncan Sands usb_clear_halt(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD)); 9451b0e6146SDuncan Sands usb_clear_halt(usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_CMD)); 9461b0e6146SDuncan Sands usb_clear_halt(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_DATA)); 9471b0e6146SDuncan Sands usb_clear_halt(usb_dev, usb_rcvbulkpipe(usb_dev, CXACRU_EP_DATA)); 9481b0e6146SDuncan Sands 9491b0e6146SDuncan Sands ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0); 9501b0e6146SDuncan Sands if (ret < 0) { 9510ec3c7e8SDuncan Sands usb_err(usbatm, "modem failed to initialize: %d\n", ret); 9521b0e6146SDuncan Sands return; 9531b0e6146SDuncan Sands } 9541b0e6146SDuncan Sands 9551b0e6146SDuncan Sands /* Load config data (le32), doing one packet at a time */ 9561b0e6146SDuncan Sands if (cf) 9571b0e6146SDuncan Sands for (off = 0; off < cf->size / 4; ) { 9581b0e6146SDuncan Sands u32 buf[CMD_PACKET_SIZE / 4 - 1]; 9591b0e6146SDuncan Sands int i, len = min_t(int, cf->size / 4 - off, CMD_PACKET_SIZE / 4 / 2 - 1); 9601b0e6146SDuncan Sands buf[0] = cpu_to_le32(len); 9611b0e6146SDuncan Sands for (i = 0; i < len; i++, off++) { 9621b0e6146SDuncan Sands buf[i * 2 + 1] = cpu_to_le32(off); 9631b0e6146SDuncan Sands memcpy(buf + i * 2 + 2, cf->data + off * 4, 4); 9641b0e6146SDuncan Sands } 9651b0e6146SDuncan Sands ret = cxacru_cm(instance, CM_REQUEST_CARD_DATA_SET, 9661b0e6146SDuncan Sands (u8 *) buf, len, NULL, 0); 9671b0e6146SDuncan Sands if (ret < 0) { 9680ec3c7e8SDuncan Sands usb_err(usbatm, "load config data failed: %d\n", ret); 9691b0e6146SDuncan Sands return; 9701b0e6146SDuncan Sands } 9711b0e6146SDuncan Sands } 9721b0e6146SDuncan Sands 9731b0e6146SDuncan Sands msleep_interruptible(4000); 9741b0e6146SDuncan Sands } 9751b0e6146SDuncan Sands 9761b0e6146SDuncan Sands static int cxacru_find_firmware(struct cxacru_data *instance, 9771b0e6146SDuncan Sands char* phase, const struct firmware **fw_p) 9781b0e6146SDuncan Sands { 9790ec3c7e8SDuncan Sands struct usbatm_data *usbatm = instance->usbatm; 9800ec3c7e8SDuncan Sands struct device *dev = &usbatm->usb_intf->dev; 9811b0e6146SDuncan Sands char buf[16]; 9821b0e6146SDuncan Sands 9831b0e6146SDuncan Sands sprintf(buf, "cxacru-%s.bin", phase); 9841b0e6146SDuncan Sands dbg("cxacru_find_firmware: looking for %s", buf); 9851b0e6146SDuncan Sands 9861b0e6146SDuncan Sands if (request_firmware(fw_p, buf, dev)) { 9870ec3c7e8SDuncan Sands usb_dbg(usbatm, "no stage %s firmware found\n", phase); 9881b0e6146SDuncan Sands return -ENOENT; 9891b0e6146SDuncan Sands } 9901b0e6146SDuncan Sands 9910ec3c7e8SDuncan Sands usb_info(usbatm, "found firmware %s\n", buf); 9921b0e6146SDuncan Sands 9931b0e6146SDuncan Sands return 0; 9941b0e6146SDuncan Sands } 9951b0e6146SDuncan Sands 9961b0e6146SDuncan Sands static int cxacru_heavy_init(struct usbatm_data *usbatm_instance, 9971b0e6146SDuncan Sands struct usb_interface *usb_intf) 9981b0e6146SDuncan Sands { 9991b0e6146SDuncan Sands const struct firmware *fw, *bp, *cf; 10001b0e6146SDuncan Sands struct cxacru_data *instance = usbatm_instance->driver_data; 10011b0e6146SDuncan Sands 10021b0e6146SDuncan Sands int ret = cxacru_find_firmware(instance, "fw", &fw); 10031b0e6146SDuncan Sands if (ret) { 10040ec3c7e8SDuncan Sands usb_warn(usbatm_instance, "firmware (cxacru-fw.bin) unavailable (system misconfigured?)\n"); 10051b0e6146SDuncan Sands return ret; 10061b0e6146SDuncan Sands } 10071b0e6146SDuncan Sands 10081b0e6146SDuncan Sands if (instance->modem_type->boot_rom_patch) { 10091b0e6146SDuncan Sands ret = cxacru_find_firmware(instance, "bp", &bp); 10101b0e6146SDuncan Sands if (ret) { 10110ec3c7e8SDuncan Sands usb_warn(usbatm_instance, "boot ROM patch (cxacru-bp.bin) unavailable (system misconfigured?)\n"); 10121b0e6146SDuncan Sands release_firmware(fw); 10131b0e6146SDuncan Sands return ret; 10141b0e6146SDuncan Sands } 10151b0e6146SDuncan Sands } 10161b0e6146SDuncan Sands 10171b0e6146SDuncan Sands if (cxacru_find_firmware(instance, "cf", &cf)) /* optional */ 10181b0e6146SDuncan Sands cf = NULL; 10191b0e6146SDuncan Sands 10201b0e6146SDuncan Sands cxacru_upload_firmware(instance, fw, bp, cf); 10211b0e6146SDuncan Sands 10221b0e6146SDuncan Sands if (cf) 10231b0e6146SDuncan Sands release_firmware(cf); 10241b0e6146SDuncan Sands if (instance->modem_type->boot_rom_patch) 10251b0e6146SDuncan Sands release_firmware(bp); 10261b0e6146SDuncan Sands release_firmware(fw); 10271b0e6146SDuncan Sands 10281b0e6146SDuncan Sands ret = cxacru_card_status(instance); 10291b0e6146SDuncan Sands if (ret) 10301b0e6146SDuncan Sands dbg("modem initialisation failed"); 10311b0e6146SDuncan Sands else 10321b0e6146SDuncan Sands dbg("done setting up the modem"); 10331b0e6146SDuncan Sands 10341b0e6146SDuncan Sands return ret; 10351b0e6146SDuncan Sands } 10361b0e6146SDuncan Sands 10371b0e6146SDuncan Sands static int cxacru_bind(struct usbatm_data *usbatm_instance, 103835644b0cSDuncan Sands struct usb_interface *intf, const struct usb_device_id *id) 10391b0e6146SDuncan Sands { 10401b0e6146SDuncan Sands struct cxacru_data *instance; 10411b0e6146SDuncan Sands struct usb_device *usb_dev = interface_to_usbdev(intf); 10421b0e6146SDuncan Sands int ret; 10431b0e6146SDuncan Sands 10441b0e6146SDuncan Sands /* instance init */ 10459a734efeSDuncan Sands instance = kzalloc(sizeof(*instance), GFP_KERNEL); 10461b0e6146SDuncan Sands if (!instance) { 10471b0e6146SDuncan Sands dbg("cxacru_bind: no memory for instance data"); 10481b0e6146SDuncan Sands return -ENOMEM; 10491b0e6146SDuncan Sands } 10501b0e6146SDuncan Sands 10511b0e6146SDuncan Sands instance->usbatm = usbatm_instance; 10521b0e6146SDuncan Sands instance->modem_type = (struct cxacru_modem_type *) id->driver_info; 1053fa70fe44SSimon Arlott memset(instance->card_info, 0, sizeof(instance->card_info)); 10541b0e6146SDuncan Sands 10556a02c996SSimon Arlott mutex_init(&instance->poll_state_serialize); 10566a02c996SSimon Arlott instance->poll_state = CXPOLL_STOPPED; 10576a02c996SSimon Arlott instance->line_status = -1; 10586a02c996SSimon Arlott instance->adsl_status = -1; 10596a02c996SSimon Arlott 10606a02c996SSimon Arlott mutex_init(&instance->adsl_state_serialize); 10616a02c996SSimon Arlott 10621b0e6146SDuncan Sands instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL); 10631b0e6146SDuncan Sands if (!instance->rcv_buf) { 10641b0e6146SDuncan Sands dbg("cxacru_bind: no memory for rcv_buf"); 10651b0e6146SDuncan Sands ret = -ENOMEM; 10661b0e6146SDuncan Sands goto fail; 10671b0e6146SDuncan Sands } 10681b0e6146SDuncan Sands instance->snd_buf = (u8 *) __get_free_page(GFP_KERNEL); 10691b0e6146SDuncan Sands if (!instance->snd_buf) { 10701b0e6146SDuncan Sands dbg("cxacru_bind: no memory for snd_buf"); 10711b0e6146SDuncan Sands ret = -ENOMEM; 10721b0e6146SDuncan Sands goto fail; 10731b0e6146SDuncan Sands } 10741b0e6146SDuncan Sands instance->rcv_urb = usb_alloc_urb(0, GFP_KERNEL); 10751b0e6146SDuncan Sands if (!instance->rcv_urb) { 10761b0e6146SDuncan Sands dbg("cxacru_bind: no memory for rcv_urb"); 10771b0e6146SDuncan Sands ret = -ENOMEM; 10781b0e6146SDuncan Sands goto fail; 10791b0e6146SDuncan Sands } 10801b0e6146SDuncan Sands instance->snd_urb = usb_alloc_urb(0, GFP_KERNEL); 10811b0e6146SDuncan Sands if (!instance->snd_urb) { 10821b0e6146SDuncan Sands dbg("cxacru_bind: no memory for snd_urb"); 10831b0e6146SDuncan Sands ret = -ENOMEM; 10841b0e6146SDuncan Sands goto fail; 10851b0e6146SDuncan Sands } 10861b0e6146SDuncan Sands 10871b0e6146SDuncan Sands usb_fill_int_urb(instance->rcv_urb, 10881b0e6146SDuncan Sands usb_dev, usb_rcvintpipe(usb_dev, CXACRU_EP_CMD), 10891b0e6146SDuncan Sands instance->rcv_buf, PAGE_SIZE, 10901b0e6146SDuncan Sands cxacru_blocking_completion, &instance->rcv_done, 1); 10911b0e6146SDuncan Sands 10921b0e6146SDuncan Sands usb_fill_int_urb(instance->snd_urb, 10931b0e6146SDuncan Sands usb_dev, usb_sndintpipe(usb_dev, CXACRU_EP_CMD), 10941b0e6146SDuncan Sands instance->snd_buf, PAGE_SIZE, 10951b0e6146SDuncan Sands cxacru_blocking_completion, &instance->snd_done, 4); 10961b0e6146SDuncan Sands 1097ab3c81ffSArjan van de Ven mutex_init(&instance->cm_serialize); 10981b0e6146SDuncan Sands 1099c4028958SDavid Howells INIT_DELAYED_WORK(&instance->poll_work, cxacru_poll_status); 11001b0e6146SDuncan Sands 11011b0e6146SDuncan Sands usbatm_instance->driver_data = instance; 11021b0e6146SDuncan Sands 110335644b0cSDuncan Sands usbatm_instance->flags = (cxacru_card_status(instance) ? 0 : UDSL_SKIP_HEAVY_INIT); 11041b0e6146SDuncan Sands 11051b0e6146SDuncan Sands return 0; 11061b0e6146SDuncan Sands 11071b0e6146SDuncan Sands fail: 11081b0e6146SDuncan Sands free_page((unsigned long) instance->snd_buf); 11091b0e6146SDuncan Sands free_page((unsigned long) instance->rcv_buf); 11101b0e6146SDuncan Sands usb_free_urb(instance->snd_urb); 11111b0e6146SDuncan Sands usb_free_urb(instance->rcv_urb); 11121b0e6146SDuncan Sands kfree(instance); 11131b0e6146SDuncan Sands 11141b0e6146SDuncan Sands return ret; 11151b0e6146SDuncan Sands } 11161b0e6146SDuncan Sands 11171b0e6146SDuncan Sands static void cxacru_unbind(struct usbatm_data *usbatm_instance, 11181b0e6146SDuncan Sands struct usb_interface *intf) 11191b0e6146SDuncan Sands { 11201b0e6146SDuncan Sands struct cxacru_data *instance = usbatm_instance->driver_data; 11216a02c996SSimon Arlott int is_polling = 1; 11221b0e6146SDuncan Sands 11231b0e6146SDuncan Sands dbg("cxacru_unbind entered"); 11241b0e6146SDuncan Sands 11251b0e6146SDuncan Sands if (!instance) { 11261b0e6146SDuncan Sands dbg("cxacru_unbind: NULL instance!"); 11271b0e6146SDuncan Sands return; 11281b0e6146SDuncan Sands } 11291b0e6146SDuncan Sands 11306a02c996SSimon Arlott mutex_lock(&instance->poll_state_serialize); 11316a02c996SSimon Arlott BUG_ON(instance->poll_state == CXPOLL_SHUTDOWN); 11326a02c996SSimon Arlott 11336a02c996SSimon Arlott /* ensure that status polling continues unless 11346a02c996SSimon Arlott * it has already stopped */ 11356a02c996SSimon Arlott if (instance->poll_state == CXPOLL_STOPPED) 11366a02c996SSimon Arlott is_polling = 0; 11376a02c996SSimon Arlott 11386a02c996SSimon Arlott /* stop polling from being stopped or started */ 11396a02c996SSimon Arlott instance->poll_state = CXPOLL_SHUTDOWN; 11406a02c996SSimon Arlott mutex_unlock(&instance->poll_state_serialize); 11416a02c996SSimon Arlott 11426a02c996SSimon Arlott if (is_polling) 11436a02c996SSimon Arlott cancel_rearming_delayed_work(&instance->poll_work); 11441b0e6146SDuncan Sands 11451b0e6146SDuncan Sands usb_kill_urb(instance->snd_urb); 11461b0e6146SDuncan Sands usb_kill_urb(instance->rcv_urb); 11471b0e6146SDuncan Sands usb_free_urb(instance->snd_urb); 11481b0e6146SDuncan Sands usb_free_urb(instance->rcv_urb); 11491b0e6146SDuncan Sands 11501b0e6146SDuncan Sands free_page((unsigned long) instance->snd_buf); 11511b0e6146SDuncan Sands free_page((unsigned long) instance->rcv_buf); 1152fa70fe44SSimon Arlott 11531b0e6146SDuncan Sands kfree(instance); 11541b0e6146SDuncan Sands 11551b0e6146SDuncan Sands usbatm_instance->driver_data = NULL; 11561b0e6146SDuncan Sands } 11571b0e6146SDuncan Sands 11581b0e6146SDuncan Sands static const struct cxacru_modem_type cxacru_cafe = { 11591b0e6146SDuncan Sands .pll_f_clk = 0x02d874df, 11601b0e6146SDuncan Sands .pll_b_clk = 0x0196a51a, 11611b0e6146SDuncan Sands .boot_rom_patch = 1, 11621b0e6146SDuncan Sands }; 11631b0e6146SDuncan Sands 11641b0e6146SDuncan Sands static const struct cxacru_modem_type cxacru_cb00 = { 11651b0e6146SDuncan Sands .pll_f_clk = 0x5, 11661b0e6146SDuncan Sands .pll_b_clk = 0x3, 11671b0e6146SDuncan Sands .boot_rom_patch = 0, 11681b0e6146SDuncan Sands }; 11691b0e6146SDuncan Sands 11701b0e6146SDuncan Sands static const struct usb_device_id cxacru_usb_ids[] = { 11711b0e6146SDuncan Sands { /* V = Conexant P = ADSL modem (Euphrates project) */ 11721b0e6146SDuncan Sands USB_DEVICE(0x0572, 0xcafe), .driver_info = (unsigned long) &cxacru_cafe 11731b0e6146SDuncan Sands }, 11741b0e6146SDuncan Sands { /* V = Conexant P = ADSL modem (Hasbani project) */ 11751b0e6146SDuncan Sands USB_DEVICE(0x0572, 0xcb00), .driver_info = (unsigned long) &cxacru_cb00 11761b0e6146SDuncan Sands }, 11771b0e6146SDuncan Sands { /* V = Conexant P = ADSL modem */ 11781b0e6146SDuncan Sands USB_DEVICE(0x0572, 0xcb01), .driver_info = (unsigned long) &cxacru_cb00 11791b0e6146SDuncan Sands }, 11800ec3c7e8SDuncan Sands { /* V = Conexant P = ADSL modem (Well PTI-800) */ 11810ec3c7e8SDuncan Sands USB_DEVICE(0x0572, 0xcb02), .driver_info = (unsigned long) &cxacru_cb00 11820ec3c7e8SDuncan Sands }, 11831b0e6146SDuncan Sands { /* V = Conexant P = ADSL modem */ 11841b0e6146SDuncan Sands USB_DEVICE(0x0572, 0xcb06), .driver_info = (unsigned long) &cxacru_cb00 11851b0e6146SDuncan Sands }, 118644960af1SDuncan Sands { /* V = Conexant P = ADSL modem (ZTE ZXDSL 852) */ 118744960af1SDuncan Sands USB_DEVICE(0x0572, 0xcb07), .driver_info = (unsigned long) &cxacru_cb00 118844960af1SDuncan Sands }, 11891b0e6146SDuncan Sands { /* V = Olitec P = ADSL modem version 2 */ 11901b0e6146SDuncan Sands USB_DEVICE(0x08e3, 0x0100), .driver_info = (unsigned long) &cxacru_cafe 11911b0e6146SDuncan Sands }, 11921b0e6146SDuncan Sands { /* V = Olitec P = ADSL modem version 3 */ 11931b0e6146SDuncan Sands USB_DEVICE(0x08e3, 0x0102), .driver_info = (unsigned long) &cxacru_cb00 11941b0e6146SDuncan Sands }, 11951b0e6146SDuncan Sands { /* V = Trust/Amigo Technology Co. P = AMX-CA86U */ 11961b0e6146SDuncan Sands USB_DEVICE(0x0eb0, 0x3457), .driver_info = (unsigned long) &cxacru_cafe 11971b0e6146SDuncan Sands }, 11981b0e6146SDuncan Sands { /* V = Zoom P = 5510 */ 11991b0e6146SDuncan Sands USB_DEVICE(0x1803, 0x5510), .driver_info = (unsigned long) &cxacru_cb00 12001b0e6146SDuncan Sands }, 12011b0e6146SDuncan Sands { /* V = Draytek P = Vigor 318 */ 12021b0e6146SDuncan Sands USB_DEVICE(0x0675, 0x0200), .driver_info = (unsigned long) &cxacru_cb00 12031b0e6146SDuncan Sands }, 12041b0e6146SDuncan Sands { /* V = Zyxel P = 630-C1 aka OMNI ADSL USB (Annex A) */ 12051b0e6146SDuncan Sands USB_DEVICE(0x0586, 0x330a), .driver_info = (unsigned long) &cxacru_cb00 12061b0e6146SDuncan Sands }, 12071b0e6146SDuncan Sands { /* V = Zyxel P = 630-C3 aka OMNI ADSL USB (Annex B) */ 12081b0e6146SDuncan Sands USB_DEVICE(0x0586, 0x330b), .driver_info = (unsigned long) &cxacru_cb00 12091b0e6146SDuncan Sands }, 12101b0e6146SDuncan Sands { /* V = Aethra P = Starmodem UM1020 */ 12111b0e6146SDuncan Sands USB_DEVICE(0x0659, 0x0020), .driver_info = (unsigned long) &cxacru_cb00 12121b0e6146SDuncan Sands }, 12131b0e6146SDuncan Sands { /* V = Aztech Systems P = ? AKA Pirelli AUA-010 */ 12141b0e6146SDuncan Sands USB_DEVICE(0x0509, 0x0812), .driver_info = (unsigned long) &cxacru_cb00 12151b0e6146SDuncan Sands }, 12161b0e6146SDuncan Sands { /* V = Netopia P = Cayman 3341(Annex A)/3351(Annex B) */ 12171b0e6146SDuncan Sands USB_DEVICE(0x100d, 0xcb01), .driver_info = (unsigned long) &cxacru_cb00 12181b0e6146SDuncan Sands }, 12191b0e6146SDuncan Sands { /* V = Netopia P = Cayman 3342(Annex A)/3352(Annex B) */ 12201b0e6146SDuncan Sands USB_DEVICE(0x100d, 0x3342), .driver_info = (unsigned long) &cxacru_cb00 12211b0e6146SDuncan Sands }, 12221b0e6146SDuncan Sands {} 12231b0e6146SDuncan Sands }; 12241b0e6146SDuncan Sands 12251b0e6146SDuncan Sands MODULE_DEVICE_TABLE(usb, cxacru_usb_ids); 12261b0e6146SDuncan Sands 12271b0e6146SDuncan Sands static struct usbatm_driver cxacru_driver = { 12281b0e6146SDuncan Sands .driver_name = cxacru_driver_name, 12291b0e6146SDuncan Sands .bind = cxacru_bind, 12301b0e6146SDuncan Sands .heavy_init = cxacru_heavy_init, 12311b0e6146SDuncan Sands .unbind = cxacru_unbind, 12321b0e6146SDuncan Sands .atm_start = cxacru_atm_start, 1233da1f82b5SSimon Arlott .atm_stop = cxacru_remove_device_files, 123480aae7a1SDuncan Sands .bulk_in = CXACRU_EP_DATA, 123580aae7a1SDuncan Sands .bulk_out = CXACRU_EP_DATA, 12361b0e6146SDuncan Sands .rx_padding = 3, 12371b0e6146SDuncan Sands .tx_padding = 11, 12381b0e6146SDuncan Sands }; 12391b0e6146SDuncan Sands 12401b0e6146SDuncan Sands static int cxacru_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) 12411b0e6146SDuncan Sands { 12421b0e6146SDuncan Sands return usbatm_usb_probe(intf, id, &cxacru_driver); 12431b0e6146SDuncan Sands } 12441b0e6146SDuncan Sands 12451b0e6146SDuncan Sands static struct usb_driver cxacru_usb_driver = { 12461b0e6146SDuncan Sands .name = cxacru_driver_name, 12471b0e6146SDuncan Sands .probe = cxacru_usb_probe, 12481b0e6146SDuncan Sands .disconnect = usbatm_usb_disconnect, 12491b0e6146SDuncan Sands .id_table = cxacru_usb_ids 12501b0e6146SDuncan Sands }; 12511b0e6146SDuncan Sands 12521b0e6146SDuncan Sands static int __init cxacru_init(void) 12531b0e6146SDuncan Sands { 12541b0e6146SDuncan Sands return usb_register(&cxacru_usb_driver); 12551b0e6146SDuncan Sands } 12561b0e6146SDuncan Sands 12571b0e6146SDuncan Sands static void __exit cxacru_cleanup(void) 12581b0e6146SDuncan Sands { 12591b0e6146SDuncan Sands usb_deregister(&cxacru_usb_driver); 12601b0e6146SDuncan Sands } 12611b0e6146SDuncan Sands 12621b0e6146SDuncan Sands module_init(cxacru_init); 12631b0e6146SDuncan Sands module_exit(cxacru_cleanup); 12641b0e6146SDuncan Sands 12651b0e6146SDuncan Sands MODULE_AUTHOR(DRIVER_AUTHOR); 12661b0e6146SDuncan Sands MODULE_DESCRIPTION(DRIVER_DESC); 12671b0e6146SDuncan Sands MODULE_LICENSE("GPL"); 12681b0e6146SDuncan Sands MODULE_VERSION(DRIVER_VERSION); 1269