📄 cxacru.c
字号:
/****************************************************************************** * cxacru.c - driver for USB ADSL modems based on * Conexant AccessRunner chipset * * Copyright (C) 2004 David Woodhouse, Duncan Sands, Roman Kagan * Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru) * * 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. * ******************************************************************************//* * Credit is due for Josep Comas, who created the original patch to speedtch.c * to support the different padding used by the AccessRunner (now generalized * into usbatm), and the userspace firmware loading utility. */#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/kernel.h>#include <linux/timer.h>#include <linux/errno.h>#include <linux/slab.h>#include <linux/init.h>#include <linux/device.h> /* FIXME: linux/firmware.h should include it itself */#include <linux/firmware.h>#include "usbatm.h"#define DRIVER_AUTHOR "Roman Kagan, David Woodhouse, Duncan Sands"#define DRIVER_VERSION "0.2"#define DRIVER_DESC "Conexant AccessRunner ADSL USB modem driver"static const char cxacru_driver_name[] = "cxacru";#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 SDRAM_ENA 0x1#define CMD_TIMEOUT 2000 /* msecs */#define POLL_INTERVAL 5000 /* msecs *//* 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_modem_type { u32 pll_f_clk; u32 pll_b_clk; int boot_rom_patch;};struct cxacru_data { struct usbatm_data *usbatm; const struct cxacru_modem_type *modem_type; int line_status; struct work_struct poll_work; /* 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 + msecs_to_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_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) { dbg("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) { dbg("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) { dbg("sending cm %#x failed", cm); ret = ret; goto fail; } ret = cxacru_start_wait_urb(instance->rcv_urb, &instance->rcv_done, &actlen); if (ret < 0) { dbg("receiving cm %#x failed", cm); ret = ret; goto fail; } if (actlen % CMD_PACKET_SIZE || !actlen) { dbg("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) { dbg("wrong cm %#x in response", rbuf[offb]); ret = -EIO; goto fail; } if (rbuf[offb + 1] != CM_STATUS_SUCCESS) { dbg("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_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) { dbg("wrong data length %#x in response", l); ret = -EIO; goto cleanup; } while (l--) { offd = le32_to_cpu(buf[offb++]); if (offd >= size) { dbg("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 int cxacru_card_status(struct cxacru_data *instance){ int 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); return ret; } return 0;}static void cxacru_poll_status(struct cxacru_data *instance);static int cxacru_atm_start(struct usbatm_data *usbatm_instance, struct atm_dev *atm_dev){ struct cxacru_data *instance = usbatm_instance->driver_data; struct device *dev = &usbatm_instance->usb_intf->dev; /* struct atm_dev *atm_dev = usbatm_instance->atm_dev; */ int ret; dbg("cxacru_atm_start"); /* Read MAC address */ ret = cxacru_cm(instance, CM_REQUEST_CARD_GET_MAC_ADDRESS, NULL, 0, atm_dev->esi, sizeof(atm_dev->esi)); if (ret < 0) { dev_err(dev, "cxacru_atm_start: CARD_GET_MAC_ADDRESS returned %d\n", ret); return ret; } /* start ADSL */ ret = cxacru_cm(instance, CM_REQUEST_CHIP_ADSL_LINE_START, NULL, 0, NULL, 0); if (ret < 0) { dev_err(dev, "cxacru_atm_start: CHIP_ADSL_LINE_START returned %d\n", ret); return ret; } /* Start status polling */ cxacru_poll_status(instance); return 0;}static void cxacru_poll_status(struct cxacru_data *instance){ u32 buf[CXINF_MAX] = {}; struct device *dev = &instance->usbatm->usb_intf->dev; struct atm_dev *atm_dev = instance->usbatm->atm_dev; int ret; ret = cxacru_cm_get_array(instance, CM_REQUEST_CARD_INFO_GET, buf, CXINF_MAX); if (ret < 0) { dev_warn(dev, "poll status: error %d\n", ret); goto reschedule; } if (instance->line_status == buf[CXINF_LINE_STATUS]) goto reschedule; instance->line_status = buf[CXINF_LINE_STATUS]; switch (instance->line_status) { case 0: atm_dev->signal = ATM_PHY_SIG_LOST; dev_info(dev, "ADSL line: down\n"); break; case 1: atm_dev->signal = ATM_PHY_SIG_LOST; dev_info(dev, "ADSL line: attemtping to activate\n"); break; case 2: atm_dev->signal = ATM_PHY_SIG_LOST; dev_info(dev, "ADSL line: training\n"); break; case 3: atm_dev->signal = ATM_PHY_SIG_LOST; dev_info(dev, "ADSL line: channel analysis\n"); break; case 4: atm_dev->signal = ATM_PHY_SIG_LOST; dev_info(dev, "ADSL line: exchange\n"); break; case 5: atm_dev->link_rate = buf[CXINF_DOWNSTREAM_RATE] * 1000 / 424; atm_dev->signal = ATM_PHY_SIG_FOUND; dev_info(dev, "ADSL line: up (%d kb/s down | %d kb/s up)\n", buf[CXINF_DOWNSTREAM_RATE], buf[CXINF_UPSTREAM_RATE]); break; case 6: atm_dev->signal = ATM_PHY_SIG_LOST; dev_info(dev, "ADSL line: waiting\n"); break; case 7: atm_dev->signal = ATM_PHY_SIG_LOST;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -