aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/zd1211rw/zd_usb.c
diff options
context:
space:
mode:
authorDaniel Drake <dsd@gentoo.org>2006-06-02 17:11:32 +0100
committerJeff Garzik <jeff@garzik.org>2006-07-05 13:42:58 -0400
commite85d0918b54fbd9b38003752f7d665416b06edd8 (patch)
tree6f53e6bb10562eec331defc6811fb6d434eb21e5 /drivers/net/wireless/zd1211rw/zd_usb.c
parent4a232e725b5cc1bc7fc5b177424a9ff8313b23ad (diff)
downloadlinux-e85d0918b54fbd9b38003752f7d665416b06edd8.tar.gz
[PATCH] ZyDAS ZD1211 USB-WLAN driver
There are 60+ USB wifi adapters available on the market based on the ZyDAS ZD1211 chip. Unlike the predecessor (ZD1201), ZD1211 does not have a hardware MAC, so most data operations are coordinated by the device driver. The ZD1211 chip sits alongside an RF transceiver which is also controlled by the driver. Our driver currently supports 2 RF types, we know of one other available in a few marketed products which we will be supporting soon. Our driver also supports the newer revision of ZD1211, called ZD1211B. The initialization and RF operations are slightly different for the new revision, but the main difference is 802.11e support. Our driver does not support the QoS features yet, but we think we know how to use them. This driver is based on ZyDAS's own GPL driver available from www.zydas.com.tw. ZyDAS engineers have been responsive and supportive of our efforts, so thumbs up to them. Additionally, the firmware is redistributable and they have provided device specs. This driver has been written primarily by Ulrich Kunitz and myself. Graham Gower, Greg KH, Remco and Bryan Rittmeyer have also contributed. The developers of ieee80211 and softmac have made our lives so much easier- thanks! We maintain a small info-page: http://zd1211.ath.cx/wiki/DriverRewrite If there is enough time for review, we would like to aim for inclusion in 2.6.18. The driver works nicely as a STA, and can connect to both open and encrypted networks (we are using software-based encryption for now). We will work towards supporting more advanced features in the future (ad-hoc, master mode, 802.11a, ...). Signed-off-by: Daniel Drake <dsd@gentoo.org> Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/net/wireless/zd1211rw/zd_usb.c')
-rw-r--r--drivers/net/wireless/zd1211rw/zd_usb.c1316
1 files changed, 1316 insertions, 0 deletions
diff --git a/drivers/net/wireless/zd1211rw/zd_usb.c b/drivers/net/wireless/zd1211rw/zd_usb.c
new file mode 100644
index 00000000000000..ce1cb2c6aa8d38
--- /dev/null
+++ b/drivers/net/wireless/zd1211rw/zd_usb.c
@@ -0,0 +1,1316 @@
+/* zd_usb.c
+ *
+ * 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 <asm/unaligned.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/skbuff.h>
+#include <linux/usb.h>
+#include <net/ieee80211.h>
+
+#include "zd_def.h"
+#include "zd_netdev.h"
+#include "zd_mac.h"
+#include "zd_usb.h"
+#include "zd_util.h"
+
+static struct usb_device_id usb_ids[] = {
+ /* ZD1211 */
+ { USB_DEVICE(0x0ace, 0x1211), .driver_info = DEVICE_ZD1211 },
+ { USB_DEVICE(0x07b8, 0x6001), .driver_info = DEVICE_ZD1211 },
+ { USB_DEVICE(0x126f, 0xa006), .driver_info = DEVICE_ZD1211 },
+ { USB_DEVICE(0x6891, 0xa727), .driver_info = DEVICE_ZD1211 },
+ { USB_DEVICE(0x0df6, 0x9071), .driver_info = DEVICE_ZD1211 },
+ { USB_DEVICE(0x157e, 0x300b), .driver_info = DEVICE_ZD1211 },
+ /* ZD1211B */
+ { USB_DEVICE(0x0ace, 0x1215), .driver_info = DEVICE_ZD1211B },
+ { USB_DEVICE(0x157e, 0x300d), .driver_info = DEVICE_ZD1211B },
+ {}
+};
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("USB driver for devices with the ZD1211 chip.");
+MODULE_AUTHOR("Ulrich Kunitz");
+MODULE_AUTHOR("Daniel Drake");
+MODULE_VERSION("1.0");
+MODULE_DEVICE_TABLE(usb, usb_ids);
+
+#define FW_ZD1211_PREFIX "zd1211/zd1211_"
+#define FW_ZD1211B_PREFIX "zd1211/zd1211b_"
+
+/* register address handling */
+
+#ifdef DEBUG
+static int check_addr(struct zd_usb *usb, zd_addr_t addr)
+{
+ u32 base = ZD_ADDR_BASE(addr);
+ u32 offset = ZD_OFFSET(addr);
+
+ if ((u32)addr & ADDR_ZERO_MASK)
+ goto invalid_address;
+ switch (base) {
+ case USB_BASE:
+ break;
+ case CR_BASE:
+ if (offset > CR_MAX_OFFSET) {
+ dev_dbg(zd_usb_dev(usb),
+ "CR offset %#010x larger than"
+ " CR_MAX_OFFSET %#10x\n",
+ offset, CR_MAX_OFFSET);
+ goto invalid_address;
+ }
+ if (offset & 1) {
+ dev_dbg(zd_usb_dev(usb),
+ "CR offset %#010x is not a multiple of 2\n",
+ offset);
+ goto invalid_address;
+ }
+ break;
+ case E2P_BASE:
+ if (offset > E2P_MAX_OFFSET) {
+ dev_dbg(zd_usb_dev(usb),
+ "E2P offset %#010x larger than"
+ " E2P_MAX_OFFSET %#010x\n",
+ offset, E2P_MAX_OFFSET);
+ goto invalid_address;
+ }
+ break;
+ case FW_BASE:
+ if (!usb->fw_base_offset) {
+ dev_dbg(zd_usb_dev(usb),
+ "ERROR: fw base offset has not been set\n");
+ return -EAGAIN;
+ }
+ if (offset > FW_MAX_OFFSET) {
+ dev_dbg(zd_usb_dev(usb),
+ "FW offset %#10x is larger than"
+ " FW_MAX_OFFSET %#010x\n",
+ offset, FW_MAX_OFFSET);
+ goto invalid_address;
+ }
+ break;
+ default:
+ dev_dbg(zd_usb_dev(usb),
+ "address has unsupported base %#010x\n", addr);
+ goto invalid_address;
+ }
+
+ return 0;
+invalid_address:
+ dev_dbg(zd_usb_dev(usb),
+ "ERROR: invalid address: %#010x\n", addr);
+ return -EINVAL;
+}
+#endif /* DEBUG */
+
+static u16 usb_addr(struct zd_usb *usb, zd_addr_t addr)
+{
+ u32 base;
+ u16 offset;
+
+ base = ZD_ADDR_BASE(addr);
+ offset = ZD_OFFSET(addr);
+
+ ZD_ASSERT(check_addr(usb, addr) == 0);
+
+ switch (base) {
+ case CR_BASE:
+ offset += CR_BASE_OFFSET;
+ break;
+ case E2P_BASE:
+ offset += E2P_BASE_OFFSET;
+ break;
+ case FW_BASE:
+ offset += usb->fw_base_offset;
+ break;
+ }
+
+ return offset;
+}
+
+/* USB device initialization */
+
+static int request_fw_file(
+ const struct firmware **fw, const char *name, struct device *device)
+{
+ int r;
+
+ dev_dbg_f(device, "fw name %s\n", name);
+
+ r = request_firmware(fw, name, device);
+ if (r)
+ dev_err(device,
+ "Could not load firmware file %s. Error number %d\n",
+ name, r);
+ return r;
+}
+
+static inline u16 get_bcdDevice(const struct usb_device *udev)
+{
+ return le16_to_cpu(udev->descriptor.bcdDevice);
+}
+
+enum upload_code_flags {
+ REBOOT = 1,
+};
+
+/* Ensures that MAX_TRANSFER_SIZE is even. */
+#define MAX_TRANSFER_SIZE (USB_MAX_TRANSFER_SIZE & ~1)
+
+static int upload_code(struct usb_device *udev,
+ const u8 *data, size_t size, u16 code_offset, int flags)
+{
+ u8 *p;
+ int r;
+
+ /* USB request blocks need "kmalloced" buffers.
+ */
+ p = kmalloc(MAX_TRANSFER_SIZE, GFP_KERNEL);
+ if (!p) {
+ dev_err(&udev->dev, "out of memory\n");
+ r = -ENOMEM;
+ goto error;
+ }
+
+ size &= ~1;
+ while (size > 0) {
+ size_t transfer_size = size <= MAX_TRANSFER_SIZE ?
+ size : MAX_TRANSFER_SIZE;
+
+ dev_dbg_f(&udev->dev, "transfer size %zu\n", transfer_size);
+
+ memcpy(p, data, transfer_size);
+ r = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ USB_REQ_FIRMWARE_DOWNLOAD,
+ USB_DIR_OUT | USB_TYPE_VENDOR,
+ code_offset, 0, p, transfer_size, 1000 /* ms */);
+ if (r < 0) {
+ dev_err(&udev->dev,
+ "USB control request for firmware upload"
+ " failed. Error number %d\n", r);
+ goto error;
+ }
+ transfer_size = r & ~1;
+
+ size -= transfer_size;
+ data += transfer_size;
+ code_offset += transfer_size/sizeof(u16);
+ }
+
+ if (flags & REBOOT) {
+ u8 ret;
+
+ r = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ USB_REQ_FIRMWARE_CONFIRM,
+ USB_DIR_IN | USB_TYPE_VENDOR,
+ 0, 0, &ret, sizeof(ret), 5000 /* ms */);
+ if (r != sizeof(ret)) {
+ dev_err(&udev->dev,
+ "control request firmeware confirmation failed."
+ " Return value %d\n", r);
+ if (r >= 0)
+ r = -ENODEV;
+ goto error;
+ }
+ if (ret & 0x80) {
+ dev_err(&udev->dev,
+ "Internal error while downloading."
+ " Firmware confirm return value %#04x\n",
+ (unsigned int)ret);
+ r = -ENODEV;
+ goto error;
+ }
+ dev_dbg_f(&udev->dev, "firmware confirm return value %#04x\n",
+ (unsigned int)ret);
+ }
+
+ r = 0;
+error:
+ kfree(p);
+ return r;
+}
+
+static u16 get_word(const void *data, u16 offset)
+{
+ const __le16 *p = data;
+ return le16_to_cpu(p[offset]);
+}
+
+static char *get_fw_name(char *buffer, size_t size, u8 device_type,
+ const char* postfix)
+{
+ scnprintf(buffer, size, "%s%s",
+ device_type == DEVICE_ZD1211B ?
+ FW_ZD1211B_PREFIX : FW_ZD1211_PREFIX,
+ postfix);
+ return buffer;
+}
+
+static int upload_firmware(struct usb_device *udev, u8 device_type)
+{
+ int r;
+ u16 fw_bcdDevice;
+ u16 bcdDevice;
+ const struct firmware *ub_fw = NULL;
+ const struct firmware *uph_fw = NULL;
+ char fw_name[128];
+
+ bcdDevice = get_bcdDevice(udev);
+
+ r = request_fw_file(&ub_fw,
+ get_fw_name(fw_name, sizeof(fw_name), device_type, "ub"),
+ &udev->dev);
+ if (r)
+ goto error;
+
+ fw_bcdDevice = get_word(ub_fw->data, EEPROM_REGS_OFFSET);
+
+ /* FIXME: do we have any reason to perform the kludge that the vendor
+ * driver does when there is a version mismatch? (their driver uploads
+ * different firmwares and stuff)
+ */
+ if (fw_bcdDevice != bcdDevice) {
+ dev_info(&udev->dev,
+ "firmware device id %#06x and actual device id "
+ "%#06x differ, continuing anyway\n",
+ fw_bcdDevice, bcdDevice);
+ } else {
+ dev_dbg_f(&udev->dev,
+ "firmware device id %#06x is equal to the "
+ "actual device id\n", fw_bcdDevice);
+ }
+
+
+ r = request_fw_file(&uph_fw,
+ get_fw_name(fw_name, sizeof(fw_name), device_type, "uphr"),
+ &udev->dev);
+ if (r)
+ goto error;
+
+ r = upload_code(udev, uph_fw->data, uph_fw->size, FW_START_OFFSET,
+ REBOOT);
+ if (r) {
+ dev_err(&udev->dev,
+ "Could not upload firmware code uph. Error number %d\n",
+ r);
+ }
+
+ /* FALL-THROUGH */
+error:
+ release_firmware(ub_fw);
+ release_firmware(uph_fw);
+ return r;
+}
+
+static void disable_read_regs_int(struct zd_usb *usb)
+{
+ struct zd_usb_interrupt *intr = &usb->intr;
+
+ ZD_ASSERT(in_interrupt());
+ spin_lock(&intr->lock);
+ intr->read_regs_enabled = 0;
+ spin_unlock(&intr->lock);
+}
+
+#define urb_dev(urb) (&(urb)->dev->dev)
+
+static inline void handle_regs_int(struct urb *urb)
+{
+ struct zd_usb *usb = urb->context;
+ struct zd_usb_interrupt *intr = &usb->intr;
+ int len;
+
+ ZD_ASSERT(in_interrupt());
+ spin_lock(&intr->lock);
+
+ if (intr->read_regs_enabled) {
+ intr->read_regs.length = len = urb->actual_length;
+
+ if (len > sizeof(intr->read_regs.buffer))
+ len = sizeof(intr->read_regs.buffer);
+ memcpy(intr->read_regs.buffer, urb->transfer_buffer, len);
+ intr->read_regs_enabled = 0;
+ complete(&intr->read_regs.completion);
+ goto out;
+ }
+
+ dev_dbg_f(urb_dev(urb), "regs interrupt ignored\n");
+out:
+ spin_unlock(&intr->lock);
+}
+
+static inline void handle_retry_failed_int(struct urb *urb)
+{
+ dev_dbg_f(urb_dev(urb), "retry failed interrupt\n");
+}
+
+
+static void int_urb_complete(struct urb *urb, struct pt_regs *pt_regs)
+{
+ int r;
+ struct usb_int_header *hdr;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ESHUTDOWN:
+ case -EINVAL:
+ case -ENODEV:
+ case -ENOENT:
+ case -ECONNRESET:
+ goto kfree;
+ case -EPIPE:
+ usb_clear_halt(urb->dev, EP_INT_IN);
+ /* FALL-THROUGH */
+ default:
+ goto resubmit;
+ }
+
+ if (urb->actual_length < sizeof(hdr)) {
+ dev_dbg_f(urb_dev(urb), "error: urb %p to small\n", urb);
+ goto resubmit;
+ }
+
+ hdr = urb->transfer_buffer;
+ if (hdr->type != USB_INT_TYPE) {
+ dev_dbg_f(urb_dev(urb), "error: urb %p wrong type\n", urb);
+ goto resubmit;
+ }
+
+ switch (hdr->id) {
+ case USB_INT_ID_REGS:
+ handle_regs_int(urb);
+ break;
+ case USB_INT_ID_RETRY_FAILED:
+ handle_retry_failed_int(urb);
+ break;
+ default:
+ dev_dbg_f(urb_dev(urb), "error: urb %p unknown id %x\n", urb,
+ (unsigned int)hdr->id);
+ goto resubmit;
+ }
+
+resubmit:
+ r = usb_submit_urb(urb, GFP_ATOMIC);
+ if (r) {
+ dev_dbg_f(urb_dev(urb), "resubmit urb %p\n", urb);
+ goto kfree;
+ }
+ return;
+kfree:
+ kfree(urb->transfer_buffer);
+}
+
+static inline int int_urb_interval(struct usb_device *udev)
+{
+ switch (udev->speed) {
+ case USB_SPEED_HIGH:
+ return 4;
+ case USB_SPEED_LOW:
+ return 10;
+ case USB_SPEED_FULL:
+ default:
+ return 1;
+ }
+}
+
+static inline int usb_int_enabled(struct zd_usb *usb)
+{
+ unsigned long flags;
+ struct zd_usb_interrupt *intr = &usb->intr;
+ struct urb *urb;
+
+ spin_lock_irqsave(&intr->lock, flags);
+ urb = intr->urb;
+ spin_unlock_irqrestore(&intr->lock, flags);
+ return urb != NULL;
+}
+
+int zd_usb_enable_int(struct zd_usb *usb)
+{
+ int r;
+ struct usb_device *udev;
+ struct zd_usb_interrupt *intr = &usb->intr;
+ void *transfer_buffer = NULL;
+ struct urb *urb;
+
+ dev_dbg_f(zd_usb_dev(usb), "\n");
+
+ urb = usb_alloc_urb(0, GFP_NOFS);
+ if (!urb) {
+ r = -ENOMEM;
+ goto out;
+ }
+
+ ZD_ASSERT(!irqs_disabled());
+ spin_lock_irq(&intr->lock);
+ if (intr->urb) {
+ spin_unlock_irq(&intr->lock);
+ r = 0;
+ goto error_free_urb;
+ }
+ intr->urb = urb;
+ spin_unlock_irq(&intr->lock);
+
+ /* TODO: make it a DMA buffer */
+ r = -ENOMEM;
+ transfer_buffer = kmalloc(USB_MAX_EP_INT_BUFFER, GFP_NOFS);
+ if (!transfer_buffer) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "couldn't allocate transfer_buffer\n");
+ goto error_set_urb_null;
+ }
+
+ udev = zd_usb_to_usbdev(usb);
+ usb_fill_int_urb(urb, udev, usb_rcvintpipe(udev, EP_INT_IN),
+ transfer_buffer, USB_MAX_EP_INT_BUFFER,
+ int_urb_complete, usb,
+ intr->interval);
+
+ dev_dbg_f(zd_usb_dev(usb), "submit urb %p\n", intr->urb);
+ r = usb_submit_urb(urb, GFP_NOFS);
+ if (r) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "Couldn't submit urb. Error number %d\n", r);
+ goto error;
+ }
+
+ return 0;
+error:
+ kfree(transfer_buffer);
+error_set_urb_null:
+ spin_lock_irq(&intr->lock);
+ intr->urb = NULL;
+ spin_unlock_irq(&intr->lock);
+error_free_urb:
+ usb_free_urb(urb);
+out:
+ return r;
+}
+
+void zd_usb_disable_int(struct zd_usb *usb)
+{
+ unsigned long flags;
+ struct zd_usb_interrupt *intr = &usb->intr;
+ struct urb *urb;
+
+ spin_lock_irqsave(&intr->lock, flags);
+ urb = intr->urb;
+ if (!urb) {
+ spin_unlock_irqrestore(&intr->lock, flags);
+ return;
+ }
+ intr->urb = NULL;
+ spin_unlock_irqrestore(&intr->lock, flags);
+
+ usb_kill_urb(urb);
+ dev_dbg_f(zd_usb_dev(usb), "urb %p killed\n", urb);
+ usb_free_urb(urb);
+}
+
+static void handle_rx_packet(struct zd_usb *usb, const u8 *buffer,
+ unsigned int length)
+{
+ int i;
+ struct zd_mac *mac = zd_usb_to_mac(usb);
+ const struct rx_length_info *length_info;
+
+ if (length < sizeof(struct rx_length_info)) {
+ /* It's not a complete packet anyhow. */
+ return;
+ }
+ length_info = (struct rx_length_info *)
+ (buffer + length - sizeof(struct rx_length_info));
+
+ /* It might be that three frames are merged into a single URB
+ * transaction. We have to check for the length info tag.
+ *
+ * While testing we discovered that length_info might be unaligned,
+ * because if USB transactions are merged, the last packet will not
+ * be padded. Unaligned access might also happen if the length_info
+ * structure is not present.
+ */
+ if (get_unaligned(&length_info->tag) == RX_LENGTH_INFO_TAG) {
+ unsigned int l, k, n;
+ for (i = 0, l = 0;; i++) {
+ k = le16_to_cpu(get_unaligned(
+ &length_info->length[i]));
+ n = l+k;
+ if (n > length)
+ return;
+ zd_mac_rx(mac, buffer+l, k);
+ if (i >= 2)
+ return;
+ l = (n+3) & ~3;
+ }
+ } else {
+ zd_mac_rx(mac, buffer, length);
+ }
+}
+
+static void rx_urb_complete(struct urb *urb, struct pt_regs *pt_regs)
+{
+ struct zd_usb *usb;
+ struct zd_usb_rx *rx;
+ const u8 *buffer;
+ unsigned int length;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ESHUTDOWN:
+ case -EINVAL:
+ case -ENODEV:
+ case -ENOENT:
+ case -ECONNRESET:
+ return;
+ case -EPIPE:
+ usb_clear_halt(urb->dev, EP_DATA_IN);
+ /* FALL-THROUGH */
+ default:
+ dev_dbg_f(urb_dev(urb), "urb %p error %d\n", urb, urb->status);
+ goto resubmit;
+ }
+
+ buffer = urb->transfer_buffer;
+ length = urb->actual_length;
+ usb = urb->context;
+ rx = &usb->rx;
+
+ if (length%rx->usb_packet_size > rx->usb_packet_size-4) {
+ /* If there is an old first fragment, we don't care. */
+ dev_dbg_f(urb_dev(urb), "*** first fragment ***\n");
+ ZD_ASSERT(length <= ARRAY_SIZE(rx->fragment));
+ spin_lock(&rx->lock);
+ memcpy(rx->fragment, buffer, length);
+ rx->fragment_length = length;
+ spin_unlock(&rx->lock);
+ goto resubmit;
+ }
+
+ spin_lock(&rx->lock);
+ if (rx->fragment_length > 0) {
+ /* We are on a second fragment, we believe */
+ ZD_ASSERT(length + rx->fragment_length <=
+ ARRAY_SIZE(rx->fragment));
+ dev_dbg_f(urb_dev(urb), "*** second fragment ***\n");
+ memcpy(rx->fragment+rx->fragment_length, buffer, length);
+ handle_rx_packet(usb, rx->fragment,
+ rx->fragment_length + length);
+ rx->fragment_length = 0;
+ spin_unlock(&rx->lock);
+ } else {
+ spin_unlock(&rx->lock);
+ handle_rx_packet(usb, buffer, length);
+ }
+
+resubmit:
+ usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+struct urb *alloc_urb(struct zd_usb *usb)
+{
+ struct usb_device *udev = zd_usb_to_usbdev(usb);
+ struct urb *urb;
+ void *buffer;
+
+ urb = usb_alloc_urb(0, GFP_NOFS);
+ if (!urb)
+ return NULL;
+ buffer = usb_buffer_alloc(udev, USB_MAX_RX_SIZE, GFP_NOFS,
+ &urb->transfer_dma);
+ if (!buffer) {
+ usb_free_urb(urb);
+ return NULL;
+ }
+
+ usb_fill_bulk_urb(urb, udev, usb_rcvbulkpipe(udev, EP_DATA_IN),
+ buffer, USB_MAX_RX_SIZE,
+ rx_urb_complete, usb);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ return urb;
+}
+
+void free_urb(struct urb *urb)
+{
+ if (!urb)
+ return;
+ usb_buffer_free(urb->dev, urb->transfer_buffer_length,
+ urb->transfer_buffer, urb->transfer_dma);
+ usb_free_urb(urb);
+}
+
+int zd_usb_enable_rx(struct zd_usb *usb)
+{
+ int i, r;
+ struct zd_usb_rx *rx = &usb->rx;
+ struct urb **urbs;
+
+ dev_dbg_f(zd_usb_dev(usb), "\n");
+
+ r = -ENOMEM;
+ urbs = kcalloc(URBS_COUNT, sizeof(struct urb *), GFP_NOFS);
+ if (!urbs)
+ goto error;
+ for (i = 0; i < URBS_COUNT; i++) {
+ urbs[i] = alloc_urb(usb);
+ if (!urbs[i])
+ goto error;
+ }
+
+ ZD_ASSERT(!irqs_disabled());
+ spin_lock_irq(&rx->lock);
+ if (rx->urbs) {
+ spin_unlock_irq(&rx->lock);
+ r = 0;
+ goto error;
+ }
+ rx->urbs = urbs;
+ rx->urbs_count = URBS_COUNT;
+ spin_unlock_irq(&rx->lock);
+
+ for (i = 0; i < URBS_COUNT; i++) {
+ r = usb_submit_urb(urbs[i], GFP_NOFS);
+ if (r)
+ goto error_submit;
+ }
+
+ return 0;
+error_submit:
+ for (i = 0; i < URBS_COUNT; i++) {
+ usb_kill_urb(urbs[i]);
+ }
+ spin_lock_irq(&rx->lock);
+ rx->urbs = NULL;
+ rx->urbs_count = 0;
+ spin_unlock_irq(&rx->lock);
+error:
+ if (urbs) {
+ for (i = 0; i < URBS_COUNT; i++)
+ free_urb(urbs[i]);
+ }
+ return r;
+}
+
+void zd_usb_disable_rx(struct zd_usb *usb)
+{
+ int i;
+ unsigned long flags;
+ struct urb **urbs;
+ unsigned int count;
+ struct zd_usb_rx *rx = &usb->rx;
+
+ spin_lock_irqsave(&rx->lock, flags);
+ urbs = rx->urbs;
+ count = rx->urbs_count;
+ spin_unlock_irqrestore(&rx->lock, flags);
+ if (!urbs)
+ return;
+
+ for (i = 0; i < count; i++) {
+ usb_kill_urb(urbs[i]);
+ free_urb(urbs[i]);
+ }
+ kfree(urbs);
+
+ spin_lock_irqsave(&rx->lock, flags);
+ rx->urbs = NULL;
+ rx->urbs_count = 0;
+ spin_unlock_irqrestore(&rx->lock, flags);
+}
+
+static void tx_urb_complete(struct urb *urb, struct pt_regs *pt_regs)
+{
+ int r;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ESHUTDOWN:
+ case -EINVAL:
+ case -ENODEV:
+ case -ENOENT:
+ case -ECONNRESET:
+ dev_dbg_f(urb_dev(urb), "urb %p error %d\n", urb, urb->status);
+ break;
+ case -EPIPE:
+ usb_clear_halt(urb->dev, EP_DATA_OUT);
+ /* FALL-THROUGH */
+ default:
+ dev_dbg_f(urb_dev(urb), "urb %p error %d\n", urb, urb->status);
+ goto resubmit;
+ }
+free_urb:
+ usb_buffer_free(urb->dev, urb->transfer_buffer_length,
+ urb->transfer_buffer, urb->transfer_dma);
+ usb_free_urb(urb);
+ return;
+resubmit:
+ r = usb_submit_urb(urb, GFP_ATOMIC);
+ if (r) {
+ dev_dbg_f(urb_dev(urb), "error resubmit urb %p %d\n", urb, r);
+ goto free_urb;
+ }
+}
+
+/* Puts the frame on the USB endpoint. It doesn't wait for
+ * completion. The frame must contain the control set.
+ */
+int zd_usb_tx(struct zd_usb *usb, const u8 *frame, unsigned int length)
+{
+ int r;
+ struct usb_device *udev = zd_usb_to_usbdev(usb);
+ struct urb *urb;
+ void *buffer;
+
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!urb) {
+ r = -ENOMEM;
+ goto out;
+ }
+
+ buffer = usb_buffer_alloc(zd_usb_to_usbdev(usb), length, GFP_ATOMIC,
+ &urb->transfer_dma);
+ if (!buffer) {
+ r = -ENOMEM;
+ goto error_free_urb;
+ }
+ memcpy(buffer, frame, length);
+
+ usb_fill_bulk_urb(urb, udev, usb_sndbulkpipe(udev, EP_DATA_OUT),
+ buffer, length, tx_urb_complete, NULL);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ r = usb_submit_urb(urb, GFP_ATOMIC);
+ if (r)
+ goto error;
+ return 0;
+error:
+ usb_buffer_free(zd_usb_to_usbdev(usb), length, buffer,
+ urb->transfer_dma);
+error_free_urb:
+ usb_free_urb(urb);
+out:
+ return r;
+}
+
+static inline void init_usb_interrupt(struct zd_usb *usb)
+{
+ struct zd_usb_interrupt *intr = &usb->intr;
+
+ spin_lock_init(&intr->lock);
+ intr->interval = int_urb_interval(zd_usb_to_usbdev(usb));
+ init_completion(&intr->read_regs.completion);
+ intr->read_regs.cr_int_addr = cpu_to_le16(usb_addr(usb, CR_INTERRUPT));
+}
+
+static inline void init_usb_rx(struct zd_usb *usb)
+{
+ struct zd_usb_rx *rx = &usb->rx;
+ spin_lock_init(&rx->lock);
+ if (interface_to_usbdev(usb->intf)->speed == USB_SPEED_HIGH) {
+ rx->usb_packet_size = 512;
+ } else {
+ rx->usb_packet_size = 64;
+ }
+ ZD_ASSERT(rx->fragment_length == 0);
+}
+
+static inline void init_usb_tx(struct zd_usb *usb)
+{
+ /* FIXME: at this point we will allocate a fixed number of urb's for
+ * use in a cyclic scheme */
+}
+
+void zd_usb_init(struct zd_usb *usb, struct net_device *netdev,
+ struct usb_interface *intf)
+{
+ memset(usb, 0, sizeof(*usb));
+ usb->intf = usb_get_intf(intf);
+ usb_set_intfdata(usb->intf, netdev);
+ init_usb_interrupt(usb);
+ init_usb_tx(usb);
+ init_usb_rx(usb);
+}
+
+int zd_usb_init_hw(struct zd_usb *usb)
+{
+ int r;
+ struct zd_chip *chip = zd_usb_to_chip(usb);
+
+ ZD_ASSERT(mutex_is_locked(&chip->mutex));
+ r = zd_ioread16_locked(chip, &usb->fw_base_offset,
+ USB_REG((u16)FW_BASE_ADDR_OFFSET));
+ if (r)
+ return r;
+ dev_dbg_f(zd_usb_dev(usb), "fw_base_offset: %#06hx\n",
+ usb->fw_base_offset);
+
+ return 0;
+}
+
+void zd_usb_clear(struct zd_usb *usb)
+{
+ usb_set_intfdata(usb->intf, NULL);
+ usb_put_intf(usb->intf);
+ memset(usb, 0, sizeof(*usb));
+ /* FIXME: usb_interrupt, usb_tx, usb_rx? */
+}
+
+static const char *speed(enum usb_device_speed speed)
+{
+ switch (speed) {
+ case USB_SPEED_LOW:
+ return "low";
+ case USB_SPEED_FULL:
+ return "full";
+ case USB_SPEED_HIGH:
+ return "high";
+ default:
+ return "unknown speed";
+ }
+}
+
+static int scnprint_id(struct usb_device *udev, char *buffer, size_t size)
+{
+ return scnprintf(buffer, size, "%04hx:%04hx v%04hx %s",
+ le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct),
+ get_bcdDevice(udev),
+ speed(udev->speed));
+}
+
+int zd_usb_scnprint_id(struct zd_usb *usb, char *buffer, size_t size)
+{
+ struct usb_device *udev = interface_to_usbdev(usb->intf);
+ return scnprint_id(udev, buffer, size);
+}
+
+#ifdef DEBUG
+static void print_id(struct usb_device *udev)
+{
+ char buffer[40];
+
+ scnprint_id(udev, buffer, sizeof(buffer));
+ buffer[sizeof(buffer)-1] = 0;
+ dev_dbg_f(&udev->dev, "%s\n", buffer);
+}
+#else
+#define print_id(udev) do { } while (0)
+#endif
+
+static int probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ int r;
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct net_device *netdev = NULL;
+
+ print_id(udev);
+
+ switch (udev->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ case USB_SPEED_HIGH:
+ break;
+ default:
+ dev_dbg_f(&intf->dev, "Unknown USB speed\n");
+ r = -ENODEV;
+ goto error;
+ }
+
+ netdev = zd_netdev_alloc(intf);
+ if (netdev == NULL) {
+ r = -ENOMEM;
+ goto error;
+ }
+
+ r = upload_firmware(udev, id->driver_info);
+ if (r) {
+ dev_err(&intf->dev,
+ "couldn't load firmware. Error number %d\n", r);
+ goto error;
+ }
+
+ r = usb_reset_configuration(udev);
+ if (r) {
+ dev_dbg_f(&intf->dev,
+ "couldn't reset configuration. Error number %d\n", r);
+ goto error;
+ }
+
+ /* At this point the interrupt endpoint is not generally enabled. We
+ * save the USB bandwidth until the network device is opened. But
+ * notify that the initialization of the MAC will require the
+ * interrupts to be temporary enabled.
+ */
+ r = zd_mac_init_hw(zd_netdev_mac(netdev), id->driver_info);
+ if (r) {
+ dev_dbg_f(&intf->dev,
+ "couldn't initialize mac. Error number %d\n", r);
+ goto error;
+ }
+
+ r = register_netdev(netdev);
+ if (r) {
+ dev_dbg_f(&intf->dev,
+ "couldn't register netdev. Error number %d\n", r);
+ goto error;
+ }
+
+ dev_dbg_f(&intf->dev, "successful\n");
+ dev_info(&intf->dev,"%s\n", netdev->name);
+ return 0;
+error:
+ usb_reset_device(interface_to_usbdev(intf));
+ zd_netdev_free(netdev);
+ return r;
+}
+
+static void disconnect(struct usb_interface *intf)
+{
+ struct net_device *netdev = zd_intf_to_netdev(intf);
+ struct zd_mac *mac = zd_netdev_mac(netdev);
+ struct zd_usb *usb = &mac->chip.usb;
+
+ dev_dbg_f(zd_usb_dev(usb), "\n");
+
+ zd_netdev_disconnect(netdev);
+
+ /* Just in case something has gone wrong! */
+ zd_usb_disable_rx(usb);
+ zd_usb_disable_int(usb);
+
+ /* If the disconnect has been caused by a removal of the
+ * driver module, the reset allows reloading of the driver. If the
+ * reset will not be executed here, the upload of the firmware in the
+ * probe function caused by the reloading of the driver will fail.
+ */
+ usb_reset_device(interface_to_usbdev(intf));
+
+ /* If somebody still waits on this lock now, this is an error. */
+ zd_netdev_free(netdev);
+ dev_dbg(&intf->dev, "disconnected\n");
+}
+
+static struct usb_driver driver = {
+ .name = "zd1211rw",
+ .id_table = usb_ids,
+ .probe = probe,
+ .disconnect = disconnect,
+};
+
+static int __init usb_init(void)
+{
+ int r;
+
+ pr_debug("usb_init()\n");
+
+ r = usb_register(&driver);
+ if (r) {
+ printk(KERN_ERR "usb_register() failed. Error number %d\n", r);
+ return r;
+ }
+
+ pr_debug("zd1211rw initialized\n");
+ return 0;
+}
+
+static void __exit usb_exit(void)
+{
+ pr_debug("usb_exit()\n");
+ usb_deregister(&driver);
+}
+
+module_init(usb_init);
+module_exit(usb_exit);
+
+static int usb_int_regs_length(unsigned int count)
+{
+ return sizeof(struct usb_int_regs) + count * sizeof(struct reg_data);
+}
+
+static void prepare_read_regs_int(struct zd_usb *usb)
+{
+ struct zd_usb_interrupt *intr = &usb->intr;
+
+ spin_lock(&intr->lock);
+ intr->read_regs_enabled = 1;
+ INIT_COMPLETION(intr->read_regs.completion);
+ spin_unlock(&intr->lock);
+}
+
+static int get_results(struct zd_usb *usb, u16 *values,
+ struct usb_req_read_regs *req, unsigned int count)
+{
+ int r;
+ int i;
+ struct zd_usb_interrupt *intr = &usb->intr;
+ struct read_regs_int *rr = &intr->read_regs;
+ struct usb_int_regs *regs = (struct usb_int_regs *)rr->buffer;
+
+ spin_lock(&intr->lock);
+
+ r = -EIO;
+ /* The created block size seems to be larger than expected.
+ * However results appear to be correct.
+ */
+ if (rr->length < usb_int_regs_length(count)) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error: actual length %d less than expected %d\n",
+ rr->length, usb_int_regs_length(count));
+ goto error_unlock;
+ }
+ if (rr->length > sizeof(rr->buffer)) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error: actual length %d exceeds buffer size %zu\n",
+ rr->length, sizeof(rr->buffer));
+ goto error_unlock;
+ }
+
+ for (i = 0; i < count; i++) {
+ struct reg_data *rd = &regs->regs[i];
+ if (rd->addr != req->addr[i]) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "rd[%d] addr %#06hx expected %#06hx\n", i,
+ le16_to_cpu(rd->addr),
+ le16_to_cpu(req->addr[i]));
+ goto error_unlock;
+ }
+ values[i] = le16_to_cpu(rd->value);
+ }
+
+ r = 0;
+error_unlock:
+ spin_unlock(&intr->lock);
+ return r;
+}
+
+int zd_usb_ioread16v(struct zd_usb *usb, u16 *values,
+ const zd_addr_t *addresses, unsigned int count)
+{
+ int r;
+ int i, req_len, actual_req_len;
+ struct usb_device *udev;
+ struct usb_req_read_regs *req = NULL;
+ unsigned long timeout;
+
+ if (count < 1) {
+ dev_dbg_f(zd_usb_dev(usb), "error: count is zero\n");
+ return -EINVAL;
+ }
+ if (count > USB_MAX_IOREAD16_COUNT) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error: count %u exceeds possible max %u\n",
+ count, USB_MAX_IOREAD16_COUNT);
+ return -EINVAL;
+ }
+ if (in_atomic()) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error: io in atomic context not supported\n");
+ return -EWOULDBLOCK;
+ }
+ if (!usb_int_enabled(usb)) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error: usb interrupt not enabled\n");
+ return -EWOULDBLOCK;
+ }
+
+ req_len = sizeof(struct usb_req_read_regs) + count * sizeof(__le16);
+ req = kmalloc(req_len, GFP_NOFS);
+ if (!req)
+ return -ENOMEM;
+ req->id = cpu_to_le16(USB_REQ_READ_REGS);
+ for (i = 0; i < count; i++)
+ req->addr[i] = cpu_to_le16(usb_addr(usb, addresses[i]));
+
+ udev = zd_usb_to_usbdev(usb);
+ prepare_read_regs_int(usb);
+ r = usb_bulk_msg(udev, usb_sndbulkpipe(udev, EP_REGS_OUT),
+ req, req_len, &actual_req_len, 1000 /* ms */);
+ if (r) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error in usb_bulk_msg(). Error number %d\n", r);
+ goto error;
+ }
+ if (req_len != actual_req_len) {
+ dev_dbg_f(zd_usb_dev(usb), "error in usb_bulk_msg()\n"
+ " req_len %d != actual_req_len %d\n",
+ req_len, actual_req_len);
+ r = -EIO;
+ goto error;
+ }
+
+ timeout = wait_for_completion_timeout(&usb->intr.read_regs.completion,
+ msecs_to_jiffies(1000));
+ if (!timeout) {
+ disable_read_regs_int(usb);
+ dev_dbg_f(zd_usb_dev(usb), "read timed out\n");
+ r = -ETIMEDOUT;
+ goto error;
+ }
+
+ r = get_results(usb, values, req, count);
+error:
+ kfree(req);
+ return r;
+}
+
+int zd_usb_iowrite16v(struct zd_usb *usb, const struct zd_ioreq16 *ioreqs,
+ unsigned int count)
+{
+ int r;
+ struct usb_device *udev;
+ struct usb_req_write_regs *req = NULL;
+ int i, req_len, actual_req_len;
+
+ if (count == 0)
+ return 0;
+ if (count > USB_MAX_IOWRITE16_COUNT) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error: count %u exceeds possible max %u\n",
+ count, USB_MAX_IOWRITE16_COUNT);
+ return -EINVAL;
+ }
+ if (in_atomic()) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error: io in atomic context not supported\n");
+ return -EWOULDBLOCK;
+ }
+
+ req_len = sizeof(struct usb_req_write_regs) +
+ count * sizeof(struct reg_data);
+ req = kmalloc(req_len, GFP_NOFS);
+ if (!req)
+ return -ENOMEM;
+
+ req->id = cpu_to_le16(USB_REQ_WRITE_REGS);
+ for (i = 0; i < count; i++) {
+ struct reg_data *rw = &req->reg_writes[i];
+ rw->addr = cpu_to_le16(usb_addr(usb, ioreqs[i].addr));
+ rw->value = cpu_to_le16(ioreqs[i].value);
+ }
+
+ udev = zd_usb_to_usbdev(usb);
+ r = usb_bulk_msg(udev, usb_sndbulkpipe(udev, EP_REGS_OUT),
+ req, req_len, &actual_req_len, 1000 /* ms */);
+ if (r) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error in usb_bulk_msg(). Error number %d\n", r);
+ goto error;
+ }
+ if (req_len != actual_req_len) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error in usb_bulk_msg()"
+ " req_len %d != actual_req_len %d\n",
+ req_len, actual_req_len);
+ r = -EIO;
+ goto error;
+ }
+
+ /* FALL-THROUGH with r == 0 */
+error:
+ kfree(req);
+ return r;
+}
+
+int zd_usb_rfwrite(struct zd_usb *usb, u32 value, u8 bits)
+{
+ int r;
+ struct usb_device *udev;
+ struct usb_req_rfwrite *req = NULL;
+ int i, req_len, actual_req_len;
+ u16 bit_value_template;
+
+ if (in_atomic()) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error: io in atomic context not supported\n");
+ return -EWOULDBLOCK;
+ }
+ if (bits < USB_MIN_RFWRITE_BIT_COUNT) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error: bits %d are smaller than"
+ " USB_MIN_RFWRITE_BIT_COUNT %d\n",
+ bits, USB_MIN_RFWRITE_BIT_COUNT);
+ return -EINVAL;
+ }
+ if (bits > USB_MAX_RFWRITE_BIT_COUNT) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error: bits %d exceed USB_MAX_RFWRITE_BIT_COUNT %d\n",
+ bits, USB_MAX_RFWRITE_BIT_COUNT);
+ return -EINVAL;
+ }
+#ifdef DEBUG
+ if (value & (~0UL << bits)) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error: value %#09x has bits >= %d set\n",
+ value, bits);
+ return -EINVAL;
+ }
+#endif /* DEBUG */
+
+ dev_dbg_f(zd_usb_dev(usb), "value %#09x bits %d\n", value, bits);
+
+ r = zd_usb_ioread16(usb, &bit_value_template, CR203);
+ if (r) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error %d: Couldn't read CR203\n", r);
+ goto out;
+ }
+ bit_value_template &= ~(RF_IF_LE|RF_CLK|RF_DATA);
+
+ req_len = sizeof(struct usb_req_rfwrite) + bits * sizeof(__le16);
+ req = kmalloc(req_len, GFP_NOFS);
+ if (!req)
+ return -ENOMEM;
+
+ req->id = cpu_to_le16(USB_REQ_WRITE_RF);
+ /* 1: 3683a, but not used in ZYDAS driver */
+ req->value = cpu_to_le16(2);
+ req->bits = cpu_to_le16(bits);
+
+ for (i = 0; i < bits; i++) {
+ u16 bv = bit_value_template;
+ if (value & (1 << (bits-1-i)))
+ bv |= RF_DATA;
+ req->bit_values[i] = cpu_to_le16(bv);
+ }
+
+ udev = zd_usb_to_usbdev(usb);
+ r = usb_bulk_msg(udev, usb_sndbulkpipe(udev, EP_REGS_OUT),
+ req, req_len, &actual_req_len, 1000 /* ms */);
+ if (r) {
+ dev_dbg_f(zd_usb_dev(usb),
+ "error in usb_bulk_msg(). Error number %d\n", r);
+ goto out;
+ }
+ if (req_len != actual_req_len) {
+ dev_dbg_f(zd_usb_dev(usb), "error in usb_bulk_msg()"
+ " req_len %d != actual_req_len %d\n",
+ req_len, actual_req_len);
+ r = -EIO;
+ goto out;
+ }
+
+ /* FALL-THROUGH with r == 0 */
+out:
+ kfree(req);
+ return r;
+}