diff options
author | Srinivasan Raju <srini.raju@purelifi.com> | 2022-02-24 18:20:07 +0000 |
---|---|---|
committer | Kalle Valo <kvalo@kernel.org> | 2022-04-25 15:30:42 +0300 |
commit | 68d57a07bfe5bb29b80cd8b8fa24c9d1ea104124 (patch) | |
tree | 3ea356728b9e97cefc9fa97ac5c3820ae4351680 /drivers/net/wireless/purelifi/plfxlc/usb.c | |
parent | 2fb822f82a59db899ba7b3a615cb0ddbc8c04f0f (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.c | 892 |
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); |