summaryrefslogtreecommitdiff
path: root/drivers/net/wireless/purelifi/plfxlc/usb.c
diff options
context:
space:
mode:
authorSrinivasan Raju <srini.raju@purelifi.com>2022-02-24 18:20:07 +0000
committerKalle Valo <kvalo@kernel.org>2022-04-25 15:30:42 +0300
commit68d57a07bfe5bb29b80cd8b8fa24c9d1ea104124 (patch)
tree3ea356728b9e97cefc9fa97ac5c3820ae4351680 /drivers/net/wireless/purelifi/plfxlc/usb.c
parent2fb822f82a59db899ba7b3a615cb0ddbc8c04f0f (diff)
wireless: add plfxlc driver for pureLiFi X, XL, XC devices
This is a driver for pureLiFi X, XL, XC devices which use light to transmit data, so they are not compatible with normal Wi-Fi devices. The driver uses separate NL80211_BAND_LC band to distinguish from Wi-Fi. The driver is based on 802.11 softMAC Architecture and uses native 802.11 for configuration and management. Station and Ad-Hoc modes are supported. The driver is compiled and tested in ARM, x86 architectures and compiled in powerpc architecture. This driver implementation has been based on the zd1211rw driver. Signed-off-by: Srinivasan Raju <srini.raju@purelifi.com> Signed-off-by: Kalle Valo <kvalo@kernel.org> Link: https://lore.kernel.org/r/20220224182042.132466-3-srini.raju@purelifi.com
Diffstat (limited to 'drivers/net/wireless/purelifi/plfxlc/usb.c')
-rw-r--r--drivers/net/wireless/purelifi/plfxlc/usb.c892
1 files changed, 892 insertions, 0 deletions
diff --git a/drivers/net/wireless/purelifi/plfxlc/usb.c b/drivers/net/wireless/purelifi/plfxlc/usb.c
new file mode 100644
index 000000000000..6f338b1572a1
--- /dev/null
+++ b/drivers/net/wireless/purelifi/plfxlc/usb.c
@@ -0,0 +1,892 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021 pureLiFi
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+#include <linux/proc_fs.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/module.h>
+#include <net/mac80211.h>
+#include <asm/unaligned.h>
+#include <linux/version.h>
+#include <linux/sysfs.h>
+
+#include "mac.h"
+#include "usb.h"
+#include "chip.h"
+
+static const struct usb_device_id usb_ids[] = {
+ { USB_DEVICE(PURELIFI_X_VENDOR_ID_0, PURELIFI_X_PRODUCT_ID_0),
+ .driver_info = DEVICE_LIFI_X },
+ { USB_DEVICE(PURELIFI_XC_VENDOR_ID_0, PURELIFI_XC_PRODUCT_ID_0),
+ .driver_info = DEVICE_LIFI_XC },
+ { USB_DEVICE(PURELIFI_XL_VENDOR_ID_0, PURELIFI_XL_PRODUCT_ID_0),
+ .driver_info = DEVICE_LIFI_XL },
+ {}
+};
+
+void plfxlc_send_packet_from_data_queue(struct plfxlc_usb *usb)
+{
+ struct plfxlc_usb_tx *tx = &usb->tx;
+ struct sk_buff *skb = NULL;
+ unsigned long flags;
+ u8 last_served_sidx;
+
+ spin_lock_irqsave(&tx->lock, flags);
+ last_served_sidx = usb->sidx;
+ do {
+ usb->sidx = (usb->sidx + 1) % MAX_STA_NUM;
+ if (!(tx->station[usb->sidx].flag & STATION_CONNECTED_FLAG))
+ continue;
+ if (!(tx->station[usb->sidx].flag & STATION_FIFO_FULL_FLAG))
+ skb = skb_peek(&tx->station[usb->sidx].data_list);
+ } while ((usb->sidx != last_served_sidx) && (!skb));
+
+ if (skb) {
+ skb = skb_dequeue(&tx->station[usb->sidx].data_list);
+ plfxlc_usb_wreq_async(usb, skb->data, skb->len, USB_REQ_DATA_TX,
+ plfxlc_tx_urb_complete, skb);
+ if (skb_queue_len(&tx->station[usb->sidx].data_list) <= 60)
+ ieee80211_wake_queues(plfxlc_usb_to_hw(usb));
+ }
+ spin_unlock_irqrestore(&tx->lock, flags);
+}
+
+static void handle_rx_packet(struct plfxlc_usb *usb, const u8 *buffer,
+ unsigned int length)
+{
+ plfxlc_mac_rx(plfxlc_usb_to_hw(usb), buffer, length);
+}
+
+static void rx_urb_complete(struct urb *urb)
+{
+ struct plfxlc_usb_tx *tx;
+ struct plfxlc_usb *usb;
+ unsigned int length;
+ const u8 *buffer;
+ u16 status;
+ u8 sidx;
+ int r;
+
+ if (!urb) {
+ pr_err("urb is NULL\n");
+ return;
+ }
+ if (!urb->context) {
+ pr_err("urb ctx is NULL\n");
+ return;
+ }
+ usb = urb->context;
+
+ if (usb->initialized != 1) {
+ pr_err("usb is not initialized\n");
+ return;
+ }
+
+ tx = &usb->tx;
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ESHUTDOWN:
+ case -EINVAL:
+ case -ENODEV:
+ case -ENOENT:
+ case -ECONNRESET:
+ case -EPIPE:
+ dev_dbg(plfxlc_urb_dev(urb), "urb %p error %d\n", urb, urb->status);
+ return;
+ default:
+ dev_dbg(plfxlc_urb_dev(urb), "urb %p error %d\n", urb, urb->status);
+ if (tx->submitted_urbs++ < PURELIFI_URB_RETRY_MAX) {
+ dev_dbg(plfxlc_urb_dev(urb), "urb %p resubmit %d", urb,
+ tx->submitted_urbs++);
+ goto resubmit;
+ } else {
+ dev_dbg(plfxlc_urb_dev(urb), "urb %p max resubmits reached", urb);
+ tx->submitted_urbs = 0;
+ return;
+ }
+ }
+
+ buffer = urb->transfer_buffer;
+ length = le32_to_cpu(*(__le32 *)(buffer + sizeof(struct rx_status)))
+ + sizeof(u32);
+
+ if (urb->actual_length != (PLF_MSG_STATUS_OFFSET + 1)) {
+ if (usb->initialized && usb->link_up)
+ handle_rx_packet(usb, buffer, length);
+ goto resubmit;
+ }
+
+ status = buffer[PLF_MSG_STATUS_OFFSET];
+
+ switch (status) {
+ case STATION_FIFO_ALMOST_FULL_NOT_MESSAGE:
+ dev_dbg(&usb->intf->dev,
+ "FIFO full not packet receipt\n");
+ tx->mac_fifo_full = 1;
+ for (sidx = 0; sidx < MAX_STA_NUM; sidx++)
+ tx->station[sidx].flag |= STATION_FIFO_FULL_FLAG;
+ break;
+ case STATION_FIFO_ALMOST_FULL_MESSAGE:
+ dev_dbg(&usb->intf->dev, "FIFO full packet receipt\n");
+
+ for (sidx = 0; sidx < MAX_STA_NUM; sidx++)
+ tx->station[sidx].flag &= STATION_ACTIVE_FLAG;
+
+ plfxlc_send_packet_from_data_queue(usb);
+ break;
+ case STATION_CONNECT_MESSAGE:
+ usb->link_up = 1;
+ dev_dbg(&usb->intf->dev, "ST_CONNECT_MSG packet receipt\n");
+ break;
+ case STATION_DISCONNECT_MESSAGE:
+ usb->link_up = 0;
+ dev_dbg(&usb->intf->dev, "ST_DISCONN_MSG packet receipt\n");
+ break;
+ default:
+ dev_dbg(&usb->intf->dev, "Unknown packet receipt\n");
+ break;
+ }
+
+resubmit:
+ r = usb_submit_urb(urb, GFP_ATOMIC);
+ if (r)
+ dev_dbg(plfxlc_urb_dev(urb), "urb %p resubmit fail (%d)\n", urb, r);
+}
+
+static struct urb *alloc_rx_urb(struct plfxlc_usb *usb)
+{
+ struct usb_device *udev = plfxlc_usb_to_usbdev(usb);
+ struct urb *urb;
+ void *buffer;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return NULL;
+
+ buffer = usb_alloc_coherent(udev, USB_MAX_RX_SIZE, GFP_KERNEL,
+ &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;
+}
+
+static void free_rx_urb(struct urb *urb)
+{
+ if (!urb)
+ return;
+ usb_free_coherent(urb->dev, urb->transfer_buffer_length,
+ urb->transfer_buffer, urb->transfer_dma);
+ usb_free_urb(urb);
+}
+
+static int __lf_x_usb_enable_rx(struct plfxlc_usb *usb)
+{
+ struct plfxlc_usb_rx *rx = &usb->rx;
+ struct urb **urbs;
+ int i, r;
+
+ r = -ENOMEM;
+ urbs = kcalloc(RX_URBS_COUNT, sizeof(struct urb *), GFP_KERNEL);
+ if (!urbs)
+ goto error;
+
+ for (i = 0; i < RX_URBS_COUNT; i++) {
+ urbs[i] = alloc_rx_urb(usb);
+ if (!urbs[i])
+ goto error;
+ }
+
+ spin_lock_irq(&rx->lock);
+
+ dev_dbg(plfxlc_usb_dev(usb), "irq_disabled %d\n", irqs_disabled());
+
+ if (rx->urbs) {
+ spin_unlock_irq(&rx->lock);
+ r = 0;
+ goto error;
+ }
+ rx->urbs = urbs;
+ rx->urbs_count = RX_URBS_COUNT;
+ spin_unlock_irq(&rx->lock);
+
+ for (i = 0; i < RX_URBS_COUNT; i++) {
+ r = usb_submit_urb(urbs[i], GFP_KERNEL);
+ if (r)
+ goto error_submit;
+ }
+
+ return 0;
+
+error_submit:
+ for (i = 0; i < RX_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 < RX_URBS_COUNT; i++)
+ free_rx_urb(urbs[i]);
+ }
+ return r;
+}
+
+int plfxlc_usb_enable_rx(struct plfxlc_usb *usb)
+{
+ struct plfxlc_usb_rx *rx = &usb->rx;
+ int r;
+
+ mutex_lock(&rx->setup_mutex);
+ r = __lf_x_usb_enable_rx(usb);
+ if (!r)
+ usb->rx_usb_enabled = 1;
+
+ mutex_unlock(&rx->setup_mutex);
+
+ return r;
+}
+
+static void __lf_x_usb_disable_rx(struct plfxlc_usb *usb)
+{
+ struct plfxlc_usb_rx *rx = &usb->rx;
+ unsigned long flags;
+ unsigned int count;
+ struct urb **urbs;
+ int i;
+
+ 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_rx_urb(urbs[i]);
+ }
+ kfree(urbs);
+ rx->urbs = NULL;
+ rx->urbs_count = 0;
+}
+
+void plfxlc_usb_disable_rx(struct plfxlc_usb *usb)
+{
+ struct plfxlc_usb_rx *rx = &usb->rx;
+
+ mutex_lock(&rx->setup_mutex);
+ __lf_x_usb_disable_rx(usb);
+ usb->rx_usb_enabled = 0;
+ mutex_unlock(&rx->setup_mutex);
+}
+
+void plfxlc_usb_disable_tx(struct plfxlc_usb *usb)
+{
+ struct plfxlc_usb_tx *tx = &usb->tx;
+ unsigned long flags;
+
+ clear_bit(PLF_BIT_ENABLED, &tx->enabled);
+
+ /* kill all submitted tx-urbs */
+ usb_kill_anchored_urbs(&tx->submitted);
+
+ spin_lock_irqsave(&tx->lock, flags);
+ WARN_ON(!skb_queue_empty(&tx->submitted_skbs));
+ WARN_ON(tx->submitted_urbs != 0);
+ tx->submitted_urbs = 0;
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ /* The stopped state is ignored, relying on ieee80211_wake_queues()
+ * in a potentionally following plfxlc_usb_enable_tx().
+ */
+}
+
+void plfxlc_usb_enable_tx(struct plfxlc_usb *usb)
+{
+ struct plfxlc_usb_tx *tx = &usb->tx;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tx->lock, flags);
+ set_bit(PLF_BIT_ENABLED, &tx->enabled);
+ tx->submitted_urbs = 0;
+ ieee80211_wake_queues(plfxlc_usb_to_hw(usb));
+ tx->stopped = 0;
+ spin_unlock_irqrestore(&tx->lock, flags);
+}
+
+void plfxlc_tx_urb_complete(struct urb *urb)
+{
+ struct ieee80211_tx_info *info;
+ struct plfxlc_usb *usb;
+ struct sk_buff *skb;
+
+ skb = urb->context;
+ info = IEEE80211_SKB_CB(skb);
+ /* grab 'usb' pointer before handing off the skb (since
+ * it might be freed by plfxlc_mac_tx_to_dev or mac80211)
+ */
+ usb = &plfxlc_hw_mac(info->rate_driver_data[0])->chip.usb;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ESHUTDOWN:
+ case -EINVAL:
+ case -ENODEV:
+ case -ENOENT:
+ case -ECONNRESET:
+ case -EPIPE:
+ dev_dbg(plfxlc_urb_dev(urb), "urb %p error %d\n", urb, urb->status);
+ break;
+ default:
+ dev_dbg(plfxlc_urb_dev(urb), "urb %p error %d\n", urb, urb->status);
+ return;
+ }
+
+ plfxlc_mac_tx_to_dev(skb, urb->status);
+ plfxlc_send_packet_from_data_queue(usb);
+ usb_free_urb(urb);
+}
+
+static inline void init_usb_rx(struct plfxlc_usb *usb)
+{
+ struct plfxlc_usb_rx *rx = &usb->rx;
+
+ spin_lock_init(&rx->lock);
+ mutex_init(&rx->setup_mutex);
+
+ if (interface_to_usbdev(usb->intf)->speed == USB_SPEED_HIGH)
+ rx->usb_packet_size = 512;
+ else
+ rx->usb_packet_size = 64;
+
+ if (rx->fragment_length != 0)
+ dev_dbg(plfxlc_usb_dev(usb), "fragment_length error\n");
+}
+
+static inline void init_usb_tx(struct plfxlc_usb *usb)
+{
+ struct plfxlc_usb_tx *tx = &usb->tx;
+
+ spin_lock_init(&tx->lock);
+ clear_bit(PLF_BIT_ENABLED, &tx->enabled);
+ tx->stopped = 0;
+ skb_queue_head_init(&tx->submitted_skbs);
+ init_usb_anchor(&tx->submitted);
+}
+
+void plfxlc_usb_init(struct plfxlc_usb *usb, struct ieee80211_hw *hw,
+ struct usb_interface *intf)
+{
+ memset(usb, 0, sizeof(*usb));
+ usb->intf = usb_get_intf(intf);
+ usb_set_intfdata(usb->intf, hw);
+ init_usb_tx(usb);
+ init_usb_rx(usb);
+}
+
+void plfxlc_usb_release(struct plfxlc_usb *usb)
+{
+ plfxlc_op_stop(plfxlc_usb_to_hw(usb));
+ plfxlc_usb_disable_tx(usb);
+ plfxlc_usb_disable_rx(usb);
+ usb_set_intfdata(usb->intf, NULL);
+ usb_put_intf(usb->intf);
+}
+
+const char *plfxlc_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";
+ }
+}
+
+int plfxlc_usb_init_hw(struct plfxlc_usb *usb)
+{
+ int r;
+
+ r = usb_reset_configuration(plfxlc_usb_to_usbdev(usb));
+ if (r) {
+ dev_err(plfxlc_usb_dev(usb), "cfg reset failed (%d)\n", r);
+ return r;
+ }
+ return 0;
+}
+
+static void get_usb_req(struct usb_device *udev, void *buffer,
+ u32 buffer_len, enum plf_usb_req_enum usb_req_id,
+ struct plf_usb_req *usb_req)
+{
+ __be32 payload_len_nw = cpu_to_be32(buffer_len + FCS_LEN);
+ const u8 *buffer_src_p = buffer;
+ u8 *buffer_dst = usb_req->buf;
+ u32 temp_usb_len = 0;
+
+ usb_req->id = cpu_to_be32(usb_req_id);
+ usb_req->len = cpu_to_be32(0);
+
+ /* Copy buffer length into the transmitted buffer, as it is important
+ * for the Rx MAC to know its exact length.
+ */
+ if (usb_req->id == cpu_to_be32(USB_REQ_BEACON_WR)) {
+ memcpy(buffer_dst, &payload_len_nw, sizeof(payload_len_nw));
+ buffer_dst += sizeof(payload_len_nw);
+ temp_usb_len += sizeof(payload_len_nw);
+ }
+
+ memcpy(buffer_dst, buffer_src_p, buffer_len);
+ buffer_dst += buffer_len;
+ buffer_src_p += buffer_len;
+ temp_usb_len += buffer_len;
+
+ /* Set the FCS_LEN (4) bytes as 0 for CRC checking. */
+ memset(buffer_dst, 0, FCS_LEN);
+ buffer_dst += FCS_LEN;
+ temp_usb_len += FCS_LEN;
+
+ /* Round the packet to be transmitted to 4 bytes. */
+ if (temp_usb_len % PURELIFI_BYTE_NUM_ALIGNMENT) {
+ memset(buffer_dst, 0, PURELIFI_BYTE_NUM_ALIGNMENT -
+ (temp_usb_len %
+ PURELIFI_BYTE_NUM_ALIGNMENT));
+ buffer_dst += PURELIFI_BYTE_NUM_ALIGNMENT -
+ (temp_usb_len %
+ PURELIFI_BYTE_NUM_ALIGNMENT);
+ temp_usb_len += PURELIFI_BYTE_NUM_ALIGNMENT -
+ (temp_usb_len % PURELIFI_BYTE_NUM_ALIGNMENT);
+ }
+
+ usb_req->len = cpu_to_be32(temp_usb_len);
+}
+
+int plfxlc_usb_wreq_async(struct plfxlc_usb *usb, const u8 *buffer,
+ int buffer_len, enum plf_usb_req_enum usb_req_id,
+ usb_complete_t complete_fn,
+ void *context)
+{
+ struct usb_device *udev = interface_to_usbdev(usb->ez_usb);
+ struct urb *urb = usb_alloc_urb(0, GFP_ATOMIC);
+ int r;
+
+ usb_fill_bulk_urb(urb, udev, usb_sndbulkpipe(udev, EP_DATA_OUT),
+ (void *)buffer, buffer_len, complete_fn, context);
+
+ r = usb_submit_urb(urb, GFP_ATOMIC);
+ if (r)
+ dev_err(&udev->dev, "Async write submit failed (%d)\n", r);
+
+ return r;
+}
+
+int plfxlc_usb_wreq(struct usb_interface *ez_usb, void *buffer, int buffer_len,
+ enum plf_usb_req_enum usb_req_id)
+{
+ struct usb_device *udev = interface_to_usbdev(ez_usb);
+ unsigned char *dma_buffer = NULL;
+ struct plf_usb_req usb_req;
+ int usb_bulk_msg_len;
+ int actual_length;
+ int r;
+
+ get_usb_req(udev, buffer, buffer_len, usb_req_id, &usb_req);
+ usb_bulk_msg_len = sizeof(__le32) + sizeof(__le32) +
+ be32_to_cpu(usb_req.len);
+
+ dma_buffer = kmemdup(&usb_req, usb_bulk_msg_len, GFP_KERNEL);
+
+ if (!dma_buffer) {
+ r = -ENOMEM;
+ goto error;
+ }
+
+ r = usb_bulk_msg(udev,
+ usb_sndbulkpipe(udev, EP_DATA_OUT),
+ dma_buffer, usb_bulk_msg_len,
+ &actual_length, USB_BULK_MSG_TIMEOUT_MS);
+ kfree(dma_buffer);
+error:
+ if (r) {
+ r = -ENOMEM;
+ dev_err(&udev->dev, "usb_bulk_msg failed (%d)\n", r);
+ }
+
+ return r;
+}
+
+static void slif_data_plane_sap_timer_callb(struct timer_list *t)
+{
+ struct plfxlc_usb *usb = from_timer(usb, t, tx.tx_retry_timer);
+
+ plfxlc_send_packet_from_data_queue(usb);
+ timer_setup(&usb->tx.tx_retry_timer,
+ slif_data_plane_sap_timer_callb, 0);
+ mod_timer(&usb->tx.tx_retry_timer, jiffies + TX_RETRY_BACKOFF_JIFF);
+}
+
+static void sta_queue_cleanup_timer_callb(struct timer_list *t)
+{
+ struct plfxlc_usb *usb = from_timer(usb, t, sta_queue_cleanup);
+ struct plfxlc_usb_tx *tx = &usb->tx;
+ int sidx;
+
+ for (sidx = 0; sidx < MAX_STA_NUM - 1; sidx++) {
+ if (!(tx->station[sidx].flag & STATION_CONNECTED_FLAG))
+ continue;
+ if (tx->station[sidx].flag & STATION_HEARTBEAT_FLAG) {
+ tx->station[sidx].flag ^= STATION_HEARTBEAT_FLAG;
+ } else {
+ memset(tx->station[sidx].mac, 0, ETH_ALEN);
+ tx->station[sidx].flag = 0;
+ }
+ }
+ timer_setup(&usb->sta_queue_cleanup,
+ sta_queue_cleanup_timer_callb, 0);
+ mod_timer(&usb->sta_queue_cleanup, jiffies + STA_QUEUE_CLEANUP_JIFF);
+}
+
+static int probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ u8 serial_number[PURELIFI_SERIAL_LEN];
+ struct ieee80211_hw *hw = NULL;
+ struct plfxlc_usb_tx *tx;
+ struct plfxlc_chip *chip;
+ struct plfxlc_usb *usb;
+ u8 hw_address[ETH_ALEN];
+ unsigned int i;
+ int r = 0;
+
+ hw = plfxlc_mac_alloc_hw(intf);
+
+ if (!hw) {
+ r = -ENOMEM;
+ goto error;
+ }
+
+ chip = &plfxlc_hw_mac(hw)->chip;
+ usb = &chip->usb;
+ usb->ez_usb = intf;
+ tx = &usb->tx;
+
+ r = plfxlc_upload_mac_and_serial(intf, hw_address, serial_number);
+ if (r) {
+ dev_err(&intf->dev, "MAC and Serial upload failed (%d)\n", r);
+ goto error;
+ }
+
+ chip->unit_type = STA;
+ dev_err(&intf->dev, "Unit type is station");
+
+ r = plfxlc_mac_preinit_hw(hw, hw_address);
+ if (r) {
+ dev_err(&intf->dev, "Init mac failed (%d)\n", r);
+ goto error;
+ }
+
+ r = ieee80211_register_hw(hw);
+ if (r) {
+ dev_err(&intf->dev, "Register device failed (%d)\n", r);
+ goto error;
+ }
+
+ if ((le16_to_cpu(interface_to_usbdev(intf)->descriptor.idVendor) ==
+ PURELIFI_XL_VENDOR_ID_0) &&
+ (le16_to_cpu(interface_to_usbdev(intf)->descriptor.idProduct) ==
+ PURELIFI_XL_PRODUCT_ID_0)) {
+ r = plfxlc_download_xl_firmware(intf);
+ } else {
+ r = plfxlc_download_fpga(intf);
+ }
+ if (r != 0) {
+ dev_err(&intf->dev, "FPGA download failed (%d)\n", r);
+ goto error;
+ }
+
+ tx->mac_fifo_full = 0;
+ spin_lock_init(&tx->lock);
+
+ msleep(PLF_MSLEEP_TIME);
+ r = plfxlc_usb_init_hw(usb);
+ if (r < 0) {
+ dev_err(&intf->dev, "usb_init_hw failed (%d)\n", r);
+ goto error;
+ }
+
+ msleep(PLF_MSLEEP_TIME);
+ r = plfxlc_chip_switch_radio(chip, PLFXLC_RADIO_ON);
+ if (r < 0) {
+ dev_dbg(&intf->dev, "chip_switch_radio_on failed (%d)\n", r);
+ goto error;
+ }
+
+ msleep(PLF_MSLEEP_TIME);
+ r = plfxlc_chip_set_rate(chip, 8);
+ if (r < 0) {
+ dev_dbg(&intf->dev, "chip_set_rate failed (%d)\n", r);
+ goto error;
+ }
+
+ msleep(PLF_MSLEEP_TIME);
+ r = plfxlc_usb_wreq(usb->ez_usb,
+ hw_address, ETH_ALEN, USB_REQ_MAC_WR);
+ if (r < 0) {
+ dev_dbg(&intf->dev, "MAC_WR failure (%d)\n", r);
+ goto error;
+ }
+
+ plfxlc_chip_enable_rxtx(chip);
+
+ /* Initialise the data plane Tx queue */
+ for (i = 0; i < MAX_STA_NUM; i++) {
+ skb_queue_head_init(&tx->station[i].data_list);
+ tx->station[i].flag = 0;
+ }
+
+ tx->station[STA_BROADCAST_INDEX].flag |= STATION_CONNECTED_FLAG;
+ for (i = 0; i < ETH_ALEN; i++)
+ tx->station[STA_BROADCAST_INDEX].mac[i] = 0xFF;
+
+ timer_setup(&tx->tx_retry_timer, slif_data_plane_sap_timer_callb, 0);
+ tx->tx_retry_timer.expires = jiffies + TX_RETRY_BACKOFF_JIFF;
+ add_timer(&tx->tx_retry_timer);
+
+ timer_setup(&usb->sta_queue_cleanup,
+ sta_queue_cleanup_timer_callb, 0);
+ usb->sta_queue_cleanup.expires = jiffies + STA_QUEUE_CLEANUP_JIFF;
+ add_timer(&usb->sta_queue_cleanup);
+
+ plfxlc_mac_init_hw(hw);
+ usb->initialized = true;
+ return 0;
+error:
+ if (hw) {
+ plfxlc_mac_release(plfxlc_hw_mac(hw));
+ ieee80211_unregister_hw(hw);
+ ieee80211_free_hw(hw);
+ }
+ dev_err(&intf->dev, "pureLifi:Device error");
+ return r;
+}
+
+static void disconnect(struct usb_interface *intf)
+{
+ struct ieee80211_hw *hw = plfxlc_intf_to_hw(intf);
+ struct plfxlc_mac *mac;
+ struct plfxlc_usb *usb;
+
+ /* Either something really bad happened, or
+ * we're just dealing with a DEVICE_INSTALLER.
+ */
+ if (!hw)
+ return;
+
+ mac = plfxlc_hw_mac(hw);
+ usb = &mac->chip.usb;
+
+ del_timer_sync(&usb->tx.tx_retry_timer);
+ del_timer_sync(&usb->sta_queue_cleanup);
+
+ ieee80211_unregister_hw(hw);
+
+ plfxlc_chip_disable_rxtx(&mac->chip);
+
+ /* 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));
+
+ plfxlc_mac_release(mac);
+ ieee80211_free_hw(hw);
+}
+
+static void plfxlc_usb_resume(struct plfxlc_usb *usb)
+{
+ struct plfxlc_mac *mac = plfxlc_usb_to_mac(usb);
+ int r;
+
+ r = plfxlc_op_start(plfxlc_usb_to_hw(usb));
+ if (r < 0) {
+ dev_warn(plfxlc_usb_dev(usb),
+ "Device resume failed (%d)\n", r);
+
+ if (usb->was_running)
+ set_bit(PURELIFI_DEVICE_RUNNING, &mac->flags);
+
+ usb_queue_reset_device(usb->intf);
+ return;
+ }
+
+ if (mac->type != NL80211_IFTYPE_UNSPECIFIED) {
+ r = plfxlc_restore_settings(mac);
+ if (r < 0) {
+ dev_dbg(plfxlc_usb_dev(usb),
+ "Restore failed (%d)\n", r);
+ return;
+ }
+ }
+}
+
+static void plfxlc_usb_stop(struct plfxlc_usb *usb)
+{
+ plfxlc_op_stop(plfxlc_usb_to_hw(usb));
+ plfxlc_usb_disable_tx(usb);
+ plfxlc_usb_disable_rx(usb);
+
+ usb->initialized = false;
+}
+
+static int pre_reset(struct usb_interface *intf)
+{
+ struct ieee80211_hw *hw = usb_get_intfdata(intf);
+ struct plfxlc_mac *mac;
+ struct plfxlc_usb *usb;
+
+ if (!hw || intf->condition != USB_INTERFACE_BOUND)
+ return 0;
+
+ mac = plfxlc_hw_mac(hw);
+ usb = &mac->chip.usb;
+
+ usb->was_running = test_bit(PURELIFI_DEVICE_RUNNING, &mac->flags);
+
+ plfxlc_usb_stop(usb);
+
+ return 0;
+}
+
+static int post_reset(struct usb_interface *intf)
+{
+ struct ieee80211_hw *hw = usb_get_intfdata(intf);
+ struct plfxlc_mac *mac;
+ struct plfxlc_usb *usb;
+
+ if (!hw || intf->condition != USB_INTERFACE_BOUND)
+ return 0;
+
+ mac = plfxlc_hw_mac(hw);
+ usb = &mac->chip.usb;
+
+ if (usb->was_running)
+ plfxlc_usb_resume(usb);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static struct plfxlc_usb *get_plfxlc_usb(struct usb_interface *intf)
+{
+ struct ieee80211_hw *hw = plfxlc_intf_to_hw(intf);
+ struct plfxlc_mac *mac;
+
+ /* Either something really bad happened, or
+ * we're just dealing with a DEVICE_INSTALLER.
+ */
+ if (!hw)
+ return NULL;
+
+ mac = plfxlc_hw_mac(hw);
+ return &mac->chip.usb;
+}
+
+static int suspend(struct usb_interface *interface,
+ pm_message_t message)
+{
+ struct plfxlc_usb *pl = get_plfxlc_usb(interface);
+ struct plfxlc_mac *mac = plfxlc_usb_to_mac(pl);
+
+ if (!pl || !plfxlc_usb_dev(pl))
+ return -ENODEV;
+ if (pl->initialized == 0)
+ return 0;
+ pl->was_running = test_bit(PURELIFI_DEVICE_RUNNING, &mac->flags);
+ plfxlc_usb_stop(pl);
+ return 0;
+}
+
+static int resume(struct usb_interface *interface)
+{
+ struct plfxlc_usb *pl = get_plfxlc_usb(interface);
+
+ if (!pl || !plfxlc_usb_dev(pl))
+ return -ENODEV;
+ if (pl->was_running)
+ plfxlc_usb_resume(pl);
+ return 0;
+}
+
+#endif
+
+static struct usb_driver driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = usb_ids,
+ .probe = probe,
+ .disconnect = disconnect,
+ .pre_reset = pre_reset,
+ .post_reset = post_reset,
+#ifdef CONFIG_PM
+ .suspend = suspend,
+ .resume = resume,
+#endif
+ .disable_hub_initiated_lpm = 1,
+};
+
+static int __init usb_init(void)
+{
+ int r;
+
+ r = usb_register(&driver);
+ if (r) {
+ pr_err("%s usb_register() failed %d\n", driver.name, r);
+ return r;
+ }
+
+ pr_debug("Driver initialized :%s\n", driver.name);
+ return 0;
+}
+
+static void __exit usb_exit(void)
+{
+ usb_deregister(&driver);
+ pr_debug("%s %s\n", driver.name, __func__);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("USB driver for pureLiFi devices");
+MODULE_AUTHOR("pureLiFi");
+MODULE_VERSION("1.0");
+MODULE_FIRMWARE("plfxlc/lifi-x.bin");
+MODULE_DEVICE_TABLE(usb, usb_ids);
+
+module_init(usb_init);
+module_exit(usb_exit);