speedtch: "extended reach"

The speedtouch modem setup code was reverse engineered many years
ago from a prehistoric windows driver. Less ancient windows drivers,
even those from a few years ago, perform extra initialization steps
which this patch adds to the linux driver.  David Woodhouse observed
that this initialization along with the firmware bin/sachu3/zzzlp2.eni
from the driver at
http://www.speedtouch.co.uk/downloads/330/301/UK3012%20Extended.zip
improves line sync speeds by about 20%.  He provided the original
patch, which I've modified to use symbolic names (BMaxDSL, ModemMode,
ModemOption) rather than magic numbers.  These names may not seem like
much of an improvement (after all, what is "ModemOption" exactly?),
but they do have one big advantage: they are the names used in the
windows registry.  I've made them available as module parameters.
Thanks are due to Aurelio Arroyo, who noticed the relationship
between these magic numbers and the entries in Phonebook.ini.

Signed-off-by: Duncan Sands <baldrick@free.fr>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

diff --git a/drivers/usb/atm/speedtch.c b/drivers/usb/atm/speedtch.c
index 7c7b507..c870c80 100644
--- a/drivers/usb/atm/speedtch.c
+++ b/drivers/usb/atm/speedtch.c
@@ -55,7 +55,6 @@
 #define OFFSET_d	9		/* size 4 */
 #define OFFSET_e	13		/* size 1 */
 #define OFFSET_f	14		/* size 1 */
-#define TOTAL		15
 
 #define SIZE_7		1
 #define SIZE_b		8
@@ -79,6 +78,18 @@
 static int enable_isoc = DEFAULT_ENABLE_ISOC;
 static int sw_buffering = DEFAULT_SW_BUFFERING;
 
+#define DEFAULT_B_MAX_DSL	8128
+#define DEFAULT_MODEM_MODE	11
+#define MODEM_OPTION_LENGTH	16
+static const unsigned char DEFAULT_MODEM_OPTION[MODEM_OPTION_LENGTH] = {
+	0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static unsigned int BMaxDSL = DEFAULT_B_MAX_DSL;
+static unsigned char ModemMode = DEFAULT_MODEM_MODE;
+static unsigned char ModemOption[MODEM_OPTION_LENGTH];
+static int num_ModemOption;
+
 module_param(altsetting, uint, S_IRUGO | S_IWUSR);
 MODULE_PARM_DESC(altsetting,
 		"Alternative setting for data interface (bulk_default: "
@@ -100,6 +111,17 @@
 		 "Enable software buffering (default: "
 		 __MODULE_STRING(DEFAULT_SW_BUFFERING) ")");
 
+module_param(BMaxDSL, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(BMaxDSL,
+		"default: " __MODULE_STRING(DEFAULT_B_MAX_DSL));
+
+module_param(ModemMode, byte, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(ModemMode,
+		"default: " __MODULE_STRING(DEFAULT_MODEM_MODE));
+
+module_param_array(ModemOption, byte, &num_ModemOption, S_IRUGO);
+MODULE_PARM_DESC(ModemOption, "default: 0x10,0x00,0x00,0x00,0x20");
+
 #define INTERFACE_DATA		1
 #define ENDPOINT_INT		0x81
 #define ENDPOINT_BULK_DATA	0x07
@@ -108,10 +130,17 @@
 
 #define hex2int(c) ( (c >= '0') && (c <= '9') ? (c - '0') : ((c & 0xf) + 9) )
 
+struct speedtch_params {
+	unsigned int altsetting;
+	unsigned int BMaxDSL;
+	unsigned char ModemMode;
+	unsigned char ModemOption[MODEM_OPTION_LENGTH];
+};
+
 struct speedtch_instance_data {
 	struct usbatm_data *usbatm;
 
-	unsigned int altsetting;
+	struct speedtch_params params; /* set in probe, constant afterwards */
 
 	struct work_struct status_checker;
 
@@ -123,7 +152,7 @@
 	struct urb *int_urb;
 	unsigned char int_data[16];
 
-	unsigned char scratch_buffer[TOTAL];
+	unsigned char scratch_buffer[16];
 };
 
 /***************
@@ -186,6 +215,34 @@
 			      0x01, 0x40, 0x04, 0x00, buf, 3, CTRL_TIMEOUT);
 	if (ret < 0)
 		usb_warn(usbatm, "%s failed on URB150: %d\n", __func__, ret);
+
+	/* Extra initialisation in recent drivers - gives higher speeds */
+
+	/* URBext1 */
+	buf[0] = instance->params.ModemMode;
+	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			      0x01, 0x40, 0x11, 0x00, buf, 1, CTRL_TIMEOUT);
+	if (ret < 0)
+		usb_warn(usbatm, "%s failed on URBext1: %d\n", __func__, ret);
+
+	/* URBext2 */
+	/* This seems to be the one which actually triggers the higher sync
+	   rate -- it does require the new firmware too, although it works OK
+	   with older firmware */
+	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			      0x01, 0x40, 0x14, 0x00,
+			      instance->params.ModemOption,
+			      MODEM_OPTION_LENGTH, CTRL_TIMEOUT);
+	if (ret < 0)
+		usb_warn(usbatm, "%s failed on URBext2: %d\n", __func__, ret);
+
+	/* URBext3 */
+	buf[0] = instance->params.BMaxDSL & 0xff;
+	buf[1] = instance->params.BMaxDSL >> 8;
+	ret = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			      0x01, 0x40, 0x12, 0x00, buf, 2, CTRL_TIMEOUT);
+	if (ret < 0)
+		usb_warn(usbatm, "%s failed on URBext3: %d\n", __func__, ret);
 }
 
 static int speedtch_upload_firmware(struct speedtch_instance_data *instance,
@@ -285,8 +342,8 @@
 	   because we're in our own kernel thread anyway. */
 	msleep_interruptible(1000);
 
-	if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, instance->altsetting)) < 0) {
-		usb_err(usbatm, "%s: setting interface to %d failed (%d)!\n", __func__, instance->altsetting, ret);
+	if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, instance->params.altsetting)) < 0) {
+		usb_err(usbatm, "%s: setting interface to %d failed (%d)!\n", __func__, instance->params.altsetting, ret);
 		goto out_free;
 	}
 
@@ -372,7 +429,7 @@
 	unsigned char *buf = instance->scratch_buffer;
 	int ret;
 
-	memset(buf, 0, TOTAL);
+	memset(buf, 0, 16);
 
 	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
 			      0x12, 0xc0, 0x07, 0x00, buf + OFFSET_7, SIZE_7,
@@ -746,17 +803,21 @@
 
 	instance->usbatm = usbatm;
 
-	/* altsetting and enable_isoc may change at any moment, so take a snapshot */
-	instance->altsetting = altsetting;
+	/* module parameters may change at any moment, so take a snapshot */
+	instance->params.altsetting = altsetting;
+	instance->params.BMaxDSL = BMaxDSL;
+	instance->params.ModemMode = ModemMode;
+	memcpy(instance->params.ModemOption, DEFAULT_MODEM_OPTION, MODEM_OPTION_LENGTH);
+	memcpy(instance->params.ModemOption, ModemOption, num_ModemOption);
 	use_isoc = enable_isoc;
 
-	if (instance->altsetting)
-		if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, instance->altsetting)) < 0) {
-			usb_err(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, instance->altsetting, ret);
-			instance->altsetting = 0; /* fall back to default */
+	if (instance->params.altsetting)
+		if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, instance->params.altsetting)) < 0) {
+			usb_err(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, instance->params.altsetting, ret);
+			instance->params.altsetting = 0; /* fall back to default */
 		}
 
-	if (!instance->altsetting && use_isoc)
+	if (!instance->params.altsetting && use_isoc)
 		if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, DEFAULT_ISOC_ALTSETTING)) < 0) {
 			usb_dbg(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, DEFAULT_ISOC_ALTSETTING, ret);
 			use_isoc = 0; /* fall back to bulk */
@@ -783,14 +844,14 @@
 			usb_info(usbatm, "isochronous transfer not supported - using bulk\n");
 	}
 
-	if (!use_isoc && !instance->altsetting)
+	if (!use_isoc && !instance->params.altsetting)
 		if ((ret = usb_set_interface(usb_dev, INTERFACE_DATA, DEFAULT_BULK_ALTSETTING)) < 0) {
 			usb_err(usbatm, "%s: setting interface to %2d failed (%d)!\n", __func__, DEFAULT_BULK_ALTSETTING, ret);
 			goto fail_free;
 		}
 
-	if (!instance->altsetting)
-		instance->altsetting = use_isoc ? DEFAULT_ISOC_ALTSETTING : DEFAULT_BULK_ALTSETTING;
+	if (!instance->params.altsetting)
+		instance->params.altsetting = use_isoc ? DEFAULT_ISOC_ALTSETTING : DEFAULT_BULK_ALTSETTING;
 
 	usbatm->flags |= (use_isoc ? UDSL_USE_ISOC : 0);