speedtch cxacru2.c, NONE, 1.1 cxacru2.h, NONE, 1.1 speedtch2.c, NONE, 1.1 speedtch2.h, NONE, 1.1 usbatm2.c, NONE, 1.1 Makefile, 1.8, 1.9 usbatm.c, 1.1, NONE

Duncan Sands duncan at infradead.org
Thu Jan 20 11:04:23 EST 2005


Update of /home/cvs/speedtch
In directory phoenix.infradead.org:/tmp/cvs-serv15047

Modified Files:
	Makefile 
Added Files:
	cxacru2.c cxacru2.h speedtch2.c speedtch2.h usbatm2.c 
Removed Files:
	usbatm.c 
Log Message:
Continuing on my merry way, reworking the basic driver structure.


--- NEW FILE cxacru2.c ---
/******************************************************************************
 *  cxacru.c  -  driver for USB xDSL modems based on
 *               Conexant AccessRunner chipset
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 *  more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 *  this program; if not, write to the Free Software Foundation, Inc., 59
 *  Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 ******************************************************************************/

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/gfp.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/errno.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/list.h>
#include <asm/processor.h>
#include <asm/uaccess.h>
#include <linux/smp_lock.h>
#include <linux/interrupt.h>
#include <linux/atm.h>
#include <linux/atmdev.h>
#include <linux/crc32.h>
#include <linux/init.h>
#include <linux/firmware.h>

#include "usb_atm.h"

#if defined(CONFIG_FW_LOADER) || defined(CONFIG_FW_LOADER_MODULE)
#	define USE_FW_LOADER
#endif

#define DRIVER_AUTHOR	"Roman Kagan, Josep Comas, David Woodhouse, Duncan Sands"
#define DRIVER_VERSION	"0.1"
#define DRIVER_DESC	"Conexant AccessRunner USB driver version " DRIVER_VERSION

static const char cxacru_driver_name[] = "cxacru";

#define UDSL_IOCTL_LINE_UP		1
#define UDSL_IOCTL_LINE_DOWN		2

#define CXACRU_EP_CMD		0x01	/* Bulk/interrupt in/out */
#define CXACRU_EP_DATA		0x02	/* Bulk in/out */

#define CMD_PACKET_SIZE		64	/* Should be maxpacket(ep)? */

/* Addresses */
#define PLLFCLK_ADDR	0x00350068
#define PLLBCLK_ADDR	0x0035006c
#define SDRAMEN_ADDR	0x00350010
#define FW_ADDR		0x00801000
#define BR_ADDR		0x00180600
#define SIG_ADDR	0x00180500
#define BR_STACK_ADDR	0x00187f10

/* Values */
#define PLLFCLK_NEW	0x5
#define PLLBCLK_NEW	0x3
#define PLLFCLK_OLD	0x02d874df
#define PLLBCLK_OLD	0x0196a51a
#define SDRAM_ENA	0x1

/* Timeout in jiffies */
#define CMD_TIMEOUT	(2 * HZ)
#define DATA_TIMEOUT	(2 * HZ)

/* commands for interaction with the modem through the control channel before
 * firmware is loaded  */
enum cxacru_fw_request {
	FW_CMD_ERR,
	FW_GET_VER,
	FW_READ_MEM,
	FW_WRITE_MEM,
	FW_RMW_MEM,
	FW_CHECKSUM_MEM,
	FW_GOTO_MEM,
};

/* commands for interaction with the modem through the control channel once
 * firmware is loaded  */
enum cxacru_cm_request {
	CM_REQUEST_UNDEFINED = 0x80,
	CM_REQUEST_TEST,
	CM_REQUEST_CHIP_GET_MAC_ADDRESS,
	CM_REQUEST_CHIP_GET_DP_VERSIONS,
	CM_REQUEST_CHIP_ADSL_LINE_START,
	CM_REQUEST_CHIP_ADSL_LINE_STOP,
	CM_REQUEST_CHIP_ADSL_LINE_GET_STATUS,
	CM_REQUEST_CHIP_ADSL_LINE_GET_SPEED,
	CM_REQUEST_CARD_INFO_GET,
	CM_REQUEST_CARD_DATA_GET,
	CM_REQUEST_CARD_DATA_SET,
	CM_REQUEST_COMMAND_HW_IO,
	CM_REQUEST_INTERFACE_HW_IO,
	CM_REQUEST_CARD_SERIAL_DATA_PATH_GET,
	CM_REQUEST_CARD_SERIAL_DATA_PATH_SET,
	CM_REQUEST_CARD_CONTROLLER_VERSION_GET,
	CM_REQUEST_CARD_GET_STATUS,
	CM_REQUEST_CARD_GET_MAC_ADDRESS,
	CM_REQUEST_CARD_GET_DATA_LINK_STATUS,
	CM_REQUEST_MAX,
};

/* reply codes to the commands above */
enum cxacru_cm_status {
	CM_STATUS_UNDEFINED,
	CM_STATUS_SUCCESS,
	CM_STATUS_ERROR,
	CM_STATUS_UNSUPPORTED,
	CM_STATUS_UNIMPLEMENTED,
	CM_STATUS_PARAMETER_ERROR,
	CM_STATUS_DBG_LOOPBACK,
	CM_STATUS_MAX,
};

/* indices into CARD_INFO_GET return array */
enum cxacru_info_idx {
	CXINF_DOWNSTREAM_RATE,
	CXINF_UPSTREAM_RATE,
	CXINF_LINK_STATUS,
	CXINF_LINE_STATUS,
	CXINF_MAC_ADDRESS_HIGH,
	CXINF_MAC_ADDRESS_LOW,
	CXINF_UPSTREAM_SNR_MARGIN,
	CXINF_DOWNSTREAM_SNR_MARGIN,
	CXINF_UPSTREAM_ATTENUATION,
	CXINF_DOWNSTREAM_ATTENUATION,
	CXINF_TRANSMITTER_POWER,
	CXINF_UPSTREAM_BITS_PER_FRAME,
	CXINF_DOWNSTREAM_BITS_PER_FRAME,
	CXINF_STARTUP_ATTEMPTS,
	CXINF_UPSTREAM_CRC_ERRORS,
	CXINF_DOWNSTREAM_CRC_ERRORS,
	CXINF_UPSTREAM_FEC_ERRORS,
	CXINF_DOWNSTREAM_FEC_ERRORS,
	CXINF_UPSTREAM_HEC_ERRORS,
	CXINF_DOWNSTREAM_HEC_ERRORS,
	CXINF_LINE_STARTABLE,
	CXINF_MODULATION,
	CXINF_ADSL_HEADEND,
	CXINF_ADSL_HEADEND_ENVIRONMENT,
	CXINF_CONTROLLER_VERSION,
	/* dunno what the missing two mean */
	CXINF_MAX = 0x1c,
};

struct cxacru_instance_data {
	struct udsl_instance_data u;

	struct semaphore start_serialize;
#ifdef USE_FW_LOADER
	int firmware_start_run;
#endif

	int line_status; 
	struct work_struct poll_work;
	struct timer_list poll_timer;
	/* contol handles */
	struct semaphore cm_serialize;
	u8 *rcv_buf;
	u8 *snd_buf;
	struct urb *rcv_urb;
	struct urb *snd_urb;
	struct completion rcv_done;
	struct completion snd_done;
};

/* the following three functions are stolen from drivers/usb/core/message.c */
static void cxacru_blocking_completion(struct urb *urb, struct pt_regs *regs)
{
	complete((struct completion *)urb->context);
}

static void cxacru_timeout_kill(unsigned long data)
{
	usb_unlink_urb((struct urb *) data);
}

static int cxacru_start_wait_urb(struct urb *urb, struct completion *done,
				 int* actual_length)
{ 
	struct timer_list timer;
	int status;

	init_timer(&timer);
	timer.expires = jiffies + CMD_TIMEOUT;
	timer.data = (unsigned long) urb;
	timer.function = cxacru_timeout_kill;
	add_timer(&timer);
	wait_for_completion(done);
	status = urb->status;
	if (status == -ECONNRESET)
		status = -ETIMEDOUT;
	del_timer_sync(&timer);

	if (actual_length)
		*actual_length = urb->actual_length;
	return status;
}

static int cxacru_cm(struct cxacru_instance_data *instance, enum cxacru_cm_request cm,
		     u8 *wdata, int wsize, u8 *rdata, int rsize)
{
	int ret, actlen;
	int offb, offd;
	const int stride = CMD_PACKET_SIZE - 4;
	u8 *wbuf = instance->snd_buf;
	u8 *rbuf = instance->rcv_buf;
	int wbuflen = ((wsize - 1) / stride + 1) * CMD_PACKET_SIZE;
	int rbuflen = ((rsize - 1) / stride + 1) * CMD_PACKET_SIZE;

	if (wbuflen > PAGE_SIZE || rbuflen > PAGE_SIZE) {
		dbg("too big transfer requested");
		ret = -ENOMEM;
		goto fail;
	}

	down(&instance->cm_serialize);

	/* submit reading urb before the writing one */
	init_completion(&instance->rcv_done);
	ret = usb_submit_urb(instance->rcv_urb, GFP_KERNEL);
	if (ret < 0) {
		err("submitting read urb for cm %#x failed", cm);
		ret = ret;
		goto fail;
	}

	memset(wbuf, 0, wbuflen);
	/* handle wsize == 0 */
	wbuf[0] = cm;
	for (offb = offd = 0; offd < wsize; offd += stride, offb += CMD_PACKET_SIZE) {
		wbuf[offb] = cm;
		memcpy(wbuf + offb + 4, wdata + offd, min_t(int, stride, wsize - offd));
	}

	instance->snd_urb->transfer_buffer_length = wbuflen;
	init_completion(&instance->snd_done);
	ret = usb_submit_urb(instance->snd_urb, GFP_KERNEL);
	if (ret < 0) {
		err("submitting write urb for cm %#x failed", cm);
		ret = ret;
		goto fail;
	}

	ret = cxacru_start_wait_urb(instance->snd_urb, &instance->snd_done, NULL);
	if (ret < 0) {
		err("sending cm %#x failed", cm);
		ret = ret;
		goto fail;
	}

	ret = cxacru_start_wait_urb(instance->rcv_urb, &instance->rcv_done, &actlen);
	if (ret < 0) {
		err("receiving cm %#x failed", cm);
		ret = ret;
		goto fail;
	}
	if (actlen % CMD_PACKET_SIZE || !actlen) {
		err("response is not a positive multiple of %d: %#x",
				CMD_PACKET_SIZE, actlen);
		ret = -EIO;
		goto fail;
	}

	/* check the return status and copy the data to the output buffer, if needed */
	for (offb = offd = 0; offd < rsize && offb < actlen; offb += CMD_PACKET_SIZE) {
		if (rbuf[offb] != cm) {
			err("wrong cm %#x in response", rbuf[offb]);
			ret = -EIO;
			goto fail;
		}
		if (rbuf[offb + 1] != CM_STATUS_SUCCESS) {
			err("response failed: %#x", rbuf[offb + 1]);
			ret = -EIO;
			goto fail;
		}
		if (offd >= rsize)
			break;
		memcpy(rdata + offd, rbuf + offb + 4, min_t(int, stride, rsize - offd));
		offd += stride; 
	}

	ret = offd;
	dbg("cm %#x", cm);
fail:
	up(&instance->cm_serialize);
	return ret;
}

static int cxacru_cm_get_array(struct cxacru_instance_data *instance, enum cxacru_cm_request cm,
			       u32 *data, int size)
{
	int ret, len;
	u32 *buf;
	int offb, offd;
	const int stride = CMD_PACKET_SIZE / (4 * 2) - 1;
	int buflen =  ((size - 1) / stride + 1 + size * 2) * 4;

	buf = kmalloc(buflen, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;
	
	ret = cxacru_cm(instance, cm, NULL, 0, (u8 *) buf, buflen);
	if (ret < 0)
		goto cleanup;

	/* len > 0 && len % 4 == 0 guaranteed by cxacru_cm() */
	len = ret / 4;
	for (offb = 0; offb < len; ) {
		int l = le32_to_cpu(buf[offb++]);
		if (l > stride || l > (len - offb) / 2) {
			err("wrong data length %#x in response", l);
			ret = -EIO;
			goto cleanup;
		}
		while (l--) {
			offd = le32_to_cpu(buf[offb++]);
			if (offd >= size) {
				err("wrong index %#x in response", offd);
				ret = -EIO;
				goto cleanup;
			}
			data[offd] = le32_to_cpu(buf[offb++]);
		}
	}

	ret = 0;

cleanup:
	kfree(buf);
	return ret;
}

static void cxacru_do_timer_poll(struct cxacru_instance_data *instance)
{
	schedule_work(&instance->poll_work);
	mod_timer(&instance->poll_timer, jiffies + (5 * HZ));
}

static void cxacru_timer_poll(unsigned long data)
{
	cxacru_do_timer_poll((struct cxacru_instance_data *)data);
}

#ifdef USE_FW_LOADER
static void cxacru_firmware_start(struct cxacru_instance_data *instance);
#endif

static void cxacru_adsl_start(struct cxacru_instance_data *instance)
{
	int ret;

	dbg("cxacru_adsl_start");
	down(&instance->start_serialize);

	if (!instance->u.atm_dev) {	/* ! fully initialised */
		u8 esi[sizeof(instance->u.atm_dev->esi)] = {};

		ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0);
		if (ret < 0) {		/* firmware not loaded */
			dbg("cxacru_adsl_start: CARD_GET_STATUS returned %d", ret);
#ifdef USE_FW_LOADER
			if (!instance->firmware_start_run) {
				instance->firmware_start_run = 1;
				cxacru_firmware_start(instance);
			}
#endif /* USE_FW_LOADER */
			goto out;
		}

		/* Read MAC address */
		ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_MAC_ADDRESS, NULL, 0, 
				esi, sizeof(esi));
		if (ret < 0) {
			err("cxacru_adsl_start: CARD_GET_MAC_ADDRESS returned %d", ret);
			goto out;
		}

		/* FIXME: remove dev from args; should have been set in probe */
		ret = udsl_instance_setup(instance->u.usb_dev, &instance->u);
		if (ret)
			goto out;
		memcpy(instance->u.atm_dev->esi, esi, sizeof(esi));
		/* FIXME: remove */
		instance->u.status = UDSL_LOADED_FIRMWARE;

		dev_info(&instance->u.usb_dev->dev, "cxacru: MAC = %02x", esi[0]);
		for (ret = 1; ret < sizeof(esi); ret++)
			printk(":%02x", esi[ret]);
		printk("\n");
	}

	/* start ADSL */
	ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0);
	if (ret < 0) {
		err("cxacru_adst_start: CHIP_ADSL_LINE_START returned %d", ret);
		goto out;
	}
	
	/* Start status polling */
	cxacru_do_timer_poll(instance);

	tasklet_schedule(&instance->u.receive_tasklet);
out:
	up(&instance->start_serialize);
}

static void cxacru_poll_status(struct cxacru_instance_data *instance)
{
	u32 buf[CXINF_MAX] = {};
	int ret;

	ret = cxacru_cm_get_array(instance, CM_REQUEST_CARD_INFO_GET, buf, CXINF_MAX);
	if (ret < 0) {
		dev_warn(&instance->u.usb_dev->dev, "poll status: error %d\n", ret);
		return;
	}

	if (instance->line_status == buf[CXINF_LINE_STATUS])
		return;

	instance->line_status = buf[CXINF_LINE_STATUS];
	switch (instance->line_status) {
	case 0:
		instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
		dev_info(&instance->u.usb_dev->dev, "ADSL line: down\n");
		break;

	case 1:
		instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
		dev_info(&instance->u.usb_dev->dev, "ADSL line: attemtping to activate\n");
		break;

	case 2:
		instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
		dev_info(&instance->u.usb_dev->dev, "ADSL line: training\n");
		break;

	case 3:
		instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
		dev_info(&instance->u.usb_dev->dev, "ADSL line: channel analysis\n");
		break;

	case 4:
		instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
		dev_info(&instance->u.usb_dev->dev, "ADSL line: exchange\n");
		break;

	case 5:
		instance->u.atm_dev->link_rate = buf[CXINF_DOWNSTREAM_RATE] * 1000 / 424;
		instance->u.atm_dev->signal = ATM_PHY_SIG_FOUND;

		dev_info(&instance->u.usb_dev->dev, "ADSL line: up (%d Kib/s down | %d Kib/s up)\n",
		     buf[CXINF_DOWNSTREAM_RATE], buf[CXINF_UPSTREAM_RATE]);
		break;

	case 6:
		instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
		dev_info(&instance->u.usb_dev->dev, "ADSL line: waiting\n");
		break;

	case 7:
		instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
		dev_info(&instance->u.usb_dev->dev, "ADSL line: initializing\n");
		break;

	default:
		instance->u.atm_dev->signal = ATM_PHY_SIG_UNKNOWN;
		dev_info(&instance->u.usb_dev->dev, "Unknown line state %02x\n",
				instance->line_status);
		break;
	}
}

#ifdef USE_FW_LOADER
static int cxacru_fw(struct cxacru_instance_data *instance, enum cxacru_fw_request fw,
		     u8 code1, u8 code2, u32 addr, u8 *data, int size)
{
	int ret;
	u8 *buf;
	int offd, offb;
	const int stride = CMD_PACKET_SIZE - 8;
	struct usb_device *usb_dev = instance->u.usb_dev;

	buf = (u8 *) __get_free_page(GFP_KERNEL);
	if (!buf)
		return -ENOMEM;
	
	offb = offd = 0;
	do {
		int l = min_t(int, stride, size - offd);
		buf[offb++] = fw;
		buf[offb++] = l;
		buf[offb++] = code1;
		buf[offb++] = code2;
		*((u32 *) (buf + offb)) = cpu_to_le32(addr);
		offb += 4;
		addr += l;
		if(l)
			memcpy(buf + offb, data + offd, l);
		if (l < stride)
			memset(buf + offb + l, 0, stride - l);
		offb += stride;
		offd += stride;
		if ((offb >= PAGE_SIZE) || (offd >= size)) {
			ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, CXACRU_EP_CMD),
					   buf, offb, NULL, CMD_TIMEOUT);
			if (ret < 0) {
				err("sending fw %#x failed", fw);
				goto cleanup;
			}
			offb = 0;
		}
	} while(offd < size);
	dbg("sent fw %#x", fw);
	
	ret = 0;

cleanup:
	free_page((unsigned long) buf);
	return ret;
}

static void cxacru_upload_firmware(struct cxacru_instance_data *instance,
				   const struct firmware *fw,
				   const struct firmware *br,
				   const struct firmware *cf)
{
	int ret;
	int off;
	struct usb_device *dev = instance->u.usb_dev;
	const u32 vid = dev->descriptor.idVendor;
	const u32 pid = dev->descriptor.idProduct;
	u32 val;

	dbg("cxacru_upload_firmware");

	/* FirmwarePllFClkValue */
	val = br ? PLLFCLK_OLD : PLLFCLK_NEW;
	ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, PLLFCLK_ADDR, (u8 *) &val, 4);
	if (ret) {
		err("FirmwarePllFClkValue failed: %d", ret);
		return;
	}

	/* FirmwarePllBClkValue */
	val = br ? PLLBCLK_OLD : PLLBCLK_NEW;
	ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, PLLBCLK_ADDR, (u8 *) &val, 4);
	if (ret) {
		err("FirmwarePllBClkValue failed: %d", ret);
		return;
	}

	/* Enable SDRAM */
	val = SDRAM_ENA;
	ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, SDRAMEN_ADDR, (u8 *) &val, 4);
	if (ret) {
		err("Enable SDRAM failed: %d", ret);
		return;
	}

	/* Firmware */
	ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, FW_ADDR, fw->data, fw->size);
	if (ret) {
		err("Firmware upload failed: %d", ret);
		return;
	}

	/* Boot ROM patch */
	if (br) {
		ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, BR_ADDR, br->data, br->size);
		if (ret) {
			err("Boot ROM patching failed: %d", ret);
			return;
		}
	}

	/* Signature */
	val = (pid << 16) | (vid & 0xffff);
	ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, SIG_ADDR, (u8 *) &val, 4);
	if (ret) {
		err("Signature storing failed: %d", ret);
		return;
	}

	if (br) {
		val = BR_ADDR;
		ret = cxacru_fw(instance, FW_WRITE_MEM, 0x2, 0x0, BR_STACK_ADDR, (u8 *) &val, 4);
	}
	else {
		ret = cxacru_fw(instance, FW_GOTO_MEM, 0x0, 0x0, FW_ADDR, NULL, 0);
	}
	if (ret) {
		err("Passing control to firmware failed: %d", ret);
		return;
	}

	/* Delay to allow firmware to start up. We can do this here
	   because we're in our own kernel thread anyway. */
	msleep(1000);

	usb_clear_halt(dev, usb_sndbulkpipe(dev, CXACRU_EP_CMD));
	usb_clear_halt(dev, usb_rcvbulkpipe(dev, CXACRU_EP_CMD));
	usb_clear_halt(dev, usb_sndbulkpipe(dev, CXACRU_EP_DATA));
	usb_clear_halt(dev, usb_rcvbulkpipe(dev, CXACRU_EP_DATA));

	ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_STATUS, NULL, 0, NULL, 0);
	if (ret < 0) {
		err("modem failed to initialize: %d", ret);
		return;
	}
	
	/* Load config data (le32), doing one packet at a time */
	for (off = 0; off < cf->size / 4; ) {
		u32 buf[CMD_PACKET_SIZE / 4 - 1];
		int i, len = min_t(int, cf->size / 4 - off, CMD_PACKET_SIZE / 4 / 2 - 1);
		buf[0] = cpu_to_le32(len);
		for (i = 0; i < len; i++, off++) {
			buf[i * 2 + 1] = cpu_to_le32(off);
			memcpy(buf + i * 2 + 2, cf->data + off * 4, 4);
		}
		ret = cxacru_cm(instance, CM_REQUEST_CARD_DATA_SET,
				(u8 *) buf, len, NULL, 0);
		if (ret < 0) {
			err("load config data failed: %d", ret);
			return;
		}
	}

	msleep(4000);

	dbg("done setting up the modem");

	cxacru_adsl_start(instance);
}

static int cxacru_find_firmware(struct cxacru_instance_data *instance,
				char* phase, const struct firmware **fw_p)
{
	char buf[16];

	sprintf(buf, "cxacru-%s.bin", phase);
	dbg("cxacru_find_firmware: looking for %s", buf);

	if (request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) {
		dev_warn(&instance->u.usb_dev->dev, "no stage %s firmware found\n", phase);
		return -ENOENT;
	}

	dev_info(&instance->u.usb_dev->dev, "found firmware %s\n", buf);

	return 0;
}

static int cxacru_load_firmware(void *arg)
{
	const struct firmware *fw, *bp, *cf;
	struct cxacru_instance_data *instance = arg;

	BUG_ON(!instance);

	daemonize("firmware/cxacru");

	if (!cxacru_find_firmware(instance, "fw", &fw)) {
		if (!cxacru_find_firmware(instance, "cf", &cf)) {
			if (!cxacru_find_firmware(instance, "bp", &bp))
				/* ok, assume it's not needed */
				bp = NULL;
			cxacru_upload_firmware(instance, fw, bp, cf);
			release_firmware(cf);
		}
		release_firmware(fw);
	}

	module_put(THIS_MODULE);
	return 0;
}

static void cxacru_firmware_start(struct cxacru_instance_data *instance)
{
	int ret;

	try_module_get(THIS_MODULE);

	ret = kernel_thread(cxacru_load_firmware, instance,
			    CLONE_FS | CLONE_FILES);

	if (ret >= 0)
		return;		/* OK */

	dbg("cxacru_firmware_start: kernel_thread failed (%d)!", ret);

	module_put(THIS_MODULE);
}
#endif /* USE_FW_LOADER */

static int cxacru_usb_ioctl(struct usb_interface *intf, unsigned int code,
			      void *user_data)
{
	struct cxacru_instance_data *instance = usb_get_intfdata(intf);

	dbg("cxacru_usb_ioctl entered");

	if (!instance) {
		dbg("cxacru_usb_ioctl: NULL instance!");
		return -ENODEV;
	}

	switch (code) {
	case UDSL_IOCTL_LINE_UP:
		cxacru_adsl_start(instance);
		return (instance->u.status == UDSL_LOADED_FIRMWARE) ? 0 : -EIO;
	case UDSL_IOCTL_LINE_DOWN:
		cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_STOP, NULL, 0, NULL, 0);
		cxacru_do_timer_poll(instance);
		return 0;
	default:
		return -ENOTTY;
	}
}

static int cxacru_usb_probe(struct usb_interface *intf,
			      const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(intf);
	struct cxacru_instance_data *instance;
	int ret;

	dbg("cxacru_usb_probe: trying device with vendor=0x%x, product=0x%x",
			dev->descriptor.idVendor, dev->descriptor.idProduct);

	/* instance init */
	instance = kmalloc(sizeof(*instance), GFP_KERNEL);
	if (!instance) {
		dbg("cxacru_usb_probe: no memory for instance data");
		return -ENOMEM;
	}

	memset(instance, 0, sizeof(struct cxacru_instance_data));

	instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL);
	if (!instance->rcv_buf) {
		dbg("cxacru_usb_probe: no memory for rcv_buf");
		ret = -ENOMEM;
		goto fail;
	}
	instance->snd_buf = (u8 *) __get_free_page(GFP_KERNEL);
	if (!instance->snd_buf) {
		dbg("cxacru_usb_probe: no memory for snd_buf");
		ret = -ENOMEM;
		goto fail;
	}
	instance->rcv_urb = usb_alloc_urb(0, GFP_KERNEL);
	if (!instance->rcv_urb) {
		dbg("cxacru_usb_probe: no memory for rcv_urb");
		ret = -ENOMEM;
		goto fail;
	}
	instance->snd_urb = usb_alloc_urb(0, GFP_KERNEL);
	if (!instance->snd_urb) {
		dbg("cxacru_usb_probe: no memory for snd_urb");
		ret = -ENOMEM;
		goto fail;
	}

	instance->u.data_endpoint = CXACRU_EP_DATA;
	instance->u.snd_padding = 11;
	instance->u.rcv_padding = 3;
	instance->u.driver_name = cxacru_driver_name;
	instance->u.usb_dev = dev;

	usb_fill_int_urb(instance->rcv_urb, dev, usb_rcvintpipe(dev, CXACRU_EP_CMD),
			 instance->rcv_buf, PAGE_SIZE,
			 cxacru_blocking_completion, &instance->rcv_done, 1);
	instance->rcv_urb->transfer_flags |= URB_ASYNC_UNLINK;

	usb_fill_int_urb(instance->snd_urb, dev, usb_sndintpipe(dev, CXACRU_EP_CMD),
			 instance->snd_buf, PAGE_SIZE,
			 cxacru_blocking_completion, &instance->snd_done, 4);
	instance->snd_urb->transfer_flags |= URB_ASYNC_UNLINK;

	init_MUTEX(&instance->start_serialize);
	init_MUTEX(&instance->cm_serialize);

	init_timer(&instance->poll_timer);
	instance->poll_timer.function = cxacru_timer_poll;
	instance->poll_timer.data = (unsigned long)instance;

	INIT_WORK(&instance->poll_work, (void *)cxacru_poll_status, instance);

	usb_set_intfdata(intf, instance);

	cxacru_adsl_start(instance);

	return 0;

 fail:
	if(instance->snd_buf)
		free_page((unsigned long) instance->snd_buf);
	if(instance->rcv_buf)
		free_page((unsigned long) instance->rcv_buf);
	if(instance->snd_urb)
		usb_free_urb(instance->snd_urb);
	if(instance->rcv_urb)
		usb_free_urb(instance->rcv_urb);
	kfree(instance);

	return ret;
}

static void cxacru_usb_disconnect(struct usb_interface *intf)
{
	struct cxacru_instance_data *instance = usb_get_intfdata(intf);

	dbg("cxacru_usb_disconnect entered");

	if (!instance) {
		dbg("cxacru_usb_disconnect: NULL instance!");
		return;
	}

	del_timer_sync(&instance->poll_timer);
	wmb();
	flush_scheduled_work();

	usb_kill_urb(instance->snd_urb);
	usb_kill_urb(instance->rcv_urb);
	usb_free_urb(instance->snd_urb);
	usb_free_urb(instance->rcv_urb);

	free_page((unsigned long) instance->snd_buf);
	free_page((unsigned long) instance->rcv_buf);

	udsl_instance_disconnect(&instance->u);

	/* clean up */
	usb_set_intfdata(intf, NULL);
	/* FIXME: do we need this refcount? */
	udsl_put_instance(&instance->u);
}

--- NEW FILE cxacru2.h ---
/******************************************************************************
 *  cxacru.h - Generic USB xDSL driver core
 *
 *  Copyright (C) 2001, Alcatel
 *  Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas
 *  Copyright (C) 2004, David Woodhouse
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 *  more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 *  this program; if not, write to the Free Software Foundation, Inc., 59
 *  Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 ******************************************************************************/

#ifndef	_CXACRU_H_
#define	_CXACRU_H_

#include "usbatm.h"

static const struct usbatm_driver cxacru_driver = {
	.driver_name = "Conexant AccessRunner",
};

#endif	/* _CXACRU_H_ */

--- NEW FILE speedtch2.c ---
/******************************************************************************
 *  speedtch.c  -  Alcatel SpeedTouch USB xDSL modem driver
 *
 *  Copyright (C) 2001, Alcatel
 *  Copyright (C) 2003, Duncan Sands
 *  Copyright (C) 2004, David Woodhouse
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 *  more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 *  this program; if not, write to the Free Software Foundation, Inc., 59
 *  Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 ******************************************************************************/

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/gfp.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/errno.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/list.h>
#include <asm/processor.h>
#include <asm/uaccess.h>
#include <linux/smp_lock.h>
#include <linux/interrupt.h>
#include <linux/atm.h>
#include <linux/atmdev.h>
#include <linux/crc32.h>
#include <linux/init.h>
#include <linux/firmware.h>

#include "usb_atm.h"
#include "speedtch2.h"

#if defined(CONFIG_FW_LOADER) || defined(CONFIG_FW_LOADER_MODULE)
#	define USE_FW_LOADER
#endif

#define DRIVER_AUTHOR	"Johan Verrept, Duncan Sands <duncan.sands at free.fr>"
#define DRIVER_VERSION	"1.8"
#define DRIVER_DESC	"Alcatel SpeedTouch USB driver version " DRIVER_VERSION

static const char speedtch_driver_name[] = "speedtch";

#define SPEEDTOUCH_VENDORID		0x06b9
#define SPEEDTOUCH_PRODUCTID		0x4061

/* Timeout in jiffies */
#define CTRL_TIMEOUT (2*HZ)
#define DATA_TIMEOUT (2*HZ)

#define OFFSET_7  0		/* size 1 */
#define OFFSET_b  1		/* size 8 */
#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
#define SIZE_d 4
#define SIZE_e 1
#define SIZE_f 1

static int dl_512_first = 0;
static int sw_buffering = 0;

module_param(dl_512_first, bool, 0444);
MODULE_PARM_DESC(dl_512_first, "Read 512 bytes before sending firmware");

module_param(sw_buffering, uint, 0444);
MODULE_PARM_DESC(sw_buffering, "Enable software buffering");

#define UDSL_IOCTL_LINE_UP		1
#define UDSL_IOCTL_LINE_DOWN		2

#define SPEEDTCH_ENDPOINT_INT		0x81
#define SPEEDTCH_ENDPOINT_DATA		0x07
#define SPEEDTCH_ENDPOINT_FIRMWARE	0x05

#define hex2int(c) ( (c >= '0') && (c <= '9') ? (c - '0') : ((c & 0xf) + 9) )

struct speedtch_instance_data {
	struct udsl_instance_data u;

	/* Status */
	struct urb *int_urb;
	unsigned char int_data[16];
	struct work_struct poll_work;
	struct timer_list poll_timer;
};

/* USB */

static int speedtch_usb_probe(struct usb_interface *intf,
			      const struct usb_device_id *id);
static void speedtch_usb_disconnect(struct usb_interface *intf);
static int speedtch_usb_ioctl(struct usb_interface *intf, unsigned int code,
			      void *user_data);
static void speedtch_handle_int(struct urb *urb, struct pt_regs *regs);
static void speedtch_poll_status(struct speedtch_instance_data *instance);

static struct usb_driver speedtch_usb_driver = {
	.owner		= THIS_MODULE,
	.name		= speedtch_driver_name,
	.probe		= speedtch_usb_probe,
	.disconnect	= speedtch_usb_disconnect,
	.ioctl		= speedtch_usb_ioctl
};

/***************
**  firmware  **
***************/

static void speedtch_got_firmware(struct speedtch_instance_data *instance,
				  int got_it)
{
	int err;
	struct usb_interface *intf;

	down(&instance->u.serialize);	/* vs self, speedtch_firmware_start */
	if (instance->u.status == UDSL_LOADED_FIRMWARE)
		goto out;
	if (!got_it) {
		instance->u.status = UDSL_NO_FIRMWARE;
		goto out;
	}
	if ((err = usb_set_interface(instance->u.usb_dev, 1, 1)) < 0) {
		dbg("speedtch_got_firmware: usb_set_interface returned %d!", err);
		instance->u.status = UDSL_NO_FIRMWARE;
		goto out;
	}

	/* Set up interrupt endpoint */
	intf = usb_ifnum_to_if(instance->u.usb_dev, 0);
	if (intf && !usb_driver_claim_interface(&speedtch_usb_driver, intf, NULL)) {

		instance->int_urb = usb_alloc_urb(0, GFP_KERNEL);
		if (instance->int_urb) {

			usb_fill_int_urb(instance->int_urb, instance->u.usb_dev,
					 usb_rcvintpipe(instance->u.usb_dev, SPEEDTCH_ENDPOINT_INT),
					 instance->int_data,
					 sizeof(instance->int_data),
					 speedtch_handle_int, instance, 50);
			err = usb_submit_urb(instance->int_urb, GFP_KERNEL);
			if (err) {
				/* Doesn't matter; we'll poll anyway */
				dbg("speedtch_got_firmware: Submission of interrupt URB failed %d", err);
				usb_free_urb(instance->int_urb);
				instance->int_urb = NULL;
				usb_driver_release_interface(&speedtch_usb_driver, intf);
			}
		}
	}
	/* Start status polling */
	mod_timer(&instance->poll_timer, jiffies + (1 * HZ));

	instance->u.status = UDSL_LOADED_FIRMWARE;
	tasklet_schedule(&instance->u.receive_tasklet);
 out:
	up(&instance->u.serialize);
	wake_up_interruptible(&instance->u.firmware_waiters);
}

static int speedtch_set_swbuff(struct speedtch_instance_data *instance,
			       int state)
{
	struct usb_device *dev = instance->u.usb_dev;
	int ret;

	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
			      0x32, 0x40, state ? 0x01 : 0x00,
			      0x00, NULL, 0, 100);
	if (ret < 0) {
		printk("Warning: %sabling SW buffering: usb_control_msg returned %d\n",
		     state ? "En" : "Dis", ret);
		return ret;
	}

	dbg("speedtch_set_swbuff: %sbled SW buffering", state ? "En" : "Dis");
	return 0;
}

static void speedtch_test_sequence(struct speedtch_instance_data *instance)
{
	struct usb_device *dev = instance->u.usb_dev;
	unsigned char buf[10];
	int ret;

	/* URB 147 */
	buf[0] = 0x1c;
	buf[1] = 0x50;
	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
			      0x01, 0x40, 0x0b, 0x00, buf, 2, 100);
	if (ret < 0)
		printk(KERN_WARNING "%s failed on URB147: %d\n", __func__, ret);

	/* URB 148 */
	buf[0] = 0x32;
	buf[1] = 0x00;
	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
			      0x01, 0x40, 0x02, 0x00, buf, 2, 100);
	if (ret < 0)
		printk(KERN_WARNING "%s failed on URB148: %d\n", __func__, ret);

	/* URB 149 */
	buf[0] = 0x01;
	buf[1] = 0x00;
	buf[2] = 0x01;
	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
			      0x01, 0x40, 0x03, 0x00, buf, 3, 100);
	if (ret < 0)
		printk(KERN_WARNING "%s failed on URB149: %d\n", __func__, ret);

	/* URB 150 */
	buf[0] = 0x01;
	buf[1] = 0x00;
	buf[2] = 0x01;
	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
			      0x01, 0x40, 0x04, 0x00, buf, 3, 100);
	if (ret < 0)
		printk(KERN_WARNING "%s failed on URB150: %d\n", __func__, ret);
}

static int speedtch_start_synchro(struct speedtch_instance_data *instance)
{
	struct usb_device *dev = instance->u.usb_dev;
	unsigned char buf[2];
	int ret;

	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
			      0x12, 0xc0, 0x04, 0x00,
			      buf, sizeof(buf), CTRL_TIMEOUT);
	if (ret < 0) {
		printk(KERN_WARNING "SpeedTouch: Failed to start ADSL synchronisation: %d\n", ret);
		return ret;
	}

	dbg("speedtch_start_synchro: modem prodded. %d Bytes returned: %02x %02x", ret, buf[0], buf[1]);
	return 0;
}

static void speedtch_handle_int(struct urb *urb, struct pt_regs *regs)
{
	struct speedtch_instance_data *instance = urb->context;
	unsigned int count = urb->actual_length;
	int ret;

	/* The magic interrupt for "up state" */
	const static unsigned char up_int[6]   = { 0xa1, 0x00, 0x01, 0x00, 0x00, 0x00 };
	/* The magic interrupt for "down state" */
	const static unsigned char down_int[6] = { 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00 };

	switch (urb->status) {
	case 0:
		/* success */
		break;
	case -ECONNRESET:
	case -ENOENT:
	case -ESHUTDOWN:
		/* this urb is terminated; clean up */
		dbg("%s - urb shutting down with status: %d", __func__, urb->status);
		return;
	default:
		dbg("%s - nonzero urb status received: %d", __func__, urb->status);
		goto exit;
	}

	if (count < 6) {
		dbg("%s - int packet too short", __func__);
		goto exit;
	}

	if (!memcmp(up_int, instance->int_data, 6)) {
		del_timer(&instance->poll_timer);
		printk(KERN_NOTICE "DSL line goes up\n");
	} else if (!memcmp(down_int, instance->int_data, 6)) {
		printk(KERN_NOTICE "DSL line goes down\n");
	} else {
		int i;

		printk(KERN_DEBUG "Unknown interrupt packet of %d bytes:", count);
		for (i = 0; i < count; i++)
			printk(" %02x", instance->int_data[i]);
		printk("\n");
	}
	schedule_work(&instance->poll_work);

 exit:
	rmb();
	if (!instance->int_urb)
		return;

	ret = usb_submit_urb(urb, GFP_ATOMIC);
	if (ret)
		err("%s - usb_submit_urb failed with result %d", __func__, ret);
}

static int speedtch_get_status(struct speedtch_instance_data *instance,
			       unsigned char *buf)
{
	struct usb_device *dev = instance->u.usb_dev;
	int ret;

	memset(buf, 0, TOTAL);

	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
			      0x12, 0xc0, 0x07, 0x00, buf + OFFSET_7, SIZE_7,
			      CTRL_TIMEOUT);
	if (ret < 0) {
		dbg("MSG 7 failed");
		return ret;
	}

	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
			      0x12, 0xc0, 0x0b, 0x00, buf + OFFSET_b, SIZE_b,
			      CTRL_TIMEOUT);
	if (ret < 0) {
		dbg("MSG B failed");
		return ret;
	}

	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
			      0x12, 0xc0, 0x0d, 0x00, buf + OFFSET_d, SIZE_d,
			      CTRL_TIMEOUT);
	if (ret < 0) {
		dbg("MSG D failed");
		return ret;
	}

	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
			      0x01, 0xc0, 0x0e, 0x00, buf + OFFSET_e, SIZE_e,
			      CTRL_TIMEOUT);
	if (ret < 0) {
		dbg("MSG E failed");
		return ret;
	}

	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
			      0x01, 0xc0, 0x0f, 0x00, buf + OFFSET_f, SIZE_f,
			      CTRL_TIMEOUT);
	if (ret < 0) {
		dbg("MSG F failed");
		return ret;
	}

	return 0;
}

static void speedtch_poll_status(struct speedtch_instance_data *instance)
{
	unsigned char buf[TOTAL];
	int ret;

	ret = speedtch_get_status(instance, buf);
	if (ret) {
		printk(KERN_WARNING
		       "SpeedTouch: Error %d fetching device status\n", ret);
		return;
	}

	dbg("Line state %02x", buf[OFFSET_7]);

	switch (buf[OFFSET_7]) {
	case 0:
		if (instance->u.atm_dev->signal != ATM_PHY_SIG_LOST) {
			instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
			printk(KERN_NOTICE "ADSL line is down\n");
		}
		break;

	case 0x08:
		if (instance->u.atm_dev->signal != ATM_PHY_SIG_UNKNOWN) {
			instance->u.atm_dev->signal = ATM_PHY_SIG_UNKNOWN;
			printk(KERN_NOTICE "ADSL line is blocked?\n");
		}
		break;

	case 0x10:
		if (instance->u.atm_dev->signal != ATM_PHY_SIG_LOST) {
			instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
			printk(KERN_NOTICE "ADSL line is synchronising\n");
		}
		break;

	case 0x20:
		if (instance->u.atm_dev->signal != ATM_PHY_SIG_FOUND) {
			int down_speed = buf[OFFSET_b] | (buf[OFFSET_b + 1] << 8)
				| (buf[OFFSET_b + 2] << 16) | (buf[OFFSET_b + 3] << 24);
			int up_speed = buf[OFFSET_b + 4] | (buf[OFFSET_b + 5] << 8)
				| (buf[OFFSET_b + 6] << 16) | (buf[OFFSET_b + 7] << 24);

			if (!(down_speed & 0x0000ffff) &&
			    !(up_speed & 0x0000ffff)) {
				down_speed >>= 16;
				up_speed >>= 16;
			}
			instance->u.atm_dev->link_rate = down_speed * 1000 / 424;
			instance->u.atm_dev->signal = ATM_PHY_SIG_FOUND;

			printk(KERN_NOTICE
			       "ADSL line is up (%d Kib/s down | %d Kib/s up)\n",
			       down_speed, up_speed);
		}
		break;

	default:
		if (instance->u.atm_dev->signal != ATM_PHY_SIG_UNKNOWN) {
			instance->u.atm_dev->signal = ATM_PHY_SIG_UNKNOWN;
			printk(KERN_NOTICE "Unknown line state %02x\n", buf[OFFSET_7]);
		}
		break;
	}
}

static void speedtch_timer_poll(unsigned long data)
{
	struct speedtch_instance_data *instance = (void *)data;

	schedule_work(&instance->poll_work);
	mod_timer(&instance->poll_timer, jiffies + (5 * HZ));
}

#ifdef USE_FW_LOADER
static void speedtch_upload_firmware(struct speedtch_instance_data *instance,
				     const struct firmware *fw1,
				     const struct firmware *fw2)
{
	unsigned char *buffer;
	struct usb_device *usb_dev = instance->u.usb_dev;
	struct usb_interface *intf;
	int actual_length, ret;
	int offset;

	dbg("speedtch_upload_firmware");

	if (!(intf = usb_ifnum_to_if(usb_dev, 2))) {
		dbg("speedtch_upload_firmware: interface not found!");
		goto fail;
	}

	if (!(buffer = (unsigned char *)__get_free_page(GFP_KERNEL))) {
		dbg("speedtch_upload_firmware: no memory for buffer!");
		goto fail;
	}

	/* A user-space firmware loader may already have claimed interface #2 */
	if ((ret =
	     usb_driver_claim_interface(&speedtch_usb_driver, intf, NULL)) < 0) {
		dbg("speedtch_upload_firmware: interface in use (%d)!", ret);
		goto fail_free;
	}

	/* URB 7 */
	if (dl_512_first) {	/* some modems need a read before writing the firmware */
		ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE),
				   buffer, 0x200, &actual_length, 2 * HZ);

		if (ret < 0 && ret != -ETIMEDOUT)
			dbg("speedtch_upload_firmware: read BLOCK0 from modem failed (%d)!", ret);
		else
			dbg("speedtch_upload_firmware: BLOCK0 downloaded (%d bytes)", ret);
	}

	/* URB 8 : both leds are static green */
	for (offset = 0; offset < fw1->size; offset += PAGE_SIZE) {
		int thislen = min_t(int, PAGE_SIZE, fw1->size - offset);
		memcpy(buffer, fw1->data + offset, thislen);

		ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE),
				   buffer, thislen, &actual_length, DATA_TIMEOUT);

		if (ret < 0) {
			dbg("speedtch_upload_firmware: write BLOCK1 to modem failed (%d)!", ret);
			goto fail_release;
		}
		dbg("speedtch_upload_firmware: BLOCK1 uploaded (%zd bytes)", fw1->size);
	}

	/* USB led blinking green, ADSL led off */

	/* URB 11 */
	ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE),
			   buffer, 0x200, &actual_length, DATA_TIMEOUT);

	if (ret < 0) {
		dbg("speedtch_upload_firmware: read BLOCK2 from modem failed (%d)!", ret);
		goto fail_release;
	}
	dbg("speedtch_upload_firmware: BLOCK2 downloaded (%d bytes)", actual_length);

	/* URBs 12 to 139 - USB led blinking green, ADSL led off */
	for (offset = 0; offset < fw2->size; offset += PAGE_SIZE) {
		int thislen = min_t(int, PAGE_SIZE, fw2->size - offset);
		memcpy(buffer, fw2->data + offset, thislen);

		ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE),
				   buffer, thislen, &actual_length, DATA_TIMEOUT);

		if (ret < 0) {
			dbg("speedtch_upload_firmware: write BLOCK3 to modem failed (%d)!", ret);
			goto fail_release;
		}
	}
	dbg("speedtch_upload_firmware: BLOCK3 uploaded (%zd bytes)", fw2->size);

	/* USB led static green, ADSL led static red */

	/* URB 142 */
	ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, SPEEDTCH_ENDPOINT_FIRMWARE),
			   buffer, 0x200, &actual_length, DATA_TIMEOUT);

	if (ret < 0) {
		dbg("speedtch_upload_firmware: read BLOCK4 from modem failed (%d)!", ret);
		goto fail_release;
	}

	/* success */
	dbg("speedtch_upload_firmware: BLOCK4 downloaded (%d bytes)", actual_length);

	/* Delay to allow firmware to start up. We can do this here
	   because we're in our own kernel thread anyway. */
	msleep(1000);

	/* Enable software buffering, if requested */
	if (sw_buffering)
		speedtch_set_swbuff(instance, 1);

	/* Magic spell; don't ask us what this does */
	speedtch_test_sequence(instance);

	/* Start modem synchronisation */
	if (speedtch_start_synchro(instance))
		dbg("speedtch_start_synchro: failed");

	speedtch_got_firmware(instance, 1);

	free_page((unsigned long)buffer);
	return;

 fail_release:
	/* Only release interface #2 if uploading failed; we don't release it
	   we succeeded.  This prevents the userspace tools from trying to load
	   the firmware themselves */
	usb_driver_release_interface(&speedtch_usb_driver, intf);
 fail_free:
	free_page((unsigned long)buffer);
 fail:
	speedtch_got_firmware(instance, 0);
}

static int speedtch_find_firmware(struct speedtch_instance_data
				  *instance, int phase,
				  const struct firmware **fw_p)
{
	char buf[24];
	const u16 bcdDevice = instance->u.usb_dev->descriptor.bcdDevice;
	const u8 major_revision = bcdDevice >> 8;
	const u8 minor_revision = bcdDevice & 0xff;

	sprintf(buf, "speedtch-%d.bin.%x.%02x", phase, major_revision, minor_revision);
	dbg("speedtch_find_firmware: looking for %s", buf);

	if (request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) {
		sprintf(buf, "speedtch-%d.bin.%x", phase, major_revision);
		dbg("speedtch_find_firmware: looking for %s", buf);

		if (request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) {
			sprintf(buf, "speedtch-%d.bin", phase);
			dbg("speedtch_find_firmware: looking for %s", buf);

			if (request_firmware(fw_p, buf, &instance->u.usb_dev->dev)) {
				dev_warn(&instance->u.usb_dev->dev, "no stage %d firmware found!", phase);
				return -ENOENT;
			}
		}
	}

	dev_info(&instance->u.usb_dev->dev, "found stage %d firmware %s\n", phase, buf);

	return 0;
}

static int speedtch_load_firmware(void *arg)
{
	const struct firmware *fw1, *fw2;
	struct speedtch_instance_data *instance = arg;

	BUG_ON(!instance);

	daemonize("firmware/speedtch");

	if (!speedtch_find_firmware(instance, 1, &fw1)) {
		if (!speedtch_find_firmware(instance, 2, &fw2)) {
			speedtch_upload_firmware(instance, fw1, fw2);
			release_firmware(fw2);
		}
		release_firmware(fw1);
	}

	/* In case we failed, set state back to NO_FIRMWARE so that
	   another later attempt may work. Otherwise, we never actually
	   manage to recover if, for example, the firmware is on /usr and
	   we look for it too early. */
	speedtch_got_firmware(instance, 0);

	module_put(THIS_MODULE);
	udsl_put_instance(&instance->u);
	return 0;
}
#endif /* USE_FW_LOADER */

static void speedtch_firmware_start(struct speedtch_instance_data *instance)
{
#ifdef USE_FW_LOADER
	int ret;
#endif

	dbg("speedtch_firmware_start");

	down(&instance->u.serialize);	/* vs self, speedtch_got_firmware */

	if (instance->u.status >= UDSL_LOADING_FIRMWARE) {
		up(&instance->u.serialize);
		return;
	}

	instance->u.status = UDSL_LOADING_FIRMWARE;
	up(&instance->u.serialize);

#ifdef USE_FW_LOADER
	udsl_get_instance(&instance->u);
	try_module_get(THIS_MODULE);

	ret = kernel_thread(speedtch_load_firmware, instance,
			    CLONE_FS | CLONE_FILES);

	if (ret >= 0)
		return;		/* OK */

	dbg("speedtch_firmware_start: kernel_thread failed (%d)!", ret);

	module_put(THIS_MODULE);
	udsl_put_instance(&instance->u);
	/* Just pretend it never happened... hope modem_run happens */
#endif				/* USE_FW_LOADER */

	speedtch_got_firmware(instance, 0);
}

static int speedtch_firmware_wait(struct udsl_instance_data *instance)
{
	speedtch_firmware_start((void *)instance);

	if (wait_event_interruptible(instance->firmware_waiters, instance->status != UDSL_LOADING_FIRMWARE) < 0)
		return -ERESTARTSYS;

	return (instance->status == UDSL_LOADED_FIRMWARE) ? 0 : -EAGAIN;
}

/**********
**  USB  **
**********/

static int speedtch_usb_ioctl(struct usb_interface *intf, unsigned int code,
			      void *user_data)
{
	struct speedtch_instance_data *instance = usb_get_intfdata(intf);

	dbg("speedtch_usb_ioctl entered");

	if (!instance) {
		dbg("speedtch_usb_ioctl: NULL instance!");
		return -ENODEV;
	}

	switch (code) {
	case UDSL_IOCTL_LINE_UP:
		instance->u.atm_dev->signal = ATM_PHY_SIG_FOUND;
		speedtch_got_firmware(instance, 1);
		return (instance->u.status == UDSL_LOADED_FIRMWARE) ? 0 : -EIO;
	case UDSL_IOCTL_LINE_DOWN:
		instance->u.atm_dev->signal = ATM_PHY_SIG_LOST;
		return 0;
	default:
		return -ENOTTY;
	}
}

static int speedtch_usb_probe(struct usb_interface *intf,
			      const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(intf);
	int ifnum = intf->altsetting->desc.bInterfaceNumber;
	struct speedtch_instance_data *instance;
	unsigned char mac_str[13];
	int ret, i;
	char buf7[SIZE_7];

	dbg("speedtch_usb_probe: trying device with vendor=0x%x, product=0x%x, ifnum %d", dev->descriptor.idVendor, dev->descriptor.idProduct, ifnum);

	if ((dev->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) ||
	    (dev->descriptor.idVendor != SPEEDTOUCH_VENDORID) ||
	    (dev->descriptor.idProduct != SPEEDTOUCH_PRODUCTID) || (ifnum != 1))
		return -ENODEV;

	dbg("speedtch_usb_probe: device accepted");

	/* instance init */
	instance = kmalloc(sizeof(*instance), GFP_KERNEL);
	if (!instance) {
		dbg("speedtch_usb_probe: no memory for instance data!");
		return -ENOMEM;
	}

	memset(instance, 0, sizeof(struct speedtch_instance_data));

	if ((ret = usb_set_interface(dev, 0, 0)) < 0)
		goto fail;

	if ((ret = usb_set_interface(dev, 2, 0)) < 0)
		goto fail;

	instance->u.data_endpoint = SPEEDTCH_ENDPOINT_DATA;
	instance->u.firmware_wait = speedtch_firmware_wait;
	instance->u.driver_name = speedtch_driver_name;

	ret = udsl_instance_setup(dev, &instance->u);
	if (ret)
		goto fail;

	init_timer(&instance->poll_timer);
	instance->poll_timer.function = speedtch_timer_poll;
	instance->poll_timer.data = (unsigned long)instance;

	INIT_WORK(&instance->poll_work, (void *)speedtch_poll_status, instance);

	/* set MAC address, it is stored in the serial number */
	memset(instance->u.atm_dev->esi, 0, sizeof(instance->u.atm_dev->esi));
	if (usb_string(dev, dev->descriptor.iSerialNumber, mac_str, sizeof(mac_str)) == 12) {
		for (i = 0; i < 6; i++)
			instance->u.atm_dev->esi[i] =
				(hex2int(mac_str[i * 2]) * 16) + (hex2int(mac_str[i * 2 + 1]));
	}

	/* First check whether the modem already seems to be alive */
	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
			      0x12, 0xc0, 0x07, 0x00, buf7, SIZE_7, HZ / 2);

	if (ret == SIZE_7) {
		dbg("firmware appears to be already loaded");
		speedtch_got_firmware(instance, 1);
		speedtch_poll_status(instance);
	} else {
		speedtch_firmware_start(instance);
	}

	usb_set_intfdata(intf, instance);

	return 0;

 fail:
	kfree(instance);

	return -ENOMEM;
}

static void speedtch_usb_disconnect(struct usb_interface *intf)
{
	struct speedtch_instance_data *instance = usb_get_intfdata(intf);

	dbg("speedtch_usb_disconnect entered");

	if (!instance) {
		dbg("speedtch_usb_disconnect: NULL instance!");
		return;
	}

	if (instance->int_urb) {
		struct urb *int_urb = instance->int_urb;
		instance->int_urb = NULL;
		wmb();
		usb_unlink_urb(int_urb);
		usb_free_urb(int_urb);
	}

	instance->int_data[0] = 1;
	del_timer_sync(&instance->poll_timer);
	wmb();
	flush_scheduled_work();

	udsl_instance_disconnect(&instance->u);

	/* clean up */
	usb_set_intfdata(intf, NULL);
	udsl_put_instance(&instance->u);
}

--- NEW FILE speedtch2.h ---
/******************************************************************************
 *  speedtch.h - Generic USB xDSL driver core
 *
 *  Copyright (C) 2001, Alcatel
 *  Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas
 *  Copyright (C) 2004, David Woodhouse
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 *  more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 *  this program; if not, write to the Free Software Foundation, Inc., 59
 *  Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 ******************************************************************************/

#ifndef	_SPEEDTCH_H_
#define	_SPEEDTCH_H_

#include "usbatm.h"

static const struct usbatm_driver speedtch_driver = {
	.driver_name = "SpeedTouch USB",
};

#endif	/* _SPEEDTCH_H_ */

--- NEW FILE usbatm2.c ---
/******************************************************************************
 *  usbatm.c - Generic USB xDSL driver core
 *
 *  Copyright (C) 2001, Alcatel
 *  Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas
 *  Copyright (C) 2004, David Woodhouse
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 *  more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 *  this program; if not, write to the Free Software Foundation, Inc., 59
[...1234 lines suppressed...]
**  debug  **
************/

#ifdef VERBOSE_DEBUG
static int udsl_print_packet(const unsigned char *data, int len)
{
	unsigned char buffer[256];
	int i = 0, j = 0;

	for (i = 0; i < len;) {
		buffer[0] = '\0';
		sprintf(buffer, "%.3d :", i);
		for (j = 0; (j < 16) && (i < len); j++, i++) {
			sprintf(buffer, "%s %2.2x", buffer, data[i]);
		}
		dbg("%s", buffer);
	}
	return i;
}
#endif

Index: Makefile
===================================================================
RCS file: /home/cvs/speedtch/Makefile,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -r1.8 -r1.9
--- Makefile	12 Jan 2005 21:16:39 -0000	1.8
+++ Makefile	20 Jan 2005 16:04:20 -0000	1.9
@@ -4,6 +4,13 @@
 
 FIRMWARE_EXTRACTOR := firmware
 
+#usbatm-obj-$(CONFIG_USB_SPEEDTOUCH)	+= speedtch2.o
+usbatm-obj-y	+= speedtch2.o
+#usbatm-obj-$(CONFIG_USB_CXACRU)		+= cxacru2.o
+usbatm-obj-y		+= cxacru2.o
+
+usbatm-objs	:= usbatm2.o $(usbatm-obj-y)
+
 obj-m := cxacru.o speedtch.o usb_atm.o usbatm.o
 
 all help modules modules_install:

--- usbatm.c DELETED ---




More information about the Usbatm-commits mailing list