| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /****************************************************************************** | 
 | 2 |  *  speedtch.c  -  Alcatel SpeedTouch USB xDSL modem driver | 
 | 3 |  * | 
 | 4 |  *  Copyright (C) 2001, Alcatel | 
 | 5 |  *  Copyright (C) 2003, Duncan Sands | 
 | 6 |  *  Copyright (C) 2004, David Woodhouse | 
 | 7 |  * | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 8 |  *  Based on "modem_run.c", copyright (C) 2001, Benoit Papillault | 
 | 9 |  * | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 10 |  *  This program is free software; you can redistribute it and/or modify it | 
 | 11 |  *  under the terms of the GNU General Public License as published by the Free | 
 | 12 |  *  Software Foundation; either version 2 of the License, or (at your option) | 
 | 13 |  *  any later version. | 
 | 14 |  * | 
 | 15 |  *  This program is distributed in the hope that it will be useful, but WITHOUT | 
 | 16 |  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | 
 | 17 |  *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for | 
 | 18 |  *  more details. | 
 | 19 |  * | 
 | 20 |  *  You should have received a copy of the GNU General Public License along with | 
 | 21 |  *  this program; if not, write to the Free Software Foundation, Inc., 59 | 
 | 22 |  *  Temple Place - Suite 330, Boston, MA  02111-1307, USA. | 
 | 23 |  * | 
 | 24 |  ******************************************************************************/ | 
 | 25 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 26 | #include <asm/page.h> | 
 | 27 | #include <linux/device.h> | 
 | 28 | #include <linux/errno.h> | 
 | 29 | #include <linux/firmware.h> | 
 | 30 | #include <linux/gfp.h> | 
 | 31 | #include <linux/init.h> | 
 | 32 | #include <linux/kernel.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 33 | #include <linux/module.h> | 
 | 34 | #include <linux/moduleparam.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 35 | #include <linux/slab.h> | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 36 | #include <linux/stat.h> | 
 | 37 | #include <linux/timer.h> | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 38 | #include <linux/types.h> | 
| David Brownell | 5f84813 | 2006-12-16 15:34:53 -0800 | [diff] [blame] | 39 | #include <linux/usb/ch9.h> | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 40 | #include <linux/workqueue.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 41 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 42 | #include "usbatm.h" | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 43 |  | 
 | 44 | #define DRIVER_AUTHOR	"Johan Verrept, Duncan Sands <duncan.sands@free.fr>" | 
| Duncan Sands | 9b0e54a | 2006-01-13 11:08:05 +0100 | [diff] [blame] | 45 | #define DRIVER_VERSION	"1.10" | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 46 | #define DRIVER_DESC	"Alcatel SpeedTouch USB driver version " DRIVER_VERSION | 
 | 47 |  | 
 | 48 | static const char speedtch_driver_name[] = "speedtch"; | 
 | 49 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 50 | #define CTRL_TIMEOUT 2000	/* milliseconds */ | 
 | 51 | #define DATA_TIMEOUT 2000	/* milliseconds */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 52 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 53 | #define OFFSET_7	0		/* size 1 */ | 
 | 54 | #define OFFSET_b	1		/* size 8 */ | 
 | 55 | #define OFFSET_d	9		/* size 4 */ | 
 | 56 | #define OFFSET_e	13		/* size 1 */ | 
 | 57 | #define OFFSET_f	14		/* size 1 */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 58 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 59 | #define SIZE_7		1 | 
 | 60 | #define SIZE_b		8 | 
 | 61 | #define SIZE_d		4 | 
 | 62 | #define SIZE_e		1 | 
 | 63 | #define SIZE_f		1 | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 64 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 65 | #define MIN_POLL_DELAY		5000	/* milliseconds */ | 
 | 66 | #define MAX_POLL_DELAY		60000	/* milliseconds */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 67 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 68 | #define RESUBMIT_DELAY		1000	/* milliseconds */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 69 |  | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 70 | #define DEFAULT_BULK_ALTSETTING	1 | 
| Duncan Sands | 67c752b | 2006-04-28 18:44:06 +0200 | [diff] [blame] | 71 | #define DEFAULT_ISOC_ALTSETTING	3 | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 72 | #define DEFAULT_DL_512_FIRST	0 | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 73 | #define DEFAULT_ENABLE_ISOC	0 | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 74 | #define DEFAULT_SW_BUFFERING	0 | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 75 |  | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 76 | static unsigned int altsetting = 0; /* zero means: use the default */ | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 77 | static int dl_512_first = DEFAULT_DL_512_FIRST; | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 78 | static int enable_isoc = DEFAULT_ENABLE_ISOC; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 79 | static int sw_buffering = DEFAULT_SW_BUFFERING; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 80 |  | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 81 | #define DEFAULT_B_MAX_DSL	8128 | 
 | 82 | #define DEFAULT_MODEM_MODE	11 | 
 | 83 | #define MODEM_OPTION_LENGTH	16 | 
 | 84 | static const unsigned char DEFAULT_MODEM_OPTION[MODEM_OPTION_LENGTH] = { | 
 | 85 | 	0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | 
 | 86 | }; | 
 | 87 |  | 
 | 88 | static unsigned int BMaxDSL = DEFAULT_B_MAX_DSL; | 
 | 89 | static unsigned char ModemMode = DEFAULT_MODEM_MODE; | 
 | 90 | static unsigned char ModemOption[MODEM_OPTION_LENGTH]; | 
 | 91 | static int num_ModemOption; | 
 | 92 |  | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 93 | module_param(altsetting, uint, S_IRUGO | S_IWUSR); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 94 | MODULE_PARM_DESC(altsetting, | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 95 | 		"Alternative setting for data interface (bulk_default: " | 
 | 96 | 		__MODULE_STRING(DEFAULT_BULK_ALTSETTING) "; isoc_default: " | 
 | 97 | 		__MODULE_STRING(DEFAULT_ISOC_ALTSETTING) ")"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 98 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 99 | module_param(dl_512_first, bool, S_IRUGO | S_IWUSR); | 
 | 100 | MODULE_PARM_DESC(dl_512_first, | 
 | 101 | 		 "Read 512 bytes before sending firmware (default: " | 
 | 102 | 		 __MODULE_STRING(DEFAULT_DL_512_FIRST) ")"); | 
 | 103 |  | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 104 | module_param(enable_isoc, bool, S_IRUGO | S_IWUSR); | 
 | 105 | MODULE_PARM_DESC(enable_isoc, | 
 | 106 | 		"Use isochronous transfers if available (default: " | 
 | 107 | 		__MODULE_STRING(DEFAULT_ENABLE_ISOC) ")"); | 
 | 108 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 109 | module_param(sw_buffering, bool, S_IRUGO | S_IWUSR); | 
 | 110 | MODULE_PARM_DESC(sw_buffering, | 
 | 111 | 		 "Enable software buffering (default: " | 
 | 112 | 		 __MODULE_STRING(DEFAULT_SW_BUFFERING) ")"); | 
 | 113 |  | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 114 | module_param(BMaxDSL, uint, S_IRUGO | S_IWUSR); | 
 | 115 | MODULE_PARM_DESC(BMaxDSL, | 
 | 116 | 		"default: " __MODULE_STRING(DEFAULT_B_MAX_DSL)); | 
 | 117 |  | 
 | 118 | module_param(ModemMode, byte, S_IRUGO | S_IWUSR); | 
 | 119 | MODULE_PARM_DESC(ModemMode, | 
 | 120 | 		"default: " __MODULE_STRING(DEFAULT_MODEM_MODE)); | 
 | 121 |  | 
 | 122 | module_param_array(ModemOption, byte, &num_ModemOption, S_IRUGO); | 
 | 123 | MODULE_PARM_DESC(ModemOption, "default: 0x10,0x00,0x00,0x00,0x20"); | 
 | 124 |  | 
| Duncan Sands | 6f74947 | 2006-01-13 10:52:38 +0100 | [diff] [blame] | 125 | #define INTERFACE_DATA		1 | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 126 | #define ENDPOINT_INT		0x81 | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 127 | #define ENDPOINT_BULK_DATA	0x07 | 
 | 128 | #define ENDPOINT_ISOC_DATA	0x07 | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 129 | #define ENDPOINT_FIRMWARE	0x05 | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 130 |  | 
 | 131 | #define hex2int(c) ( (c >= '0') && (c <= '9') ? (c - '0') : ((c & 0xf) + 9) ) | 
 | 132 |  | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 133 | struct speedtch_params { | 
 | 134 | 	unsigned int altsetting; | 
 | 135 | 	unsigned int BMaxDSL; | 
 | 136 | 	unsigned char ModemMode; | 
 | 137 | 	unsigned char ModemOption[MODEM_OPTION_LENGTH]; | 
 | 138 | }; | 
 | 139 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 140 | struct speedtch_instance_data { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 141 | 	struct usbatm_data *usbatm; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 142 |  | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 143 | 	struct speedtch_params params; /* set in probe, constant afterwards */ | 
| Duncan Sands | 6f74947 | 2006-01-13 10:52:38 +0100 | [diff] [blame] | 144 |  | 
| David Howells | c402895 | 2006-11-22 14:57:56 +0000 | [diff] [blame] | 145 | 	struct delayed_work status_checker; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 146 |  | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 147 | 	unsigned char last_status; | 
 | 148 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 149 | 	int poll_delay; /* milliseconds */ | 
 | 150 |  | 
 | 151 | 	struct timer_list resubmit_timer; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 152 | 	struct urb *int_urb; | 
 | 153 | 	unsigned char int_data[16]; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 154 |  | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 155 | 	unsigned char scratch_buffer[16]; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 156 | }; | 
 | 157 |  | 
 | 158 | /*************** | 
 | 159 | **  firmware  ** | 
 | 160 | ***************/ | 
 | 161 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 162 | static void speedtch_set_swbuff(struct speedtch_instance_data *instance, int state) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 163 | { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 164 | 	struct usbatm_data *usbatm = instance->usbatm; | 
 | 165 | 	struct usb_device *usb_dev = usbatm->usb_dev; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 166 | 	int ret; | 
 | 167 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 168 | 	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), | 
 | 169 | 			      0x32, 0x40, state ? 0x01 : 0x00, 0x00, NULL, 0, CTRL_TIMEOUT); | 
 | 170 | 	if (ret < 0) | 
 | 171 | 		usb_warn(usbatm, | 
 | 172 | 			 "%sabling SW buffering: usb_control_msg returned %d\n", | 
 | 173 | 			 state ? "En" : "Dis", ret); | 
 | 174 | 	else | 
 | 175 | 		dbg("speedtch_set_swbuff: %sbled SW buffering", state ? "En" : "Dis"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 176 | } | 
 | 177 |  | 
 | 178 | static void speedtch_test_sequence(struct speedtch_instance_data *instance) | 
 | 179 | { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 180 | 	struct usbatm_data *usbatm = instance->usbatm; | 
 | 181 | 	struct usb_device *usb_dev = usbatm->usb_dev; | 
 | 182 | 	unsigned char *buf = instance->scratch_buffer; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 183 | 	int ret; | 
 | 184 |  | 
 | 185 | 	/* URB 147 */ | 
 | 186 | 	buf[0] = 0x1c; | 
 | 187 | 	buf[1] = 0x50; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 188 | 	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), | 
 | 189 | 			      0x01, 0x40, 0x0b, 0x00, buf, 2, CTRL_TIMEOUT); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 190 | 	if (ret < 0) | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 191 | 		usb_warn(usbatm, "%s failed on URB147: %d\n", __func__, ret); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 192 |  | 
 | 193 | 	/* URB 148 */ | 
 | 194 | 	buf[0] = 0x32; | 
 | 195 | 	buf[1] = 0x00; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 196 | 	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), | 
 | 197 | 			      0x01, 0x40, 0x02, 0x00, buf, 2, CTRL_TIMEOUT); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 198 | 	if (ret < 0) | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 199 | 		usb_warn(usbatm, "%s failed on URB148: %d\n", __func__, ret); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 200 |  | 
 | 201 | 	/* URB 149 */ | 
 | 202 | 	buf[0] = 0x01; | 
 | 203 | 	buf[1] = 0x00; | 
 | 204 | 	buf[2] = 0x01; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 205 | 	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), | 
 | 206 | 			      0x01, 0x40, 0x03, 0x00, buf, 3, CTRL_TIMEOUT); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 207 | 	if (ret < 0) | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 208 | 		usb_warn(usbatm, "%s failed on URB149: %d\n", __func__, ret); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 209 |  | 
 | 210 | 	/* URB 150 */ | 
 | 211 | 	buf[0] = 0x01; | 
 | 212 | 	buf[1] = 0x00; | 
 | 213 | 	buf[2] = 0x01; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 214 | 	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), | 
 | 215 | 			      0x01, 0x40, 0x04, 0x00, buf, 3, CTRL_TIMEOUT); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 216 | 	if (ret < 0) | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 217 | 		usb_warn(usbatm, "%s failed on URB150: %d\n", __func__, ret); | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 218 |  | 
 | 219 | 	/* Extra initialisation in recent drivers - gives higher speeds */ | 
 | 220 |  | 
 | 221 | 	/* URBext1 */ | 
 | 222 | 	buf[0] = instance->params.ModemMode; | 
 | 223 | 	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), | 
 | 224 | 			      0x01, 0x40, 0x11, 0x00, buf, 1, CTRL_TIMEOUT); | 
 | 225 | 	if (ret < 0) | 
 | 226 | 		usb_warn(usbatm, "%s failed on URBext1: %d\n", __func__, ret); | 
 | 227 |  | 
 | 228 | 	/* URBext2 */ | 
 | 229 | 	/* This seems to be the one which actually triggers the higher sync | 
 | 230 | 	   rate -- it does require the new firmware too, although it works OK | 
 | 231 | 	   with older firmware */ | 
 | 232 | 	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), | 
 | 233 | 			      0x01, 0x40, 0x14, 0x00, | 
 | 234 | 			      instance->params.ModemOption, | 
 | 235 | 			      MODEM_OPTION_LENGTH, CTRL_TIMEOUT); | 
 | 236 | 	if (ret < 0) | 
 | 237 | 		usb_warn(usbatm, "%s failed on URBext2: %d\n", __func__, ret); | 
 | 238 |  | 
 | 239 | 	/* URBext3 */ | 
 | 240 | 	buf[0] = instance->params.BMaxDSL & 0xff; | 
 | 241 | 	buf[1] = instance->params.BMaxDSL >> 8; | 
 | 242 | 	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), | 
 | 243 | 			      0x01, 0x40, 0x12, 0x00, buf, 2, CTRL_TIMEOUT); | 
 | 244 | 	if (ret < 0) | 
 | 245 | 		usb_warn(usbatm, "%s failed on URBext3: %d\n", __func__, ret); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 246 | } | 
 | 247 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 248 | static int speedtch_upload_firmware(struct speedtch_instance_data *instance, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 249 | 				     const struct firmware *fw1, | 
 | 250 | 				     const struct firmware *fw2) | 
 | 251 | { | 
 | 252 | 	unsigned char *buffer; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 253 | 	struct usbatm_data *usbatm = instance->usbatm; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 254 | 	struct usb_interface *intf; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 255 | 	struct usb_device *usb_dev = usbatm->usb_dev; | 
 | 256 | 	int actual_length; | 
 | 257 | 	int ret = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 258 | 	int offset; | 
 | 259 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 260 | 	usb_dbg(usbatm, "%s entered\n", __func__); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 261 |  | 
 | 262 | 	if (!(buffer = (unsigned char *)__get_free_page(GFP_KERNEL))) { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 263 | 		ret = -ENOMEM; | 
 | 264 | 		usb_dbg(usbatm, "%s: no memory for buffer!\n", __func__); | 
 | 265 | 		goto out; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 266 | 	} | 
 | 267 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 268 | 	if (!(intf = usb_ifnum_to_if(usb_dev, 2))) { | 
 | 269 | 		ret = -ENODEV; | 
 | 270 | 		usb_dbg(usbatm, "%s: interface not found!\n", __func__); | 
 | 271 | 		goto out_free; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 272 | 	} | 
 | 273 |  | 
 | 274 | 	/* URB 7 */ | 
 | 275 | 	if (dl_512_first) {	/* some modems need a read before writing the firmware */ | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 276 | 		ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, ENDPOINT_FIRMWARE), | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 277 | 				   buffer, 0x200, &actual_length, 2000); | 
 | 278 |  | 
 | 279 | 		if (ret < 0 && ret != -ETIMEDOUT) | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 280 | 			usb_warn(usbatm, "%s: read BLOCK0 from modem failed (%d)!\n", __func__, ret); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 281 | 		else | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 282 | 			usb_dbg(usbatm, "%s: BLOCK0 downloaded (%d bytes)\n", __func__, ret); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 283 | 	} | 
 | 284 |  | 
 | 285 | 	/* URB 8 : both leds are static green */ | 
 | 286 | 	for (offset = 0; offset < fw1->size; offset += PAGE_SIZE) { | 
 | 287 | 		int thislen = min_t(int, PAGE_SIZE, fw1->size - offset); | 
 | 288 | 		memcpy(buffer, fw1->data + offset, thislen); | 
 | 289 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 290 | 		ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, ENDPOINT_FIRMWARE), | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 291 | 				   buffer, thislen, &actual_length, DATA_TIMEOUT); | 
 | 292 |  | 
 | 293 | 		if (ret < 0) { | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 294 | 			usb_err(usbatm, "%s: write BLOCK1 to modem failed (%d)!\n", __func__, ret); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 295 | 			goto out_free; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 296 | 		} | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 297 | 		usb_dbg(usbatm, "%s: BLOCK1 uploaded (%zu bytes)\n", __func__, fw1->size); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 298 | 	} | 
 | 299 |  | 
 | 300 | 	/* USB led blinking green, ADSL led off */ | 
 | 301 |  | 
 | 302 | 	/* URB 11 */ | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 303 | 	ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, ENDPOINT_FIRMWARE), | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 304 | 			   buffer, 0x200, &actual_length, DATA_TIMEOUT); | 
 | 305 |  | 
 | 306 | 	if (ret < 0) { | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 307 | 		usb_err(usbatm, "%s: read BLOCK2 from modem failed (%d)!\n", __func__, ret); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 308 | 		goto out_free; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 309 | 	} | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 310 | 	usb_dbg(usbatm, "%s: BLOCK2 downloaded (%d bytes)\n", __func__, actual_length); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 311 |  | 
 | 312 | 	/* URBs 12 to 139 - USB led blinking green, ADSL led off */ | 
 | 313 | 	for (offset = 0; offset < fw2->size; offset += PAGE_SIZE) { | 
 | 314 | 		int thislen = min_t(int, PAGE_SIZE, fw2->size - offset); | 
 | 315 | 		memcpy(buffer, fw2->data + offset, thislen); | 
 | 316 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 317 | 		ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, ENDPOINT_FIRMWARE), | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 318 | 				   buffer, thislen, &actual_length, DATA_TIMEOUT); | 
 | 319 |  | 
 | 320 | 		if (ret < 0) { | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 321 | 			usb_err(usbatm, "%s: write BLOCK3 to modem failed (%d)!\n", __func__, ret); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 322 | 			goto out_free; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 323 | 		} | 
 | 324 | 	} | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 325 | 	usb_dbg(usbatm, "%s: BLOCK3 uploaded (%zu bytes)\n", __func__, fw2->size); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 326 |  | 
 | 327 | 	/* USB led static green, ADSL led static red */ | 
 | 328 |  | 
 | 329 | 	/* URB 142 */ | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 330 | 	ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, ENDPOINT_FIRMWARE), | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 331 | 			   buffer, 0x200, &actual_length, DATA_TIMEOUT); | 
 | 332 |  | 
 | 333 | 	if (ret < 0) { | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 334 | 		usb_err(usbatm, "%s: read BLOCK4 from modem failed (%d)!\n", __func__, ret); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 335 | 		goto out_free; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 336 | 	} | 
 | 337 |  | 
 | 338 | 	/* success */ | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 339 | 	usb_dbg(usbatm, "%s: BLOCK4 downloaded (%d bytes)\n", __func__, actual_length); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 340 |  | 
 | 341 | 	/* Delay to allow firmware to start up. We can do this here | 
 | 342 | 	   because we're in our own kernel thread anyway. */ | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 343 | 	msleep_interruptible(1000); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 344 |  | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 345 | 	if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, instance->params.altsetting)) < 0) { | 
 | 346 | 		usb_err(usbatm, "%s: setting interface to %d failed (%d)!\n", __func__, instance->params.altsetting, ret); | 
| Duncan Sands | 6f74947 | 2006-01-13 10:52:38 +0100 | [diff] [blame] | 347 | 		goto out_free; | 
 | 348 | 	} | 
 | 349 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 350 | 	/* Enable software buffering, if requested */ | 
 | 351 | 	if (sw_buffering) | 
 | 352 | 		speedtch_set_swbuff(instance, 1); | 
 | 353 |  | 
 | 354 | 	/* Magic spell; don't ask us what this does */ | 
 | 355 | 	speedtch_test_sequence(instance); | 
 | 356 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 357 | 	ret = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 358 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 359 | out_free: | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 360 | 	free_page((unsigned long)buffer); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 361 | out: | 
 | 362 | 	return ret; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 363 | } | 
 | 364 |  | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 365 | static int speedtch_find_firmware(struct usbatm_data *usbatm, struct usb_interface *intf, | 
 | 366 | 				  int phase, const struct firmware **fw_p) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 367 | { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 368 | 	struct device *dev = &intf->dev; | 
 | 369 | 	const u16 bcdDevice = le16_to_cpu(interface_to_usbdev(intf)->descriptor.bcdDevice); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 370 | 	const u8 major_revision = bcdDevice >> 8; | 
 | 371 | 	const u8 minor_revision = bcdDevice & 0xff; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 372 | 	char buf[24]; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 373 |  | 
 | 374 | 	sprintf(buf, "speedtch-%d.bin.%x.%02x", phase, major_revision, minor_revision); | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 375 | 	usb_dbg(usbatm, "%s: looking for %s\n", __func__, buf); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 376 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 377 | 	if (request_firmware(fw_p, buf, dev)) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 378 | 		sprintf(buf, "speedtch-%d.bin.%x", phase, major_revision); | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 379 | 		usb_dbg(usbatm, "%s: looking for %s\n", __func__, buf); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 380 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 381 | 		if (request_firmware(fw_p, buf, dev)) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 382 | 			sprintf(buf, "speedtch-%d.bin", phase); | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 383 | 			usb_dbg(usbatm, "%s: looking for %s\n", __func__, buf); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 384 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 385 | 			if (request_firmware(fw_p, buf, dev)) { | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 386 | 				usb_err(usbatm, "%s: no stage %d firmware found!\n", __func__, phase); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 387 | 				return -ENOENT; | 
 | 388 | 			} | 
 | 389 | 		} | 
 | 390 | 	} | 
 | 391 |  | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 392 | 	usb_info(usbatm, "found stage %d firmware %s\n", phase, buf); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 393 |  | 
 | 394 | 	return 0; | 
 | 395 | } | 
 | 396 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 397 | static int speedtch_heavy_init(struct usbatm_data *usbatm, struct usb_interface *intf) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 398 | { | 
 | 399 | 	const struct firmware *fw1, *fw2; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 400 | 	struct speedtch_instance_data *instance = usbatm->driver_data; | 
 | 401 | 	int ret; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 402 |  | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 403 | 	if ((ret = speedtch_find_firmware(usbatm, intf, 1, &fw1)) < 0) | 
 | 404 | 		return ret; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 405 |  | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 406 | 	if ((ret = speedtch_find_firmware(usbatm, intf, 2, &fw2)) < 0) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 407 | 		release_firmware(fw1); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 408 | 		return ret; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 409 | 	} | 
 | 410 |  | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 411 | 	if ((ret = speedtch_upload_firmware(instance, fw1, fw2)) < 0) | 
 | 412 | 		usb_err(usbatm, "%s: firmware upload failed (%d)!\n", __func__, ret); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 413 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 414 | 	release_firmware(fw2); | 
 | 415 | 	release_firmware(fw1); | 
 | 416 |  | 
 | 417 | 	return ret; | 
 | 418 | } | 
 | 419 |  | 
 | 420 |  | 
 | 421 | /********** | 
 | 422 | **  ATM  ** | 
 | 423 | **********/ | 
 | 424 |  | 
 | 425 | static int speedtch_read_status(struct speedtch_instance_data *instance) | 
 | 426 | { | 
 | 427 | 	struct usbatm_data *usbatm = instance->usbatm; | 
 | 428 | 	struct usb_device *usb_dev = usbatm->usb_dev; | 
 | 429 | 	unsigned char *buf = instance->scratch_buffer; | 
 | 430 | 	int ret; | 
 | 431 |  | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 432 | 	memset(buf, 0, 16); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 433 |  | 
 | 434 | 	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), | 
 | 435 | 			      0x12, 0xc0, 0x07, 0x00, buf + OFFSET_7, SIZE_7, | 
 | 436 | 			      CTRL_TIMEOUT); | 
 | 437 | 	if (ret < 0) { | 
 | 438 | 		atm_dbg(usbatm, "%s: MSG 7 failed\n", __func__); | 
 | 439 | 		return ret; | 
 | 440 | 	} | 
 | 441 |  | 
 | 442 | 	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), | 
 | 443 | 			      0x12, 0xc0, 0x0b, 0x00, buf + OFFSET_b, SIZE_b, | 
 | 444 | 			      CTRL_TIMEOUT); | 
 | 445 | 	if (ret < 0) { | 
 | 446 | 		atm_dbg(usbatm, "%s: MSG B failed\n", __func__); | 
 | 447 | 		return ret; | 
 | 448 | 	} | 
 | 449 |  | 
 | 450 | 	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), | 
 | 451 | 			      0x12, 0xc0, 0x0d, 0x00, buf + OFFSET_d, SIZE_d, | 
 | 452 | 			      CTRL_TIMEOUT); | 
 | 453 | 	if (ret < 0) { | 
 | 454 | 		atm_dbg(usbatm, "%s: MSG D failed\n", __func__); | 
 | 455 | 		return ret; | 
 | 456 | 	} | 
 | 457 |  | 
 | 458 | 	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), | 
 | 459 | 			      0x01, 0xc0, 0x0e, 0x00, buf + OFFSET_e, SIZE_e, | 
 | 460 | 			      CTRL_TIMEOUT); | 
 | 461 | 	if (ret < 0) { | 
 | 462 | 		atm_dbg(usbatm, "%s: MSG E failed\n", __func__); | 
 | 463 | 		return ret; | 
 | 464 | 	} | 
 | 465 |  | 
 | 466 | 	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), | 
 | 467 | 			      0x01, 0xc0, 0x0f, 0x00, buf + OFFSET_f, SIZE_f, | 
 | 468 | 			      CTRL_TIMEOUT); | 
 | 469 | 	if (ret < 0) { | 
 | 470 | 		atm_dbg(usbatm, "%s: MSG F failed\n", __func__); | 
 | 471 | 		return ret; | 
 | 472 | 	} | 
 | 473 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 474 | 	return 0; | 
 | 475 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 476 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 477 | static int speedtch_start_synchro(struct speedtch_instance_data *instance) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 478 | { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 479 | 	struct usbatm_data *usbatm = instance->usbatm; | 
 | 480 | 	struct usb_device *usb_dev = usbatm->usb_dev; | 
 | 481 | 	unsigned char *buf = instance->scratch_buffer; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 482 | 	int ret; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 483 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 484 | 	atm_dbg(usbatm, "%s entered\n", __func__); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 485 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 486 | 	memset(buf, 0, 2); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 487 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 488 | 	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), | 
 | 489 | 			      0x12, 0xc0, 0x04, 0x00, | 
 | 490 | 			      buf, 2, CTRL_TIMEOUT); | 
 | 491 |  | 
 | 492 | 	if (ret < 0) | 
 | 493 | 		atm_warn(usbatm, "failed to start ADSL synchronisation: %d\n", ret); | 
 | 494 | 	else | 
 | 495 | 		atm_dbg(usbatm, "%s: modem prodded. %d bytes returned: %02x %02x\n", | 
 | 496 | 			__func__, ret, buf[0], buf[1]); | 
 | 497 |  | 
 | 498 | 	return ret; | 
 | 499 | } | 
 | 500 |  | 
| David Howells | c402895 | 2006-11-22 14:57:56 +0000 | [diff] [blame] | 501 | static void speedtch_check_status(struct work_struct *work) | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 502 | { | 
| David Howells | c402895 | 2006-11-22 14:57:56 +0000 | [diff] [blame] | 503 | 	struct speedtch_instance_data *instance = | 
 | 504 | 		container_of(work, struct speedtch_instance_data, | 
 | 505 | 			     status_checker.work); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 506 | 	struct usbatm_data *usbatm = instance->usbatm; | 
 | 507 | 	struct atm_dev *atm_dev = usbatm->atm_dev; | 
 | 508 | 	unsigned char *buf = instance->scratch_buffer; | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 509 | 	int down_speed, up_speed, ret; | 
 | 510 | 	unsigned char status; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 511 |  | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 512 | #ifdef VERBOSE_DEBUG | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 513 | 	atm_dbg(usbatm, "%s entered\n", __func__); | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 514 | #endif | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 515 |  | 
 | 516 | 	ret = speedtch_read_status(instance); | 
 | 517 | 	if (ret < 0) { | 
 | 518 | 		atm_warn(usbatm, "error %d fetching device status\n", ret); | 
| Duncan Sands | cd5c08f | 2005-06-23 09:23:10 +0200 | [diff] [blame] | 519 | 		instance->poll_delay = min(2 * instance->poll_delay, MAX_POLL_DELAY); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 520 | 		return; | 
 | 521 | 	} | 
 | 522 |  | 
| Duncan Sands | cd5c08f | 2005-06-23 09:23:10 +0200 | [diff] [blame] | 523 | 	instance->poll_delay = max(instance->poll_delay / 2, MIN_POLL_DELAY); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 524 |  | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 525 | 	status = buf[OFFSET_7]; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 526 |  | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 527 | 	if ((status != instance->last_status) || !status) { | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 528 | 		atm_dbg(usbatm, "%s: line state 0x%02x\n", __func__, status); | 
 | 529 |  | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 530 | 		switch (status) { | 
 | 531 | 		case 0: | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 532 | 			atm_dev->signal = ATM_PHY_SIG_LOST; | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 533 | 			if (instance->last_status) | 
| David S. Miller | 52fbae2 | 2005-07-25 19:54:35 -0700 | [diff] [blame] | 534 | 				atm_info(usbatm, "ADSL line is down\n"); | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 535 | 			/* It may never resync again unless we ask it to... */ | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 536 | 			ret = speedtch_start_synchro(instance); | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 537 | 			break; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 538 |  | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 539 | 		case 0x08: | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 540 | 			atm_dev->signal = ATM_PHY_SIG_UNKNOWN; | 
| David S. Miller | 52fbae2 | 2005-07-25 19:54:35 -0700 | [diff] [blame] | 541 | 			atm_info(usbatm, "ADSL line is blocked?\n"); | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 542 | 			break; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 543 |  | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 544 | 		case 0x10: | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 545 | 			atm_dev->signal = ATM_PHY_SIG_LOST; | 
| David S. Miller | 52fbae2 | 2005-07-25 19:54:35 -0700 | [diff] [blame] | 546 | 			atm_info(usbatm, "ADSL line is synchronising\n"); | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 547 | 			break; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 548 |  | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 549 | 		case 0x20: | 
 | 550 | 			down_speed = buf[OFFSET_b] | (buf[OFFSET_b + 1] << 8) | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 551 | 				| (buf[OFFSET_b + 2] << 16) | (buf[OFFSET_b + 3] << 24); | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 552 | 			up_speed = buf[OFFSET_b + 4] | (buf[OFFSET_b + 5] << 8) | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 553 | 				| (buf[OFFSET_b + 6] << 16) | (buf[OFFSET_b + 7] << 24); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 554 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 555 | 			if (!(down_speed & 0x0000ffff) && !(up_speed & 0x0000ffff)) { | 
 | 556 | 				down_speed >>= 16; | 
 | 557 | 				up_speed >>= 16; | 
 | 558 | 			} | 
 | 559 |  | 
 | 560 | 			atm_dev->link_rate = down_speed * 1000 / 424; | 
 | 561 | 			atm_dev->signal = ATM_PHY_SIG_FOUND; | 
 | 562 |  | 
 | 563 | 			atm_info(usbatm, | 
| Duncan Sands | 322a95b | 2005-06-23 09:20:50 +0200 | [diff] [blame] | 564 | 				 "ADSL line is up (%d kb/s down | %d kb/s up)\n", | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 565 | 				 down_speed, up_speed); | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 566 | 			break; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 567 |  | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 568 | 		default: | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 569 | 			atm_dev->signal = ATM_PHY_SIG_UNKNOWN; | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 570 | 			atm_info(usbatm, "unknown line state %02x\n", status); | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 571 | 			break; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 572 | 		} | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 573 |  | 
 | 574 | 		instance->last_status = status; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 575 | 	} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 576 | } | 
 | 577 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 578 | static void speedtch_status_poll(unsigned long data) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 579 | { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 580 | 	struct speedtch_instance_data *instance = (void *)data; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 581 |  | 
| David Howells | c402895 | 2006-11-22 14:57:56 +0000 | [diff] [blame] | 582 | 	schedule_delayed_work(&instance->status_checker, 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 583 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 584 | 	/* The following check is racy, but the race is harmless */ | 
 | 585 | 	if (instance->poll_delay < MAX_POLL_DELAY) | 
 | 586 | 		mod_timer(&instance->status_checker.timer, jiffies + msecs_to_jiffies(instance->poll_delay)); | 
 | 587 | 	else | 
| David S. Miller | 52fbae2 | 2005-07-25 19:54:35 -0700 | [diff] [blame] | 588 | 		atm_warn(instance->usbatm, "Too many failures - disabling line status polling\n"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 589 | } | 
 | 590 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 591 | static void speedtch_resubmit_int(unsigned long data) | 
 | 592 | { | 
 | 593 | 	struct speedtch_instance_data *instance = (void *)data; | 
 | 594 | 	struct urb *int_urb = instance->int_urb; | 
 | 595 | 	int ret; | 
 | 596 |  | 
 | 597 | 	atm_dbg(instance->usbatm, "%s entered\n", __func__); | 
 | 598 |  | 
 | 599 | 	if (int_urb) { | 
 | 600 | 		ret = usb_submit_urb(int_urb, GFP_ATOMIC); | 
 | 601 | 		if (!ret) | 
| David Howells | c402895 | 2006-11-22 14:57:56 +0000 | [diff] [blame] | 602 | 			schedule_delayed_work(&instance->status_checker, 0); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 603 | 		else { | 
 | 604 | 			atm_dbg(instance->usbatm, "%s: usb_submit_urb failed with result %d\n", __func__, ret); | 
 | 605 | 			mod_timer(&instance->resubmit_timer, jiffies + msecs_to_jiffies(RESUBMIT_DELAY)); | 
 | 606 | 		} | 
 | 607 | 	} | 
 | 608 | } | 
 | 609 |  | 
| David Howells | 7d12e78 | 2006-10-05 14:55:46 +0100 | [diff] [blame] | 610 | static void speedtch_handle_int(struct urb *int_urb) | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 611 | { | 
 | 612 | 	struct speedtch_instance_data *instance = int_urb->context; | 
 | 613 | 	struct usbatm_data *usbatm = instance->usbatm; | 
 | 614 | 	unsigned int count = int_urb->actual_length; | 
 | 615 | 	int ret = int_urb->status; | 
 | 616 |  | 
 | 617 | 	/* The magic interrupt for "up state" */ | 
| Jesper Juhl | 3c6bee1 | 2006-01-09 20:54:01 -0800 | [diff] [blame] | 618 | 	static const unsigned char up_int[6]   = { 0xa1, 0x00, 0x01, 0x00, 0x00, 0x00 }; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 619 | 	/* The magic interrupt for "down state" */ | 
| Jesper Juhl | 3c6bee1 | 2006-01-09 20:54:01 -0800 | [diff] [blame] | 620 | 	static const unsigned char down_int[6] = { 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00 }; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 621 |  | 
 | 622 | 	atm_dbg(usbatm, "%s entered\n", __func__); | 
 | 623 |  | 
 | 624 | 	if (ret < 0) { | 
 | 625 | 		atm_dbg(usbatm, "%s: nonzero urb status %d!\n", __func__, ret); | 
 | 626 | 		goto fail; | 
 | 627 | 	} | 
 | 628 |  | 
 | 629 | 	if ((count == 6) && !memcmp(up_int, instance->int_data, 6)) { | 
 | 630 | 		del_timer(&instance->status_checker.timer); | 
| David S. Miller | 52fbae2 | 2005-07-25 19:54:35 -0700 | [diff] [blame] | 631 | 		atm_info(usbatm, "DSL line goes up\n"); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 632 | 	} else if ((count == 6) && !memcmp(down_int, instance->int_data, 6)) { | 
| David S. Miller | 52fbae2 | 2005-07-25 19:54:35 -0700 | [diff] [blame] | 633 | 		atm_info(usbatm, "DSL line goes down\n"); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 634 | 	} else { | 
 | 635 | 		int i; | 
 | 636 |  | 
 | 637 | 		atm_dbg(usbatm, "%s: unknown interrupt packet of length %d:", __func__, count); | 
 | 638 | 		for (i = 0; i < count; i++) | 
 | 639 | 			printk(" %02x", instance->int_data[i]); | 
 | 640 | 		printk("\n"); | 
 | 641 | 		goto fail; | 
 | 642 | 	} | 
 | 643 |  | 
 | 644 | 	if ((int_urb = instance->int_urb)) { | 
 | 645 | 		ret = usb_submit_urb(int_urb, GFP_ATOMIC); | 
| David Howells | c402895 | 2006-11-22 14:57:56 +0000 | [diff] [blame] | 646 | 		schedule_delayed_work(&instance->status_checker, 0); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 647 | 		if (ret < 0) { | 
 | 648 | 			atm_dbg(usbatm, "%s: usb_submit_urb failed with result %d\n", __func__, ret); | 
 | 649 | 			goto fail; | 
 | 650 | 		} | 
 | 651 | 	} | 
 | 652 |  | 
 | 653 | 	return; | 
 | 654 |  | 
 | 655 | fail: | 
 | 656 | 	if ((int_urb = instance->int_urb)) | 
 | 657 | 		mod_timer(&instance->resubmit_timer, jiffies + msecs_to_jiffies(RESUBMIT_DELAY)); | 
 | 658 | } | 
 | 659 |  | 
 | 660 | static int speedtch_atm_start(struct usbatm_data *usbatm, struct atm_dev *atm_dev) | 
 | 661 | { | 
 | 662 | 	struct usb_device *usb_dev = usbatm->usb_dev; | 
 | 663 | 	struct speedtch_instance_data *instance = usbatm->driver_data; | 
 | 664 | 	int i, ret; | 
 | 665 | 	unsigned char mac_str[13]; | 
 | 666 |  | 
 | 667 | 	atm_dbg(usbatm, "%s entered\n", __func__); | 
 | 668 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 669 | 	/* Set MAC address, it is stored in the serial number */ | 
 | 670 | 	memset(atm_dev->esi, 0, sizeof(atm_dev->esi)); | 
 | 671 | 	if (usb_string(usb_dev, usb_dev->descriptor.iSerialNumber, mac_str, sizeof(mac_str)) == 12) { | 
 | 672 | 		for (i = 0; i < 6; i++) | 
 | 673 | 			atm_dev->esi[i] = (hex2int(mac_str[i * 2]) * 16) + (hex2int(mac_str[i * 2 + 1])); | 
 | 674 | 	} | 
 | 675 |  | 
 | 676 | 	/* Start modem synchronisation */ | 
 | 677 | 	ret = speedtch_start_synchro(instance); | 
 | 678 |  | 
 | 679 | 	/* Set up interrupt endpoint */ | 
 | 680 | 	if (instance->int_urb) { | 
 | 681 | 		ret = usb_submit_urb(instance->int_urb, GFP_KERNEL); | 
 | 682 | 		if (ret < 0) { | 
 | 683 | 			/* Doesn't matter; we'll poll anyway */ | 
 | 684 | 			atm_dbg(usbatm, "%s: submission of interrupt URB failed (%d)!\n", __func__, ret); | 
 | 685 | 			usb_free_urb(instance->int_urb); | 
 | 686 | 			instance->int_urb = NULL; | 
 | 687 | 		} | 
 | 688 | 	} | 
 | 689 |  | 
 | 690 | 	/* Start status polling */ | 
 | 691 | 	mod_timer(&instance->status_checker.timer, jiffies + msecs_to_jiffies(1000)); | 
 | 692 |  | 
 | 693 | 	return 0; | 
 | 694 | } | 
 | 695 |  | 
 | 696 | static void speedtch_atm_stop(struct usbatm_data *usbatm, struct atm_dev *atm_dev) | 
 | 697 | { | 
 | 698 | 	struct speedtch_instance_data *instance = usbatm->driver_data; | 
 | 699 | 	struct urb *int_urb = instance->int_urb; | 
 | 700 |  | 
 | 701 | 	atm_dbg(usbatm, "%s entered\n", __func__); | 
 | 702 |  | 
 | 703 | 	del_timer_sync(&instance->status_checker.timer); | 
 | 704 |  | 
 | 705 | 	/* | 
 | 706 | 	 * Since resubmit_timer and int_urb can schedule themselves and | 
 | 707 | 	 * each other, shutting them down correctly takes some care | 
 | 708 | 	 */ | 
 | 709 | 	instance->int_urb = NULL; /* signal shutdown */ | 
 | 710 | 	mb(); | 
 | 711 | 	usb_kill_urb(int_urb); | 
 | 712 | 	del_timer_sync(&instance->resubmit_timer); | 
 | 713 | 	/* | 
 | 714 | 	 * At this point, speedtch_handle_int and speedtch_resubmit_int | 
 | 715 | 	 * can run or be running, but instance->int_urb == NULL means that | 
 | 716 | 	 * they will not reschedule | 
 | 717 | 	 */ | 
 | 718 | 	usb_kill_urb(int_urb); | 
 | 719 | 	del_timer_sync(&instance->resubmit_timer); | 
 | 720 | 	usb_free_urb(int_urb); | 
 | 721 |  | 
 | 722 | 	flush_scheduled_work(); | 
 | 723 | } | 
 | 724 |  | 
 | 725 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 726 | /********** | 
 | 727 | **  USB  ** | 
 | 728 | **********/ | 
 | 729 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 730 | static struct usb_device_id speedtch_usb_ids[] = { | 
 | 731 | 	{USB_DEVICE(0x06b9, 0x4061)}, | 
 | 732 | 	{} | 
 | 733 | }; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 734 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 735 | MODULE_DEVICE_TABLE(usb, speedtch_usb_ids); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 736 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 737 | static int speedtch_usb_probe(struct usb_interface *, const struct usb_device_id *); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 738 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 739 | static struct usb_driver speedtch_usb_driver = { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 740 | 	.name		= speedtch_driver_name, | 
 | 741 | 	.probe		= speedtch_usb_probe, | 
 | 742 | 	.disconnect	= usbatm_usb_disconnect, | 
 | 743 | 	.id_table	= speedtch_usb_ids | 
 | 744 | }; | 
 | 745 |  | 
 | 746 | static void speedtch_release_interfaces(struct usb_device *usb_dev, int num_interfaces) { | 
 | 747 | 	struct usb_interface *cur_intf; | 
 | 748 | 	int i; | 
 | 749 |  | 
 | 750 | 	for(i = 0; i < num_interfaces; i++) | 
 | 751 | 		if ((cur_intf = usb_ifnum_to_if(usb_dev, i))) { | 
 | 752 | 			usb_set_intfdata(cur_intf, NULL); | 
 | 753 | 			usb_driver_release_interface(&speedtch_usb_driver, cur_intf); | 
 | 754 | 		} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 755 | } | 
 | 756 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 757 | static int speedtch_bind(struct usbatm_data *usbatm, | 
 | 758 | 			 struct usb_interface *intf, | 
| Duncan Sands | 35644b0 | 2006-01-17 11:16:13 +0100 | [diff] [blame] | 759 | 			 const struct usb_device_id *id) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 760 | { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 761 | 	struct usb_device *usb_dev = interface_to_usbdev(intf); | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 762 | 	struct usb_interface *cur_intf, *data_intf; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 763 | 	struct speedtch_instance_data *instance; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 764 | 	int ifnum = intf->altsetting->desc.bInterfaceNumber; | 
 | 765 | 	int num_interfaces = usb_dev->actconfig->desc.bNumInterfaces; | 
 | 766 | 	int i, ret; | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 767 | 	int use_isoc; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 768 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 769 | 	usb_dbg(usbatm, "%s entered\n", __func__); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 770 |  | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 771 | 	/* sanity checks */ | 
 | 772 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 773 | 	if (usb_dev->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) { | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 774 | 		usb_err(usbatm, "%s: wrong device class %d\n", __func__, usb_dev->descriptor.bDeviceClass); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 775 | 		return -ENODEV; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 776 | 	} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 777 |  | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 778 | 	if (!(data_intf = usb_ifnum_to_if(usb_dev, INTERFACE_DATA))) { | 
 | 779 | 		usb_err(usbatm, "%s: data interface not found!\n", __func__); | 
 | 780 | 		return -ENODEV; | 
 | 781 | 	} | 
 | 782 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 783 | 	/* claim all interfaces */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 784 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 785 | 	for (i=0; i < num_interfaces; i++) { | 
 | 786 | 		cur_intf = usb_ifnum_to_if(usb_dev, i); | 
 | 787 |  | 
 | 788 | 		if ((i != ifnum) && cur_intf) { | 
 | 789 | 			ret = usb_driver_claim_interface(&speedtch_usb_driver, cur_intf, usbatm); | 
 | 790 |  | 
 | 791 | 			if (ret < 0) { | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 792 | 				usb_err(usbatm, "%s: failed to claim interface %2d (%d)!\n", __func__, i, ret); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 793 | 				speedtch_release_interfaces(usb_dev, i); | 
 | 794 | 				return ret; | 
 | 795 | 			} | 
 | 796 | 		} | 
 | 797 | 	} | 
 | 798 |  | 
| Duncan Sands | 9a734ef | 2006-01-13 09:38:22 +0100 | [diff] [blame] | 799 | 	instance = kzalloc(sizeof(*instance), GFP_KERNEL); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 800 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 801 | 	if (!instance) { | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 802 | 		usb_err(usbatm, "%s: no memory for instance data!\n", __func__); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 803 | 		ret = -ENOMEM; | 
 | 804 | 		goto fail_release; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 805 | 	} | 
 | 806 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 807 | 	instance->usbatm = usbatm; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 808 |  | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 809 | 	/* module parameters may change at any moment, so take a snapshot */ | 
 | 810 | 	instance->params.altsetting = altsetting; | 
 | 811 | 	instance->params.BMaxDSL = BMaxDSL; | 
 | 812 | 	instance->params.ModemMode = ModemMode; | 
 | 813 | 	memcpy(instance->params.ModemOption, DEFAULT_MODEM_OPTION, MODEM_OPTION_LENGTH); | 
 | 814 | 	memcpy(instance->params.ModemOption, ModemOption, num_ModemOption); | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 815 | 	use_isoc = enable_isoc; | 
| Duncan Sands | 6f74947 | 2006-01-13 10:52:38 +0100 | [diff] [blame] | 816 |  | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 817 | 	if (instance->params.altsetting) | 
 | 818 | 		if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, instance->params.altsetting)) < 0) { | 
 | 819 | 			usb_err(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, instance->params.altsetting, ret); | 
 | 820 | 			instance->params.altsetting = 0; /* fall back to default */ | 
| Duncan Sands | 6f74947 | 2006-01-13 10:52:38 +0100 | [diff] [blame] | 821 | 		} | 
 | 822 |  | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 823 | 	if (!instance->params.altsetting && use_isoc) | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 824 | 		if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, DEFAULT_ISOC_ALTSETTING)) < 0) { | 
 | 825 | 			usb_dbg(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, DEFAULT_ISOC_ALTSETTING, ret); | 
 | 826 | 			use_isoc = 0; /* fall back to bulk */ | 
 | 827 | 		} | 
 | 828 |  | 
 | 829 | 	if (use_isoc) { | 
 | 830 | 		const struct usb_host_interface *desc = data_intf->cur_altsetting; | 
 | 831 | 		const __u8 target_address = USB_DIR_IN | usbatm->driver->isoc_in; | 
 | 832 | 		int i; | 
 | 833 |  | 
 | 834 | 		use_isoc = 0; /* fall back to bulk if endpoint not found */ | 
 | 835 |  | 
 | 836 | 		for (i=0; i<desc->desc.bNumEndpoints; i++) { | 
 | 837 | 			const struct usb_endpoint_descriptor *endpoint_desc = &desc->endpoint[i].desc; | 
 | 838 |  | 
 | 839 | 			if ((endpoint_desc->bEndpointAddress == target_address)) { | 
| Luiz Fernando N. Capitulino | c5dd1f9 | 2006-10-26 13:02:58 -0300 | [diff] [blame] | 840 | 				use_isoc = | 
 | 841 | 					usb_endpoint_xfer_isoc(endpoint_desc); | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 842 | 				break; | 
 | 843 | 			} | 
 | 844 | 		} | 
 | 845 |  | 
 | 846 | 		if (!use_isoc) | 
 | 847 | 			usb_info(usbatm, "isochronous transfer not supported - using bulk\n"); | 
 | 848 | 	} | 
 | 849 |  | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 850 | 	if (!use_isoc && !instance->params.altsetting) | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 851 | 		if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, DEFAULT_BULK_ALTSETTING)) < 0) { | 
 | 852 | 			usb_err(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, DEFAULT_BULK_ALTSETTING, ret); | 
| Duncan Sands | 6f74947 | 2006-01-13 10:52:38 +0100 | [diff] [blame] | 853 | 			goto fail_free; | 
 | 854 | 		} | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 855 |  | 
| Duncan Sands | 6a4f1b4 | 2006-10-05 10:40:55 +0200 | [diff] [blame] | 856 | 	if (!instance->params.altsetting) | 
 | 857 | 		instance->params.altsetting = use_isoc ? DEFAULT_ISOC_ALTSETTING : DEFAULT_BULK_ALTSETTING; | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 858 |  | 
 | 859 | 	usbatm->flags |= (use_isoc ? UDSL_USE_ISOC : 0); | 
| Duncan Sands | 6f74947 | 2006-01-13 10:52:38 +0100 | [diff] [blame] | 860 |  | 
| David Howells | c402895 | 2006-11-22 14:57:56 +0000 | [diff] [blame] | 861 | 	INIT_DELAYED_WORK(&instance->status_checker, speedtch_check_status); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 862 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 863 | 	instance->status_checker.timer.function = speedtch_status_poll; | 
 | 864 | 	instance->status_checker.timer.data = (unsigned long)instance; | 
| Duncan Sands | 1a7aad1 | 2005-06-23 09:37:56 +0200 | [diff] [blame] | 865 | 	instance->last_status = 0xff; | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 866 | 	instance->poll_delay = MIN_POLL_DELAY; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 867 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 868 | 	init_timer(&instance->resubmit_timer); | 
 | 869 | 	instance->resubmit_timer.function = speedtch_resubmit_int; | 
 | 870 | 	instance->resubmit_timer.data = (unsigned long)instance; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 871 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 872 | 	instance->int_urb = usb_alloc_urb(0, GFP_KERNEL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 873 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 874 | 	if (instance->int_urb) | 
 | 875 | 		usb_fill_int_urb(instance->int_urb, usb_dev, | 
 | 876 | 				 usb_rcvintpipe(usb_dev, ENDPOINT_INT), | 
 | 877 | 				 instance->int_data, sizeof(instance->int_data), | 
 | 878 | 				 speedtch_handle_int, instance, 50); | 
 | 879 | 	else | 
 | 880 | 		usb_dbg(usbatm, "%s: no memory for interrupt urb!\n", __func__); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 881 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 882 | 	/* check whether the modem already seems to be alive */ | 
 | 883 | 	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), | 
 | 884 | 			      0x12, 0xc0, 0x07, 0x00, | 
 | 885 | 			      instance->scratch_buffer + OFFSET_7, SIZE_7, 500); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 886 |  | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 887 | 	usbatm->flags |= (ret == SIZE_7 ? UDSL_SKIP_HEAVY_INIT : 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 888 |  | 
| Duncan Sands | 35644b0 | 2006-01-17 11:16:13 +0100 | [diff] [blame] | 889 | 	usb_dbg(usbatm, "%s: firmware %s loaded\n", __func__, usbatm->flags & UDSL_SKIP_HEAVY_INIT ? "already" : "not"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 890 |  | 
| Duncan Sands | 35644b0 | 2006-01-17 11:16:13 +0100 | [diff] [blame] | 891 | 	if (!(usbatm->flags & UDSL_SKIP_HEAVY_INIT)) | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 892 | 		if ((ret = usb_reset_device(usb_dev)) < 0) { | 
 | 893 | 			usb_err(usbatm, "%s: device reset failed (%d)!\n", __func__, ret); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 894 | 			goto fail_free; | 
| Duncan Sands | 0ec3c7e | 2006-01-17 11:15:13 +0100 | [diff] [blame] | 895 | 		} | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 896 |  | 
 | 897 |         usbatm->driver_data = instance; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 898 |  | 
 | 899 | 	return 0; | 
 | 900 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 901 | fail_free: | 
 | 902 | 	usb_free_urb(instance->int_urb); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 903 | 	kfree(instance); | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 904 | fail_release: | 
 | 905 | 	speedtch_release_interfaces(usb_dev, num_interfaces); | 
 | 906 | 	return ret; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 907 | } | 
 | 908 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 909 | static void speedtch_unbind(struct usbatm_data *usbatm, struct usb_interface *intf) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 910 | { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 911 | 	struct usb_device *usb_dev = interface_to_usbdev(intf); | 
 | 912 | 	struct speedtch_instance_data *instance = usbatm->driver_data; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 913 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 914 | 	usb_dbg(usbatm, "%s entered\n", __func__); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 915 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 916 | 	speedtch_release_interfaces(usb_dev, usb_dev->actconfig->desc.bNumInterfaces); | 
 | 917 | 	usb_free_urb(instance->int_urb); | 
 | 918 | 	kfree(instance); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 919 | } | 
 | 920 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 921 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 922 | /*********** | 
 | 923 | **  init  ** | 
 | 924 | ***********/ | 
 | 925 |  | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 926 | static struct usbatm_driver speedtch_usbatm_driver = { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 927 | 	.driver_name	= speedtch_driver_name, | 
 | 928 | 	.bind		= speedtch_bind, | 
 | 929 | 	.heavy_init	= speedtch_heavy_init, | 
 | 930 | 	.unbind		= speedtch_unbind, | 
 | 931 | 	.atm_start	= speedtch_atm_start, | 
 | 932 | 	.atm_stop	= speedtch_atm_stop, | 
| Duncan Sands | 80aae7a | 2006-01-13 10:59:23 +0100 | [diff] [blame] | 933 | 	.bulk_in	= ENDPOINT_BULK_DATA, | 
 | 934 | 	.bulk_out	= ENDPOINT_BULK_DATA, | 
 | 935 | 	.isoc_in	= ENDPOINT_ISOC_DATA | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 936 | }; | 
 | 937 |  | 
 | 938 | static int speedtch_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) | 
 | 939 | { | 
 | 940 | 	return usbatm_usb_probe(intf, id, &speedtch_usbatm_driver); | 
 | 941 | } | 
 | 942 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 943 | static int __init speedtch_usb_init(void) | 
 | 944 | { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 945 | 	dbg("%s: driver version %s", __func__, DRIVER_VERSION); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 946 |  | 
 | 947 | 	return usb_register(&speedtch_usb_driver); | 
 | 948 | } | 
 | 949 |  | 
 | 950 | static void __exit speedtch_usb_cleanup(void) | 
 | 951 | { | 
| Duncan Sands | 48da726 | 2005-05-11 20:20:40 +0200 | [diff] [blame] | 952 | 	dbg("%s", __func__); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 953 |  | 
 | 954 | 	usb_deregister(&speedtch_usb_driver); | 
 | 955 | } | 
 | 956 |  | 
 | 957 | module_init(speedtch_usb_init); | 
 | 958 | module_exit(speedtch_usb_cleanup); | 
 | 959 |  | 
 | 960 | MODULE_AUTHOR(DRIVER_AUTHOR); | 
 | 961 | MODULE_DESCRIPTION(DRIVER_DESC); | 
 | 962 | MODULE_LICENSE("GPL"); | 
 | 963 | MODULE_VERSION(DRIVER_VERSION); |