M7350/external/compat-wireless/drivers/net/wireless/ath/ath6kl-3.5/usb.c
2024-09-09 08:57:42 +00:00

3264 lines
82 KiB
C
Executable File

/*
* Copyright (c) 2007-2011 Atheros Communications Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/reboot.h>
#include <linux/module.h>
#include <linux/usb.h>
#include "debug.h"
#include "core.h"
#include "cfg80211.h"
#ifdef CE_OLD_KERNEL_SUPPORT_2_6_23
#include <asm/unaligned.h>
#endif
/* constants */
#define TX_URB_COUNT 32
#define TX_URB_COUNT_MCC 10 /* prefer to queue in host side */
#define RX_URB_COUNT 32
#define ATH6KL_USB_RX_BUFFER_SIZE 2048
#define ATH6KL_USB_RX_BUNDLE_BUFFER_SIZE 16896
#define ATH6KL_USB_TX_BUNDLE_BUFFER_SIZE 16384
#define WORKER_LOCK_BIT 0
#define ATH6KL_MAX_AMSDU_SIZE 7935
/* tx/rx pipes for usb */
enum ATH6KL_USB_PIPE_ID {
ATH6KL_USB_PIPE_TX_CTRL = 0,
ATH6KL_USB_PIPE_TX_DATA_LP,
ATH6KL_USB_PIPE_TX_DATA_MP,
ATH6KL_USB_PIPE_TX_DATA_HP,
ATH6KL_USB_PIPE_TX_DATA_VHP,
ATH6KL_USB_PIPE_RX_CTRL,
ATH6KL_USB_PIPE_RX_DATA,
ATH6KL_USB_PIPE_RX_DATA2,
ATH6KL_USB_PIPE_RX_INT,
ATH6KL_USB_PIPE_MAX
};
#define ATH6KL_USB_PIPE_INVALID ATH6KL_USB_PIPE_MAX
struct ath6kl_usb_pipe_stat {
u32 num_rx_comp;
u32 num_tx_comp;
u32 num_io_comp;
u32 num_max_tx;
u32 num_max_rx;
u32 num_tx_resche;
u32 num_rx_resche;
u32 num_tx_sync;
u32 num_tx;
u32 num_tx_err;
u32 num_tx_err_others;
u32 num_tx_comp_err;
u32 num_rx_comp_err;
u32 num_rx_bundle_comp;
u32 num_tx_bundle_comp;
u32 num_tx_multi;
u32 num_tx_multi_err;
u32 num_tx_multi_err_others;
u32 num_rx_bundle_comp_err;
u32 num_tx_bundle_comp_err;
};
struct ath6kl_usb_pipe {
struct list_head urb_list_head;
struct usb_anchor urb_submitted;
u32 urb_alloc;
u32 urb_cnt;
u32 urb_cnt_thresh; /* IN */
u32 urb_cnt_thresh_out; /* OUT */
unsigned int usb_pipe_handle;
u32 flags;
u8 ep_address;
u8 logical_pipe_num;
struct ath6kl_usb *ar_usb;
u16 max_packet_size;
struct work_struct io_complete_work;
struct sk_buff_head io_comp_queue;
struct usb_endpoint_descriptor *ep_desc;
struct ath6kl_usb_pipe_stat usb_pipe_stat;
unsigned long worker_lock;
};
#define ATH6KL_USB_PIPE_FLAG_TX (1 << 0)
#define ATH6KL_USB_PIPE_FLAG_RX (1 << 1)
/* usb device object */
struct ath6kl_usb {
spinlock_t cs_lock;
spinlock_t tx_lock;
spinlock_t rx_lock;
struct ath6kl_hif_pipe_callbacks htc_callbacks;
struct usb_device *udev;
struct usb_interface *interface;
struct ath6kl_usb_pipe pipes[ATH6KL_USB_PIPE_MAX];
u8 *diag_cmd_buffer;
u8 *diag_resp_buffer;
struct ath6kl *ar;
struct notifier_block reboot_notifier; /* default mode before reboot */
u32 max_sche_tx;
u32 max_sche_rx;
u32 rxq_threshold;
/* statis */
u32 suspend_cnt;
u32 resume_cnt;
u32 pm_suspend_cnt;
u32 pm_resume_cnt;
u32 pm_reset_resume_cnt;
};
/* usb urb object */
struct ath6kl_urb_context {
struct list_head link;
struct ath6kl_usb_pipe *pipe;
struct sk_buff *buf;
struct ath6kl *ar;
struct sk_buff_head comp_queue;
};
/* USB endpoint definitions */
#define ATH6KL_USB_EP_ADDR_APP_CTRL_IN 0x81
#define ATH6KL_USB_EP_ADDR_APP_DATA_IN 0x82
#define ATH6KL_USB_EP_ADDR_APP_DATA2_IN 0x83
#define ATH6KL_USB_EP_ADDR_APP_INT_IN 0x84
#define ATH6KL_USB_EP_ADDR_APP_CTRL_OUT 0x01
#define ATH6KL_USB_EP_ADDR_APP_DATA_LP_OUT 0x02
#define ATH6KL_USB_EP_ADDR_APP_DATA_MP_OUT 0x03
#define ATH6KL_USB_EP_ADDR_APP_DATA_HP_OUT 0x04
#define ATH6KL_USB_EP_ADDR_APP_DATA_VHP_OUT 0x05
/* diagnostic command defnitions */
#define ATH6KL_USB_CONTROL_REQ_SEND_BMI_CMD 1
#define ATH6KL_USB_CONTROL_REQ_RECV_BMI_RESP 2
#define ATH6KL_USB_CONTROL_REQ_DIAG_CMD 3
#define ATH6KL_USB_CONTROL_REQ_DIAG_RESP 4
#define ATH6KL_USB_CTRL_DIAG_CC_READ 0
#define ATH6KL_USB_CTRL_DIAG_CC_WRITE 1
#define ATH6KL_USB_CTRL_DIAG_CC_WARM_RESET 2
#define HIF_USB_RX_QUEUE_THRESHOLD 64
/* Enable it by default */
#define HIF_USB_MAX_SCHE_PKT (64)
struct ath6kl_usb_ctrl_diag_cmd_write {
__le32 cmd;
__le32 address;
__le32 value;
__le32 _pad[1];
} __packed;
struct ath6kl_usb_ctrl_diag_cmd_read {
__le32 cmd;
__le32 address;
} __packed;
struct ath6kl_usb_ctrl_diag_resp_read {
__le32 value;
} __packed;
#define USB_CTRL_MAX_DIAG_CMD_SIZE \
(sizeof(struct ath6kl_usb_ctrl_diag_cmd_write))
#define USB_CTRL_MAX_DIAG_RESP_SIZE \
(sizeof(struct ath6kl_usb_ctrl_diag_resp_read))
#ifdef ATH6KL_BUS_VOTE
u8 ath6kl_platform_has_vreg;
struct semaphore usb_probe_sem;
#define USB_PROBE_WAIT_TIMEOUT 4000
#define USB_PROBE_WAIT_TIMEOUT_ENUM_WAR 8000
#endif
#ifdef ATH6KL_HSIC_RECOVER
enum ath6kl_hsic_recover_state {
ATH6KL_RECOVER_STATE_INITIALIZED = 0,
ATH6KL_RECOVER_STATE_IN_PROGRESS,
ATH6KL_RECOVER_STATE_DONE,
};
#define ATH6KL_RECOVER_WAIT_TIMEOUT (8*HZ)
static wait_queue_head_t ath6kl_hsic_recover_wq;
static atomic_t ath6kl_recover_state;
struct work_struct recover_war_work;
#endif
#if defined(ATH6KL_BUS_VOTE) || defined(ATH6KL_HSIC_RECOVER)
u8 ath6kl_driver_unloaded;
#endif
#ifdef ATHTST_SUPPORT
struct hif_product_info_t {
uint16_t idVendor;
uint16_t idProduct;
uint8_t product[64];
uint8_t manufacturer[64];
uint8_t serial[64];
};
#endif
/* function declarations */
#ifdef CONFIG_PM
static int ath6kl_usb_pm_suspend(struct usb_interface *interface,
pm_message_t message);
static int ath6kl_usb_pm_resume(struct usb_interface *interface);
static int ath6kl_usb_pm_reset_resume(struct usb_interface *intf);
#else
#define ath6kl_usb_pm_suspend NULL
#define ath6kl_usb_pm_resume NULL
#define ath6kl_usb_pm_reset_resume NULL
#endif
#ifdef CONFIG_ANDROID
/* variables for unload driver module */
#define ATH6KL_USB_UNLOAD_TIMEOUT (2*HZ)
enum ath6kl_usb_drv_unload_state {
ATH6KL_USB_UNLOAD_STATE_NULL = 0,
ATH6KL_USB_UNLOAD_STATE_DRV_DEREG,
ATH6KL_USB_UNLOAD_STATE_TARGET_RESET,
ATH6KL_USB_UNLOAD_STATE_DEV_DISCONNECTED,
};
static int ath6kl_usb_unload_dev_num = -1;
static wait_queue_head_t ath6kl_usb_unload_event_wq;
static atomic_t ath6kl_usb_unload_state;
#endif
static void ath6kl_usb_recv_complete(struct urb *urb);
static void ath6kl_usb_recv_bundle_complete(struct urb *urb);
#ifdef USB_AUTO_SUSPEND
static void usb_auto_pm_turnoff(struct ath6kl *ar);
#endif
#define ATH6KL_USB_IS_BULK_EP(attr) (((attr) & 3) == 0x02)
#define ATH6KL_USB_IS_INT_EP(attr) (((attr) & 3) == 0x03)
#define ATH6KL_USB_IS_ISOC_EP(attr) (((attr) & 3) == 0x01)
#define ATH6KL_USB_IS_DIR_IN(addr) ((addr) & 0x80)
static char *_get_suspend_stat_string(enum ath6kl_state state)
{
if (state == ATH6KL_STATE_OFF)
return "OFF";
else if (state == ATH6KL_STATE_ON)
return "ON";
else if (state == ATH6KL_STATE_DEEPSLEEP)
return "DEEPSLEEP";
else if (state == ATH6KL_STATE_CUTPOWER)
return "CUTPOWER";
else if (state == ATH6KL_STATE_WOW)
return "WOW";
else if (state == ATH6KL_STATE_PRE_SUSPEND)
return "PRE-SUSPEND";
else if (state == ATH6KL_STATE_PRE_SUSPEND_DEEPSLEEP)
return "PRE-DEEPSLEEP";
return "UNKNOWN";
}
/* pipe/urb operations */
static struct ath6kl_urb_context *ath6kl_usb_alloc_urb_from_pipe(
struct ath6kl_usb_pipe *pipe,
bool reclaim)
{
struct ath6kl_urb_context *urb_context = NULL;
unsigned long flags;
spin_lock_irqsave(&pipe->ar_usb->cs_lock, flags);
if (!list_empty(&pipe->urb_list_head)) {
if (!reclaim &&
pipe->urb_cnt_thresh_out &&
(pipe->flags & ATH6KL_USB_PIPE_FLAG_TX) &&
(pipe->ep_address != ATH6KL_USB_EP_ADDR_APP_CTRL_OUT) &&
((pipe->urb_alloc - pipe->urb_cnt) >
pipe->urb_cnt_thresh_out))
goto out;
urb_context =
list_first_entry(&pipe->urb_list_head,
struct ath6kl_urb_context, link);
list_del(&urb_context->link);
pipe->urb_cnt--;
}
out:
spin_unlock_irqrestore(&pipe->ar_usb->cs_lock, flags);
return urb_context;
}
static void ath6kl_usb_free_urb_to_pipe(struct ath6kl_usb_pipe *pipe,
struct ath6kl_urb_context *urb_context)
{
unsigned long flags;
spin_lock_irqsave(&pipe->ar_usb->cs_lock, flags);
pipe->urb_cnt++;
list_add(&urb_context->link, &pipe->urb_list_head);
spin_unlock_irqrestore(&pipe->ar_usb->cs_lock, flags);
}
static void ath6kl_usb_cleanup_recv_urb(struct ath6kl_urb_context *urb_context)
{
if (urb_context->buf != NULL) {
dev_kfree_skb(urb_context->buf);
urb_context->buf = NULL;
}
ath6kl_usb_free_urb_to_pipe(urb_context->pipe, urb_context);
}
static inline struct ath6kl_usb *ath6kl_usb_priv(struct ath6kl *ar)
{
return ar->hif_priv;
}
/* pipe resource allocation/cleanup */
static int ath6kl_usb_alloc_pipe_resources(struct ath6kl_usb_pipe *pipe,
int urb_cnt)
{
int status = 0;
int i;
struct ath6kl_urb_context *urb_context;
INIT_LIST_HEAD(&pipe->urb_list_head);
init_usb_anchor(&pipe->urb_submitted);
for (i = 0; i < urb_cnt; i++) {
urb_context = (struct ath6kl_urb_context *)
kzalloc(sizeof(struct ath6kl_urb_context), GFP_KERNEL);
if (urb_context == NULL) {
status = -ENOMEM;
goto fail_alloc_pipe_resources;
}
memset(urb_context, 0, sizeof(struct ath6kl_urb_context));
urb_context->pipe = pipe;
/*
* we are only allocate the urb contexts here, the actual URB
* is allocated from the kernel as needed to do a transaction
*/
pipe->urb_alloc++;
if (htc_bundle_send) {
/* In tx bundle mode, only pre-allocate bundle buffers
* for data pipes
*/
if (pipe->logical_pipe_num >= ATH6KL_USB_PIPE_TX_DATA_LP &&
pipe->logical_pipe_num <= ATH6KL_USB_PIPE_TX_DATA_VHP) {
urb_context->buf =
dev_alloc_skb(ATH6KL_USB_TX_BUNDLE_BUFFER_SIZE);
if (NULL == urb_context->buf)
ath6kl_dbg(ATH6KL_DBG_USB,
"athusb: alloc send bundle buffer %d-byte "
"failed\n",
ATH6KL_USB_TX_BUNDLE_BUFFER_SIZE);
}
skb_queue_head_init(&urb_context->comp_queue);
}
ath6kl_usb_free_urb_to_pipe(pipe, urb_context);
}
ath6kl_dbg(ATH6KL_DBG_USB,
"ath6kl usb: alloc resources lpipe:%d"
"hpipe:0x%X urbs:%d\n",
pipe->logical_pipe_num, pipe->usb_pipe_handle,
pipe->urb_alloc);
fail_alloc_pipe_resources:
return status;
}
static void ath6kl_usb_free_pipe_resources(struct ath6kl_usb_pipe *pipe)
{
struct ath6kl_urb_context *urb_context;
struct sk_buff *buf = NULL;
if (pipe->ar_usb == NULL) {
/* nothing allocated for this pipe */
return;
}
ath6kl_dbg(ATH6KL_DBG_USB,
"ath6kl usb: free resources lpipe:%d"
"hpipe:0x%X urbs:%d avail:%d\n",
pipe->logical_pipe_num, pipe->usb_pipe_handle,
pipe->urb_alloc, pipe->urb_cnt);
if (pipe->urb_alloc != pipe->urb_cnt) {
ath6kl_dbg(ATH6KL_DBG_USB,
"ath6kl usb: urb leak! lpipe:%d"
"hpipe:0x%X urbs:%d avail:%d\n",
pipe->logical_pipe_num, pipe->usb_pipe_handle,
pipe->urb_alloc, pipe->urb_cnt);
}
while (true) {
urb_context = ath6kl_usb_alloc_urb_from_pipe(pipe, true);
if (urb_context == NULL)
break;
if (htc_bundle_send) {
while ((buf = skb_dequeue(&urb_context->comp_queue))
!= NULL)
dev_kfree_skb(buf);
if (pipe->logical_pipe_num >=
ATH6KL_USB_PIPE_TX_DATA_LP &&
pipe->logical_pipe_num <=
ATH6KL_USB_PIPE_TX_DATA_VHP)
if (urb_context->buf != NULL) {
dev_kfree_skb(urb_context->buf);
urb_context->buf = NULL;
}
}
if (htc_bundle_recv)
if (pipe->logical_pipe_num == ATH6KL_USB_PIPE_RX_DATA)
if (urb_context->buf != NULL) {
dev_kfree_skb(urb_context->buf);
urb_context->buf = NULL;
}
kfree(urb_context);
}
}
static void ath6kl_usb_cleanup_pipe_resources(struct ath6kl_usb *device)
{
int i;
for (i = 0; i < ATH6KL_USB_PIPE_MAX; i++)
ath6kl_usb_free_pipe_resources(&device->pipes[i]);
}
static u8 ath6kl_usb_get_logical_pipe_num(struct ath6kl_usb *device,
u8 ep_address, int *urb_count)
{
u8 pipe_num = ATH6KL_USB_PIPE_INVALID;
switch (ep_address) {
case ATH6KL_USB_EP_ADDR_APP_CTRL_IN:
pipe_num = ATH6KL_USB_PIPE_RX_CTRL;
*urb_count = RX_URB_COUNT;
break;
case ATH6KL_USB_EP_ADDR_APP_DATA_IN:
pipe_num = ATH6KL_USB_PIPE_RX_DATA;
*urb_count = RX_URB_COUNT;
break;
case ATH6KL_USB_EP_ADDR_APP_INT_IN:
pipe_num = ATH6KL_USB_PIPE_RX_INT;
*urb_count = RX_URB_COUNT;
break;
case ATH6KL_USB_EP_ADDR_APP_DATA2_IN:
pipe_num = ATH6KL_USB_PIPE_RX_DATA2;
*urb_count = RX_URB_COUNT;
break;
case ATH6KL_USB_EP_ADDR_APP_CTRL_OUT:
pipe_num = ATH6KL_USB_PIPE_TX_CTRL;
*urb_count = TX_URB_COUNT;
break;
case ATH6KL_USB_EP_ADDR_APP_DATA_LP_OUT:
pipe_num = ATH6KL_USB_PIPE_TX_DATA_LP;
*urb_count = TX_URB_COUNT;
break;
case ATH6KL_USB_EP_ADDR_APP_DATA_MP_OUT:
pipe_num = ATH6KL_USB_PIPE_TX_DATA_MP;
*urb_count = TX_URB_COUNT;
break;
case ATH6KL_USB_EP_ADDR_APP_DATA_HP_OUT:
pipe_num = ATH6KL_USB_PIPE_TX_DATA_HP;
*urb_count = TX_URB_COUNT;
break;
case ATH6KL_USB_EP_ADDR_APP_DATA_VHP_OUT:
pipe_num = ATH6KL_USB_PIPE_TX_DATA_VHP;
*urb_count = TX_URB_COUNT;
break;
default:
/* note: there may be endpoints not currently used */
break;
}
return pipe_num;
}
static int ath6kl_usb_setup_pipe_resources(struct ath6kl_usb *device)
{
struct usb_interface *interface = device->interface;
struct usb_host_interface *iface_desc = interface->cur_altsetting;
struct usb_endpoint_descriptor *endpoint;
int i;
int urbcount;
int status = 0;
struct ath6kl_usb_pipe *pipe;
u8 pipe_num;
ath6kl_dbg(ATH6KL_DBG_USB, "setting up USB Pipes using interface\n");
/* walk decriptors and setup pipes */
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
endpoint = &iface_desc->endpoint[i].desc;
if (ATH6KL_USB_IS_BULK_EP(endpoint->bmAttributes)) {
ath6kl_dbg(ATH6KL_DBG_USB,
"%s Bulk Ep:0x%2.2X maxpktsz:%d\n",
ATH6KL_USB_IS_DIR_IN
(endpoint->bEndpointAddress) ?
"RX" : "TX", endpoint->bEndpointAddress,
le16_to_cpu(endpoint->wMaxPacketSize));
} else if (ATH6KL_USB_IS_INT_EP(endpoint->bmAttributes)) {
ath6kl_dbg(ATH6KL_DBG_USB,
"%s Int Ep:0x%2.2X maxpktsz:%d interval:%d\n",
ATH6KL_USB_IS_DIR_IN
(endpoint->bEndpointAddress) ?
"RX" : "TX", endpoint->bEndpointAddress,
le16_to_cpu(endpoint->wMaxPacketSize),
endpoint->bInterval);
} else if (ATH6KL_USB_IS_ISOC_EP(endpoint->bmAttributes)) {
/* TODO for ISO */
ath6kl_dbg(ATH6KL_DBG_USB,
"%s ISOC Ep:0x%2.2X maxpktsz:%d interval:%d\n",
ATH6KL_USB_IS_DIR_IN
(endpoint->bEndpointAddress) ?
"RX" : "TX", endpoint->bEndpointAddress,
le16_to_cpu(endpoint->wMaxPacketSize),
endpoint->bInterval);
}
urbcount = 0;
pipe_num =
ath6kl_usb_get_logical_pipe_num(device,
endpoint->bEndpointAddress,
&urbcount);
if (pipe_num == ATH6KL_USB_PIPE_INVALID)
continue;
pipe = &device->pipes[pipe_num];
if (pipe->ar_usb != NULL) {
/* hmmm..pipe was already setup */
continue;
}
pipe->ar_usb = device;
pipe->logical_pipe_num = pipe_num;
pipe->ep_address = endpoint->bEndpointAddress;
pipe->max_packet_size = le16_to_cpu(endpoint->wMaxPacketSize);
if (ATH6KL_USB_IS_BULK_EP(endpoint->bmAttributes)) {
if (ATH6KL_USB_IS_DIR_IN(pipe->ep_address)) {
pipe->usb_pipe_handle =
usb_rcvbulkpipe(device->udev,
pipe->ep_address);
} else {
pipe->usb_pipe_handle =
usb_sndbulkpipe(device->udev,
pipe->ep_address);
}
} else if (ATH6KL_USB_IS_INT_EP(endpoint->bmAttributes)) {
if (ATH6KL_USB_IS_DIR_IN(pipe->ep_address)) {
pipe->usb_pipe_handle =
usb_rcvintpipe(device->udev,
pipe->ep_address);
} else {
pipe->usb_pipe_handle =
usb_sndintpipe(device->udev,
pipe->ep_address);
}
} else if (ATH6KL_USB_IS_ISOC_EP(endpoint->bmAttributes)) {
/* TODO for ISO */
if (ATH6KL_USB_IS_DIR_IN(pipe->ep_address)) {
pipe->usb_pipe_handle =
usb_rcvisocpipe(device->udev,
pipe->ep_address);
} else {
pipe->usb_pipe_handle =
usb_sndisocpipe(device->udev,
pipe->ep_address);
}
}
pipe->ep_desc = endpoint;
if (!ATH6KL_USB_IS_DIR_IN(pipe->ep_address))
pipe->flags |= ATH6KL_USB_PIPE_FLAG_TX;
else
pipe->flags |= ATH6KL_USB_PIPE_FLAG_RX;
status = ath6kl_usb_alloc_pipe_resources(pipe, urbcount);
if (status != 0)
break;
}
return status;
}
/* pipe operations */
static void ath6kl_usb_post_recv_transfers(struct ath6kl_usb_pipe *recv_pipe,
int buffer_length)
{
struct ath6kl_urb_context *urb_context;
u8 *data;
u32 len;
struct urb *urb;
int usb_status;
while (1) {
urb_context = ath6kl_usb_alloc_urb_from_pipe(recv_pipe, false);
if (urb_context == NULL)
break;
urb_context->buf = dev_alloc_skb(buffer_length);
if (urb_context->buf == NULL)
goto err_cleanup_urb;
data = urb_context->buf->data;
len = urb_context->buf->len;
urb = usb_alloc_urb(0, GFP_ATOMIC);
if (urb == NULL)
goto err_cleanup_urb;
usb_fill_bulk_urb(urb,
recv_pipe->ar_usb->udev,
recv_pipe->usb_pipe_handle,
data,
buffer_length,
ath6kl_usb_recv_complete, urb_context);
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"ath6kl usb: bulk recv submit:%d, 0x%X"
"(ep:0x%2.2X), %d bytes buf:0x%p\n",
recv_pipe->logical_pipe_num,
recv_pipe->usb_pipe_handle, recv_pipe->ep_address,
buffer_length, urb_context->buf);
usb_anchor_urb(urb, &recv_pipe->urb_submitted);
usb_status = usb_submit_urb(urb, GFP_ATOMIC);
if (usb_status) {
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"ath6kl usb : usb bulk recv failed %d\n",
usb_status);
usb_unanchor_urb(urb);
usb_free_urb(urb);
goto err_cleanup_urb;
}
usb_free_urb(urb);
}
return;
err_cleanup_urb:
ath6kl_usb_cleanup_recv_urb(urb_context);
return;
}
static void hif_usb_post_recv_bundle_transfers(
struct ath6kl_usb_pipe *recv_pipe,
int buffer_length)
{
struct ath6kl_urb_context *urb_context;
u8 *data;
u32 len;
struct urb *urb;
int usb_status;
while (1) {
urb_context = ath6kl_usb_alloc_urb_from_pipe(recv_pipe, false);
if (urb_context == NULL)
break;
if (buffer_length) {
urb_context->buf = dev_alloc_skb(buffer_length);
if (urb_context->buf == NULL) {
ath6kl_usb_cleanup_recv_urb(urb_context);
break;
}
}
data = urb_context->buf->data;
len = urb_context->buf->len;
urb = usb_alloc_urb(0, GFP_ATOMIC);
if (urb == NULL) {
ath6kl_usb_cleanup_recv_urb(urb_context);
break;
}
usb_fill_bulk_urb(urb,
recv_pipe->ar_usb->udev,
recv_pipe->usb_pipe_handle,
data,
ATH6KL_USB_RX_BUNDLE_BUFFER_SIZE,
ath6kl_usb_recv_bundle_complete, urb_context);
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"ath6kl usb: bulk recv submit:%d, 0x%X"
"(ep:0x%2.2X), %d bytes buf:0x%p\n",
recv_pipe->logical_pipe_num,
recv_pipe->usb_pipe_handle, recv_pipe->ep_address,
buffer_length, urb_context->buf);
usb_anchor_urb(urb, &recv_pipe->urb_submitted);
usb_status = usb_submit_urb(urb, GFP_ATOMIC);
if (usb_status) {
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"ath6kl usb : usb bulk recv failed %d\n",
usb_status);
usb_unanchor_urb(urb);
usb_free_urb(urb);
ath6kl_usb_free_urb_to_pipe(urb_context->pipe,
urb_context);
break;
}
usb_free_urb(urb);
}
return;
}
static inline void ath6kl_usb_flush_anchor_urbs(struct usb_anchor *list)
{
struct urb *fir_urb, *cur_urb;
if (list) {
cur_urb = usb_get_from_anchor(list);
fir_urb = cur_urb;
while (cur_urb) {
cur_urb = usb_get_from_anchor(list);
if (fir_urb == cur_urb)
break;
}
}
}
static void ath6kl_usb_flush_all(struct ath6kl_usb *device)
{
int i;
struct ath6kl_usb_pipe *pipe;
for (i = 0; i < ATH6KL_USB_PIPE_MAX; i++) {
/* flush only USB scheduled work, instead of flushing all */
if (device->pipes[i].ar_usb) {
if (machine_is_apq8064_dma() ||
machine_is_apq8064_bueller()) {
ath6kl_usb_flush_anchor_urbs(
&device->pipes[i].urb_submitted);
} else {
if (&device->pipes[i].urb_submitted)
usb_kill_anchored_urbs(
&device->pipes[i].urb_submitted);
}
pipe = &device->pipes[i].ar_usb->pipes[i];
if (pipe)
flush_work(&pipe->io_complete_work);
}
}
/* flushing any pending I/O may schedule work
* this call will block until all scheduled work runs to completion */
/* flush_scheduled_work(); */
}
static void ath6kl_usb_start_recv_pipes(struct ath6kl_usb *device)
{
/*
* note: control pipe is no longer used
* device->pipes[ATH6KL_USB_PIPE_RX_CTRL].urb_cnt_thresh =
* device->pipes[ATH6KL_USB_PIPE_RX_CTRL].urb_alloc/2;
* ath6kl_usb_post_recv_transfers(&device->
* pipes[ATH6KL_USB_PIPE_RX_CTRL],
* ATH6KL_USB_RX_BUFFER_SIZE);
*/
device->pipes[ATH6KL_USB_PIPE_RX_DATA].urb_cnt_thresh =
device->pipes[ATH6KL_USB_PIPE_RX_DATA].urb_alloc / 2;
if (!htc_bundle_recv)
ath6kl_usb_post_recv_transfers(
&device->pipes[ATH6KL_USB_PIPE_RX_DATA],
ATH6KL_USB_RX_BUFFER_SIZE);
else
hif_usb_post_recv_bundle_transfers(
&device->pipes[ATH6KL_USB_PIPE_RX_DATA],
ATH6KL_USB_RX_BUNDLE_BUFFER_SIZE);
/*
* Disable rxdata2 directly, it will be enabled
* if FW enable rxdata2
*/
if (0) {
device->pipes[ATH6KL_USB_PIPE_RX_DATA2].urb_cnt_thresh =
device->pipes[ATH6KL_USB_PIPE_RX_DATA2].urb_alloc / 2;
ath6kl_usb_post_recv_transfers(
&device->pipes[ATH6KL_USB_PIPE_RX_DATA2],
ATH6KL_USB_RX_BUFFER_SIZE);
}
}
/* hif usb rx/tx completion functions */
static void ath6kl_usb_recv_complete(struct urb *urb)
{
struct ath6kl_urb_context *urb_context =
(struct ath6kl_urb_context *)urb->context;
int status = 0;
struct sk_buff *buf = NULL;
struct ath6kl_usb_pipe *pipe = urb_context->pipe;
struct ath6kl_usb_pipe_stat *pipe_st = &pipe->usb_pipe_stat;
#ifdef CE_OLD_KERNEL_SUPPORT_2_6_23
struct ath6kl *ar = pipe->ar_usb->ar;
#endif
if (WARN_ON_ONCE(pipe->ar_usb == NULL))
return;
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"%s: recv pipe: %d, stat:%d, len:%d urb:0x%p\n", __func__,
pipe->logical_pipe_num, urb->status, urb->actual_length,
urb);
if (urb->status != 0) {
pipe_st->num_rx_comp_err++;
status = -EIO;
switch (urb->status) {
case -EOVERFLOW:
urb->actual_length = ATH6KL_USB_RX_BUFFER_SIZE;
status = 0;
break;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
/*
* no need to spew these errors when device
* removed or urb killed due to driver shutdown
*/
status = -ECANCELED;
break;
default:
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"%s recv pipe: %d (ep:0x%2.2X), failed:%d\n",
__func__, pipe->logical_pipe_num,
pipe->ep_address, urb->status);
break;
}
goto cleanup_recv_urb;
}
if (urb->actual_length == 0)
goto cleanup_recv_urb;
buf = urb_context->buf;
/* we are going to pass it up */
urb_context->buf = NULL;
skb_put(buf, urb->actual_length);
/* note: queue implements a lock */
skb_queue_tail(&pipe->io_comp_queue, buf);
pipe_st->num_rx_comp++;
#ifdef CE_OLD_KERNEL_SUPPORT_2_6_23
queue_work(ar->ath6kl_wq, &pipe->io_complete_work);
#else
schedule_work(&pipe->io_complete_work);
#endif
cleanup_recv_urb:
ath6kl_usb_cleanup_recv_urb(urb_context);
if (status == 0 || urb->status == -EPROTO) {
if (pipe->urb_cnt >= pipe->urb_cnt_thresh &&
skb_queue_len(&pipe->io_comp_queue) <
pipe->ar_usb->rxq_threshold) {
/* our free urbs are piling up, post more transfers */
ath6kl_usb_post_recv_transfers(pipe,
ATH6KL_USB_RX_BUFFER_SIZE);
}
}
return;
}
static void ath6kl_usb_recv_bundle_complete(struct urb *urb)
{
struct ath6kl_urb_context *urb_context =
(struct ath6kl_urb_context *)urb->context;
int status = 0;
struct sk_buff *buf = NULL;
struct ath6kl_usb_pipe *pipe = urb_context->pipe;
struct ath6kl_usb_pipe_stat *pipe_st = &pipe->usb_pipe_stat;
u8 *netdata, *netdata_new;
u32 netlen, netlen_new;
struct htc_frame_hdr *htc_hdr;
u16 payload_len;
struct sk_buff *new_skb = NULL;
struct ath6kl *ar = pipe->ar_usb->ar;
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"%s: recv pipe: %d, stat:%d, len:%d urb:0x%p\n", __func__,
pipe->logical_pipe_num, urb->status, urb->actual_length,
urb);
do {
if (urb->status != 0) {
pipe_st->num_rx_bundle_comp_err++;
status = -EIO;
switch (urb->status) {
case -EOVERFLOW:
urb->actual_length =
ATH6KL_USB_RX_BUNDLE_BUFFER_SIZE;
status = 0;
break;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
/*
* no need to spew these errors when device
* removed or urb killed due to driver shutdown
*/
status = -ECANCELED;
break;
default:
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"%s recv pipe: %d (ep:0x%2.2X), failed:%d\n",
__func__, pipe->logical_pipe_num,
pipe->ep_address, urb->status);
}
break;
}
if (urb->actual_length == 0)
break;
buf = urb_context->buf;
netdata = buf->data;
netlen = buf->len;
netlen = urb->actual_length;
do {
u8 extra_pad = 0;
u16 act_frame_len = 0;
u16 frame_len;
/* Hack into HTC header for bundle processing */
htc_hdr = (struct htc_frame_hdr *)netdata;
if (htc_hdr->eid >= ENDPOINT_MAX) {
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"athusb: Rx: invalid eid=%d\n", htc_hdr->eid);
break;
}
payload_len =
le16_to_cpu(get_unaligned((u16 *)&htc_hdr->payld_len));
if (ar->hw.flags & ATH6KL_HW_TGT_ALIGN_PADDING) {
act_frame_len = (HTC_HDR_LENGTH + payload_len);
if (htc_hdr->eid == 0 || htc_hdr->eid == 1)
/* assumption: target won't pad on
* HTC endpoint 0 & 1.
*/
extra_pad = 0;
else
extra_pad =
get_unaligned((u8 *)&htc_hdr->ctrl[1]);
}
if (payload_len > ATH6KL_MAX_AMSDU_SIZE) {
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"athusb: payload_len too long %u\n",
payload_len);
break;
}
if (ar->hw.flags & ATH6KL_HW_TGT_ALIGN_PADDING)
frame_len = (act_frame_len + extra_pad);
else
frame_len = (HTC_HDR_LENGTH + payload_len);
if (netlen >= frame_len) {
/* allocate a new skb and copy */
if (ar->hw.flags &
ATH6KL_HW_TGT_ALIGN_PADDING) {
new_skb = dev_alloc_skb(act_frame_len);
if (new_skb == NULL) {
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"athusb: allocate skb (len=%u) "
"failed\n", act_frame_len);
break;
}
netdata_new = new_skb->data;
netlen_new = new_skb->len;
memcpy(netdata_new,
netdata, act_frame_len);
skb_put(new_skb, act_frame_len);
} else {
new_skb = dev_alloc_skb(frame_len);
if (new_skb == NULL) {
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"athusb: allocate skb (len=%u) "
"failed\n", frame_len);
break;
}
netdata_new = new_skb->data;
netlen_new = new_skb->len;
memcpy(netdata_new, netdata, frame_len);
skb_put(new_skb, frame_len);
}
skb_queue_tail(&pipe->io_comp_queue, new_skb);
new_skb = NULL;
netdata += frame_len;
netlen -= frame_len;
} else {
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"athusb: subframe length %d not fitted "
"into bundle packet length %d\n",
netlen, frame_len);
break;
}
} while (netlen);
pipe_st->num_rx_bundle_comp++;
#ifdef CE_OLD_KERNEL_SUPPORT_2_6_23
queue_work(ar->ath6kl_wq, &pipe->io_complete_work);
#else
schedule_work(&pipe->io_complete_work);
#endif
} while (0);
if (urb_context->buf == NULL)
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"athusb: buffer in urb_context is NULL\n");
ath6kl_usb_free_urb_to_pipe(urb_context->pipe, urb_context);
if (status == 0 || urb->status == -EPROTO) {
if (pipe->urb_cnt >= pipe->urb_cnt_thresh)
/* our free urbs are piling up, post more transfers */
hif_usb_post_recv_bundle_transfers(pipe,
0 /* pass zero for not allocating urb-buffer again */);
}
return;
}
static void ath6kl_usb_usb_transmit_complete(struct urb *urb)
{
struct ath6kl_urb_context *urb_context =
(struct ath6kl_urb_context *)urb->context;
struct sk_buff *buf;
struct ath6kl_usb_pipe *pipe = urb_context->pipe;
struct ath6kl_usb_pipe_stat *pipe_st = &pipe->usb_pipe_stat;
#ifdef CE_OLD_KERNEL_SUPPORT_2_6_23
struct ath6kl *ar = pipe->ar_usb->ar;
#endif
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"%s: pipe: %d, stat:%d, len:%d\n",
__func__, pipe->logical_pipe_num, urb->status,
urb->actual_length);
if (urb->status != 0) {
pipe_st->num_tx_comp_err++;
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"%s: pipe: %d, failed:%d\n",
__func__, pipe->logical_pipe_num, urb->status);
}
buf = urb_context->buf;
urb_context->buf = NULL;
ath6kl_usb_free_urb_to_pipe(urb_context->pipe, urb_context);
/* note: queue implements a lock */
skb_queue_tail(&pipe->io_comp_queue, buf);
pipe_st->num_tx_comp++;
#ifdef CE_OLD_KERNEL_SUPPORT_2_6_23
queue_work(ar->ath6kl_wq, &pipe->io_complete_work);
#else
schedule_work(&pipe->io_complete_work);
#endif
}
static void ath6kl_usb_io_comp_work(struct work_struct *work)
{
struct ath6kl_usb_pipe *pipe =
container_of(work, struct ath6kl_usb_pipe, io_complete_work);
struct sk_buff *buf;
struct ath6kl_usb *device;
struct ath6kl_usb_pipe_stat *pipe_st = &pipe->usb_pipe_stat;
u32 tx = 0, rx = 0;
#if defined(CE_OLD_KERNEL_SUPPORT_2_6_23) || defined(USB_AUTO_SUSPEND)
struct ath6kl *ar = pipe->ar_usb->ar;
#endif
pipe_st->num_io_comp++;
device = pipe->ar_usb;
if (test_and_set_bit(WORKER_LOCK_BIT, &pipe->worker_lock))
return;
while ((buf = skb_dequeue(&pipe->io_comp_queue))) {
#ifdef USB_AUTO_SUSPEND
/* to check if need to postpone auto pm timeout */
if (pipe->flags & ATH6KL_USB_PIPE_FLAG_RX) {
if (ar->htc_ops->skip_usb_mark_busy != NULL) {
if (ar->htc_ops->skip_usb_mark_busy(ar, buf))
goto skip_mark_busy;
}
}
usb_mark_last_busy(device->udev);
skip_mark_busy:
#endif
if (pipe->flags & ATH6KL_USB_PIPE_FLAG_TX) {
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"ath6kl usb xmit callback buf:0x%p\n", buf);
device->htc_callbacks.
tx_completion(device->ar->htc_target, buf);
#ifdef USB_AUTO_SUSPEND
spin_lock_bh(&ar->usb_pm_lock);
/*
* TX-complete back means USB bus is awake and flush
* pending queue immediately.
*/
ath6kl_auto_pm_wakeup_resume(ar);
spin_unlock_bh(&ar->usb_pm_lock);
#endif
if (tx++ > device->max_sche_tx) {
clear_bit(WORKER_LOCK_BIT, &pipe->worker_lock);
pipe_st->num_tx_resche++;
goto reschedule;
}
} else {
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"ath6kl usb recv callback buf:0x%p\n", buf);
if (!device->htc_callbacks.rx_completion) {
dev_kfree_skb(buf);
continue;
}
device->htc_callbacks.
rx_completion(device->ar->htc_target, buf,
pipe->logical_pipe_num);
if (rx++ > device->max_sche_rx) {
clear_bit(WORKER_LOCK_BIT, &pipe->worker_lock);
pipe_st->num_rx_resche++;
goto reschedule;
}
}
}
if (pipe->flags & ATH6KL_USB_PIPE_FLAG_RX &&
pipe->urb_cnt >= pipe->urb_cnt_thresh) {
/* our free urbs are piling up, post more transfers */
ath6kl_usb_post_recv_transfers(pipe, ATH6KL_USB_RX_BUFFER_SIZE);
}
clear_bit(WORKER_LOCK_BIT, &pipe->worker_lock);
if (tx > pipe_st->num_max_tx)
pipe_st->num_max_tx = tx;
if (rx > pipe_st->num_max_rx)
pipe_st->num_max_rx = rx;
return;
reschedule:
/* Re-schedule it to avoid one direction to starve another direction. */
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"ath6kl usb re-schedule work.\n");
#ifdef CE_OLD_KERNEL_SUPPORT_2_6_23
queue_work(ar->ath6kl_wq, &pipe->io_complete_work);
#else
schedule_work(&pipe->io_complete_work);
#endif
return;
}
#define ATH6KL_USB_MAX_DIAG_CMD (sizeof(struct ath6kl_usb_ctrl_diag_cmd_write))
#define ATH6KL_USB_MAX_DIAG_RESP (sizeof(struct ath6kl_usb_ctrl_diag_resp_read))
static void ath6kl_usb_destroy(struct ath6kl_usb *ar_usb)
{
unregister_reboot_notifier(&ar_usb->reboot_notifier);
#ifndef CE_OLD_KERNEL_SUPPORT_2_6_23
ath6kl_usb_flush_all(ar_usb);
#endif
ath6kl_usb_cleanup_pipe_resources(ar_usb);
usb_set_intfdata(ar_usb->interface, NULL);
kfree(ar_usb->diag_cmd_buffer);
kfree(ar_usb->diag_resp_buffer);
kfree(ar_usb);
}
static int ath6kl_usb_reboot(struct notifier_block *nb, unsigned long val,
void *v)
{
struct ath6kl_usb *ar_usb;
struct ath6kl *ar ;
ar_usb = container_of(nb, struct ath6kl_usb, reboot_notifier);
if (ar_usb == NULL)
return NOTIFY_DONE;
ar = (struct ath6kl *) ar_usb->ar;
#ifdef CE_SUPPORT
if ((ar != NULL) && (ar->state == ATH6KL_STATE_ON))
#else
if (ar != NULL)
#endif
if (BOOTSTRAP_IS_HSIC(ar->bootstrap_mode) == 0)
ath6kl_reset_device(ar, ar->target_type, true, true);
return NOTIFY_DONE;
}
static struct ath6kl_usb *ath6kl_usb_create(struct usb_interface *interface)
{
struct ath6kl_usb *ar_usb = NULL;
struct usb_device *dev = interface_to_usbdev(interface);
struct ath6kl_usb_pipe *pipe;
int status = 0;
int i;
ar_usb = kzalloc(sizeof(struct ath6kl_usb), GFP_KERNEL);
if (ar_usb == NULL)
goto fail_ath6kl_usb_create;
memset(ar_usb, 0, sizeof(struct ath6kl_usb));
usb_set_intfdata(interface, ar_usb);
spin_lock_init(&(ar_usb->cs_lock));
spin_lock_init(&(ar_usb->rx_lock));
spin_lock_init(&(ar_usb->tx_lock));
ar_usb->udev = dev;
ar_usb->interface = interface;
#ifdef CONFIG_ANDROID
ath6kl_usb_unload_dev_num = dev->devnum;
#endif
for (i = 0; i < ATH6KL_USB_PIPE_MAX; i++) {
pipe = &ar_usb->pipes[i];
INIT_WORK(&pipe->io_complete_work,
ath6kl_usb_io_comp_work);
skb_queue_head_init(&pipe->io_comp_queue);
pipe->worker_lock = 0;
}
ar_usb->diag_cmd_buffer = kzalloc(ATH6KL_USB_MAX_DIAG_CMD, GFP_KERNEL);
if (ar_usb->diag_cmd_buffer == NULL) {
status = -ENOMEM;
goto fail_ath6kl_usb_create;
}
ar_usb->diag_resp_buffer = kzalloc(ATH6KL_USB_MAX_DIAG_RESP,
GFP_KERNEL);
if (ar_usb->diag_resp_buffer == NULL) {
status = -ENOMEM;
goto fail_ath6kl_usb_create;
}
ar_usb->max_sche_tx =
ar_usb->max_sche_rx = HIF_USB_MAX_SCHE_PKT;
ar_usb->rxq_threshold = HIF_USB_RX_QUEUE_THRESHOLD;
status = ath6kl_usb_setup_pipe_resources(ar_usb);
ar_usb->reboot_notifier.notifier_call = ath6kl_usb_reboot;
register_reboot_notifier(&ar_usb->reboot_notifier);
fail_ath6kl_usb_create:
if (status != 0) {
ath6kl_usb_destroy(ar_usb);
ar_usb = NULL;
}
return ar_usb;
}
static void ath6kl_usb_device_detached(struct usb_interface *interface)
{
struct ath6kl_usb *ar_usb;
#ifdef USB_AUTO_SUSPEND
struct usb_pm_skb_queue_t *entry, *p_usb_pm_skb_queue;
#endif
struct ath6kl *ar;
ar_usb = usb_get_intfdata(interface);
if (ar_usb == NULL)
return;
ar = ar_usb->ar;
ath6kl_stop_txrx(ar_usb->ar);
#ifdef ATH6KL_HSIC_RECOVER
cancel_work_sync(&ar->reset_cover_war_work);
#endif
/* Delay to wait for the target to reboot */
#ifdef CONFIG_ANDROID
if (atomic_read(&ath6kl_usb_unload_state) ==
ATH6KL_USB_UNLOAD_STATE_DRV_DEREG)
atomic_set(&ath6kl_usb_unload_state,
ATH6KL_USB_UNLOAD_STATE_TARGET_RESET);
#else
mdelay(20);
#endif
#ifdef USB_AUTO_SUSPEND
/*
* when packets are in pm_skb_queue,
* and usb remove before resume happens,
* we need to clean pm_skb_queue to avoid memory leak.
*/
p_usb_pm_skb_queue = &ar->usb_pm_skb_queue;
while (get_queue_depth(&(p_usb_pm_skb_queue->list)) > 0) {
ath6kl_dbg(ATH6KL_DBG_USB |
ATH6KL_DBG_EXT_AUTOPM,
"%s resume_wk qeue %d\n",
__func__,
get_queue_depth(&(p_usb_pm_skb_queue->list)));
entry = list_first_entry(&p_usb_pm_skb_queue->list,
struct usb_pm_skb_queue_t, list);
list_del(&entry->list);
kfree(entry);
}
#ifdef ATH6KL_BUS_VOTE
if (ath6kl_platform_has_vreg == 0 ||
(ath6kl_platform_has_vreg == 1 && ath6kl_bt_on == 0))
#endif
usb_auto_pm_turnoff(ar);
#endif
#ifdef CE_OLD_KERNEL_SUPPORT_2_6_23
ath6kl_usb_flush_all(ar_usb);
#endif
ath6kl_core_cleanup(ar_usb->ar);
ath6kl_usb_destroy(ar_usb);
}
/* exported hif usb APIs for htc pipe */
static void hif_start(struct ath6kl *ar)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
int i;
ath6kl_usb_start_recv_pipes(device);
/* set the TX resource avail threshold for each TX pipe */
for (i = ATH6KL_USB_PIPE_TX_CTRL;
i <= ATH6KL_USB_PIPE_TX_DATA_VHP; i++) {
device->pipes[i].urb_cnt_thresh =
device->pipes[i].urb_alloc / 2;
}
}
static void ath6kl_usb_transmit_bundle_complete(struct urb *urb)
{
struct ath6kl_urb_context *urb_context =
(struct ath6kl_urb_context *)urb->context;
struct ath6kl_usb_pipe *pipe = urb_context->pipe;
struct ath6kl_usb_pipe_stat *pipe_st = &pipe->usb_pipe_stat;
struct sk_buff *tmp_buf;
#ifdef CE_OLD_KERNEL_SUPPORT_2_6_23
struct ath6kl *ar = pipe->ar_usb->ar;
#endif
ath6kl_dbg(ATH6KL_DBG_USB_BULK, "+%s: pipe: %d, stat:%d, len:%d " "\n",
__func__, pipe->logical_pipe_num, urb->status,
urb->actual_length);
if (urb->status != 0) {
pipe_st->num_tx_bundle_comp_err++;
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"%s: pipe: %d, failed:%d\n",
__func__, pipe->logical_pipe_num, urb->status);
}
ath6kl_usb_free_urb_to_pipe(urb_context->pipe, urb_context);
while ((tmp_buf = skb_dequeue(&urb_context->comp_queue)))
skb_queue_tail(&pipe->io_comp_queue, tmp_buf);
pipe_st->num_tx_bundle_comp++;
#ifdef CE_OLD_KERNEL_SUPPORT_2_6_23
queue_work(ar->ath6kl_wq, &pipe->io_complete_work);
#else
schedule_work(&pipe->io_complete_work);
#endif
}
static int ath6kl_usb_send_bundle(struct ath6kl *ar, u8 pid,
struct sk_buff **msg_bundle, int num_msgs)
{
int status = 0;
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
struct ath6kl_usb_pipe *pipe = &device->pipes[pid];
struct ath6kl_usb_pipe_stat *pipe_st = &pipe->usb_pipe_stat;
struct ath6kl_urb_context *urb_context;
struct urb *urb;
struct sk_buff *stream_buf = NULL, *buf = NULL;
int usb_status;
int i;
pipe_st->num_tx_multi++;
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"+%s pipe : %d\n",
__func__, pid);
do {
u8 *stream_netdata, *netdata, *stream_netdata_start;
u32 stream_netlen, netlen;
urb_context = ath6kl_usb_alloc_urb_from_pipe(pipe, false);
if (urb_context == NULL) {
pipe_st->num_tx_multi_err_others++;
/*
* TODO: it is possible to run out of urbs if
* 2 endpoints map to the same pipe ID
*/
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"%s pipe:%d no urbs left. URB Cnt : %d\n",
__func__, pid, pipe->urb_cnt);
status = -ENOMEM;
break;
}
urb = usb_alloc_urb(0, GFP_ATOMIC);
if (urb == NULL) {
pipe_st->num_tx_multi_err_others++;
status = -ENOMEM;
ath6kl_usb_free_urb_to_pipe(urb_context->pipe,
urb_context);
break;
}
stream_buf = urb_context->buf;
stream_netdata = stream_buf->data;
stream_netlen = stream_buf->len;
stream_netlen = 0;
stream_netdata_start = stream_netdata;
for (i = 0; i < num_msgs; i++) {
buf = msg_bundle[i];
netdata = buf->data;
netlen = buf->len;
memcpy(stream_netdata, netdata, netlen);
/* add additional dummy padding */
stream_netdata += 1664; /* target credit size */
stream_netlen += 1664;
/* note: queue implements a lock */
skb_queue_tail(&urb_context->comp_queue, buf);
}
usb_fill_bulk_urb(urb,
device->udev,
pipe->usb_pipe_handle,
stream_netdata_start,
stream_netlen,
ath6kl_usb_transmit_bundle_complete,
urb_context);
if ((stream_netlen % pipe->max_packet_size) == 0)
/* hit a max packet boundary on this pipe */
urb->transfer_flags |= URB_ZERO_PACKET;
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"athusb bulk send submit:%d, "
"0x%X (ep:0x%2.2X), %d bytes\n",
pipe->logical_pipe_num, pipe->usb_pipe_handle,
pipe->ep_address, stream_netlen);
usb_anchor_urb(urb, &pipe->urb_submitted);
spin_lock_bh(&ar->state_lock);
if ((ar->state == ATH6KL_STATE_DEEPSLEEP) ||
(ar->state == ATH6KL_STATE_WOW))
usb_status = -EINVAL;
else
usb_status = usb_submit_urb(urb, GFP_ATOMIC);
spin_unlock_bh(&ar->state_lock);
if (usb_status) {
pipe_st->num_tx_multi_err++;
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"ath6kl usb : usb bulk transmit failed %d "
"\n", usb_status);
usb_unanchor_urb(urb);
usb_free_urb(urb);
ath6kl_usb_free_urb_to_pipe(urb_context->pipe,
urb_context);
status = -EINVAL;
break;
}
usb_free_urb(urb);
} while (0);
return status;
}
#ifdef USB_AUTO_SUSPEND
int usb_debugfs_get_pm_usage_cnt(struct ath6kl *ar)
{
struct ath6kl_usb *device = (struct ath6kl_usb *)ar->hif_priv;
struct usb_interface *interface = device->interface;
return atomic_read(&interface->pm_usage_cnt);
}
int usb_auto_pm_disable(struct ath6kl *ar)
{
struct ath6kl_usb *device = (struct ath6kl_usb *)ar->hif_priv;
struct usb_interface *interface = device->interface;
int refcnt;
int ret;
spin_lock_bh(&ar->usb_pm_lock);
ret = usb_autopm_get_interface_async(interface);
if (ret)
ar->auto_pm_fail_cnt++;
else
ar->auto_pm_cnt++;
spin_unlock_bh(&ar->usb_pm_lock);
refcnt = usb_debugfs_get_pm_usage_cnt(ar);
if (refcnt != ar->auto_pm_cnt)
ath6kl_err("autopm unsync, refcnt=%d my=%d/%d ret %d",
refcnt,
ar->auto_pm_cnt,
ar->auto_pm_fail_cnt,
ret);
ath6kl_dbg(ATH6KL_DBG_USB |
ATH6KL_DBG_EXT_AUTOPM,
"autopm +1 refcnt=%d my=%d/%d ret %d\n",
refcnt,
ar->auto_pm_cnt,
ar->auto_pm_fail_cnt,
ret);
return ret;
}
void usb_auto_pm_enable(struct ath6kl *ar)
{
struct ath6kl_usb *device = (struct ath6kl_usb *)ar->hif_priv;
struct usb_interface *interface = device->interface;
int refcnt;
spin_lock_bh(&ar->usb_pm_lock);
if (ar->auto_pm_fail_cnt)
ar->auto_pm_fail_cnt--;
else {
ar->auto_pm_cnt--;
usb_autopm_put_interface_async(interface);
}
spin_unlock_bh(&ar->usb_pm_lock);
refcnt = usb_debugfs_get_pm_usage_cnt(ar);
if (refcnt != ar->auto_pm_cnt)
ath6kl_err("autopm unsync, refcnt=%d my=%d/%d",
refcnt,
ar->auto_pm_cnt,
ar->auto_pm_fail_cnt);
ath6kl_dbg(ATH6KL_DBG_USB |
ATH6KL_DBG_EXT_AUTOPM,
"autopm -1 refcnt=%d my=%d/%d\n",
usb_debugfs_get_pm_usage_cnt(ar),
ar->auto_pm_cnt,
ar->auto_pm_fail_cnt);
}
void usb_auto_pm_turnoff(struct ath6kl *ar)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
if (!ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_DISABLE_USB_AUTO_PM)) {
ath6kl_dbg(ATH6KL_DBG_EXT_AUTOPM,
"autopm turn-off, refcnt=%d my=%d/%d\n",
usb_debugfs_get_pm_usage_cnt(ar),
ar->auto_pm_cnt,
ar->auto_pm_fail_cnt);
usb_disable_autosuspend(device->udev);
}
}
void usb_auto_pm_turnon(struct ath6kl *ar)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
if (!ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_DISABLE_USB_AUTO_PM)) {
ath6kl_dbg(ATH6KL_DBG_EXT_AUTOPM,
"autopm turn-on, refcnt=%d my=%d/%d\n",
usb_debugfs_get_pm_usage_cnt(ar),
ar->auto_pm_cnt,
ar->auto_pm_fail_cnt);
usb_enable_autosuspend(device->udev);
}
}
void ath6kl_auto_pm_wakeup_resume(struct ath6kl *wk)
{
struct sk_buff *buf;
int status = 0;
struct ath6kl_usb *device;
struct ath6kl_usb_pipe *pipe;
struct ath6kl_usb_pipe_stat *pipe_st;
struct ath6kl_urb_context *urb_context;
u8 *data;
u32 len;
struct urb *urb;
int usb_status;
struct usb_pm_skb_queue_t *entry, *p_usb_pm_skb_queue;
struct ath6kl *pm_ar;
int pm_PipeID, q_cnt = 0;
pm_ar = wk;
p_usb_pm_skb_queue = &pm_ar->usb_pm_skb_queue;
ath6kl_dbg(ATH6KL_DBG_USB |
ATH6KL_DBG_EXT_AUTOPM,
"autopm resume_wk queue depth %d\n",
get_queue_depth(&(p_usb_pm_skb_queue->list)));
while (get_queue_depth(&(p_usb_pm_skb_queue->list)) > 0) {
q_cnt++;
ath6kl_dbg(ATH6KL_DBG_USB |
ATH6KL_DBG_EXT_AUTOPM,
"autopm resume_wk entry %d\n",
q_cnt);
entry = list_first_entry(&p_usb_pm_skb_queue->list,
struct usb_pm_skb_queue_t, list);
pm_PipeID = entry->pipeID;
pm_ar = entry->ar;
buf = entry->skb;
device = ath6kl_usb_priv(pm_ar);
pipe = &device->pipes[pm_PipeID];
pipe_st = &pipe->usb_pipe_stat;
usb_mark_last_busy(device->udev);
urb_context = ath6kl_usb_alloc_urb_from_pipe(pipe, false);
if (urb_context == NULL) {
pipe_st->num_tx_err_others++;
/*
* TODO: it is possible to run out of urbs if
* 2 endpoints map to the same pipe ID
*/
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"%s pipe:%d no urbs left. URB Cnt : %d\n",
__func__, pm_PipeID, pipe->urb_cnt);
status = -ENOMEM;
goto fail_hif_send_usb;
}
urb_context->buf = buf;
data = buf->data;
len = buf->len;
urb = usb_alloc_urb(0, GFP_ATOMIC);
if (urb == NULL) {
pipe_st->num_tx_err_others++;
status = -ENOMEM;
ath6kl_usb_free_urb_to_pipe(urb_context->pipe,
urb_context);
goto fail_hif_send_usb;
}
usb_fill_bulk_urb(urb,
device->udev,
pipe->usb_pipe_handle,
data,
len,
ath6kl_usb_usb_transmit_complete,
urb_context);
if ((len % pipe->max_packet_size) == 0) {
/* hit a max packet boundary on this pipe */
urb->transfer_flags |= URB_ZERO_PACKET;
}
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"athusb bulk send submit:%d, 0x%X (ep:0x%2.2X), %d bytes\n",
pipe->logical_pipe_num, pipe->usb_pipe_handle,
pipe->ep_address, len);
usb_anchor_urb(urb, &pipe->urb_submitted);
list_del(&entry->list);
/* spin_lock_bh(&pm_ar->state_lock); */
usb_status = usb_submit_urb(urb, GFP_ATOMIC);
/* spin_unlock_bh(&pm_ar->state_lock); */
if (usb_status) {
pipe_st->num_tx_err++;
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"ath6kl usb : usb bulk transmit failed %d\n",
usb_status);
usb_unanchor_urb(urb);
ath6kl_usb_free_urb_to_pipe(urb_context->pipe,
urb_context);
status = -EINVAL;
}
usb_free_urb(urb);
pipe_st->num_tx++;
kfree(entry);
} /* end of while (Dequeu ...) */
fail_hif_send_usb:
ath6kl_dbg(ATH6KL_DBG_USB |
ATH6KL_DBG_EXT_AUTOPM,
"autopm wakeup_resume done\n");
}
void usb_auto_pm_set_delay(struct ath6kl *ar, int delay)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
struct usb_device *udev = device->udev;
if (machine_is_apq8064_dma() || machine_is_apq8064_bueller())
return;
ath6kl_dbg(ATH6KL_DBG_USB |
ATH6KL_DBG_EXT_AUTOPM,
"autopm set_delay %d --> %d, delay_cnt %d (MAX_DELAY %d)\n",
ar->autopm_curr_delay_time,
delay,
ar->autopm_defer_delay_change_cnt,
USB_SUSPEND_DELAY_MAX);
/* If the the delay is too small or too large, skip the update */
if (delay < 0 || delay > USB_SUSPEND_DELAY_MAX)
return;
ar->autopm_curr_delay_time = delay;
pm_runtime_set_autosuspend_delay(&udev->dev, delay);
}
#endif /* USB_AUTO_SUSPEND */
static int ath6kl_usb_send(struct ath6kl *ar, u8 PipeID,
struct sk_buff *hdr_buf, struct sk_buff *buf)
{
int status = 0;
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
struct ath6kl_usb_pipe *pipe = &device->pipes[PipeID];
struct ath6kl_usb_pipe_stat *pipe_st = &pipe->usb_pipe_stat;
struct ath6kl_urb_context *urb_context;
u8 *data;
u32 len;
struct urb *urb;
int usb_status;
#ifdef USB_AUTO_SUSPEND
struct usb_pm_skb_queue_t *p_pmskb;
int qlen, usb_pm_increament = 0;
struct usb_pm_skb_queue_t *p_usb_pm_skb_queue = &ar->usb_pm_skb_queue;
int pm_disable_result = 0;
#endif
#ifdef USB_AUTO_SUSPEND
#elif defined(CONFIG_ANDROID)
struct usb_interface *interface = device->interface;
#endif
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"+%s pipe : %d, buf:0x%p, ar state:%s\n",
__func__, PipeID, buf,
_get_suspend_stat_string(ar->state));
#ifdef USB_AUTO_SUSPEND
if (ar->state == ATH6KL_STATE_PRE_SUSPEND_DEEPSLEEP) {
ath6kl_dbg(ATH6KL_DBG_USB, "%s: deep sleep state=%s\n",
__func__,
_get_suspend_stat_string(ar->state));
status = -ETXTBSY;
pipe_st->num_tx++;
return status;
}
usb_pm_increament = 0;
if (ar->state != ATH6KL_STATE_PRE_SUSPEND) {
usb_pm_increament++;
pm_disable_result = usb_auto_pm_disable(ar);
}
spin_lock_bh(&ar->usb_pm_lock);
if (!list_empty(&p_usb_pm_skb_queue->list) ||
(ar->state == ATH6KL_STATE_WOW) ||
(ar->state == ATH6KL_STATE_DEEPSLEEP) ||
(pm_disable_result != 0)) {
ath6kl_dbg(ATH6KL_DBG_USB |
ATH6KL_DBG_EXT_AUTOPM,
"autopm usb_send Q pId %d %s l %d cnt %d/%d\n",
PipeID,
_get_suspend_stat_string(ar->state),
get_queue_depth(&(p_usb_pm_skb_queue->list)),
usb_pm_increament,
pm_disable_result);
p_pmskb = kmalloc(sizeof(struct usb_pm_skb_queue_t),
GFP_ATOMIC);
if (p_pmskb == NULL) {
ath6kl_err("p_pmskb = kmalloc fail!\n");
BUG_ON(1); /* TODO */
}
p_pmskb->pipeID = PipeID;
p_pmskb->ar = ar;
p_pmskb->skb = buf;
list_add_tail(&(p_pmskb->list), &(p_usb_pm_skb_queue->list));
qlen = get_queue_depth(&(p_usb_pm_skb_queue->list));
/*
msleep_interruptible(3000);
ath6kl_auto_pm_wakeup_resume(&auto_pm_wakeup_resume_wk);
*/
spin_unlock_bh(&ar->usb_pm_lock);
if (usb_pm_increament != 0)
usb_auto_pm_enable(ar);
return 0;
}
ath6kl_dbg(ATH6KL_DBG_USB |
ATH6KL_DBG_EXT_AUTOPM,
"autopm usb_send pId %d state %s inc %d\n",
PipeID,
_get_suspend_stat_string(ar->state),
usb_pm_increament);
spin_unlock_bh(&ar->usb_pm_lock);
#elif defined(CONFIG_ANDROID)
if (PipeID != ATH6KL_USB_PIPE_TX_CTRL)
usb_autopm_get_interface_async(interface);
#endif
urb_context = ath6kl_usb_alloc_urb_from_pipe(pipe, false);
if (urb_context == NULL) {
pipe_st->num_tx_err_others++;
/*
* TODO: it is possible to run out of urbs if
* 2 endpoints map to the same pipe ID
*/
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"%s pipe:%d no urbs left. URB Cnt : %d\n",
__func__, PipeID, pipe->urb_cnt);
status = -ENOMEM;
goto fail_hif_send;
}
urb_context->buf = buf;
data = buf->data;
len = buf->len;
urb = usb_alloc_urb(0, GFP_ATOMIC);
if (urb == NULL) {
pipe_st->num_tx_err_others++;
status = -ENOMEM;
ath6kl_usb_free_urb_to_pipe(urb_context->pipe,
urb_context);
goto fail_hif_send;
}
usb_fill_bulk_urb(urb,
device->udev,
pipe->usb_pipe_handle,
data,
len,
ath6kl_usb_usb_transmit_complete, urb_context);
if ((len % pipe->max_packet_size) == 0) {
/* hit a max packet boundary on this pipe */
urb->transfer_flags |= URB_ZERO_PACKET;
}
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"athusb bulk send submit:%d, 0x%X (ep:0x%2.2X), %d bytes\n",
pipe->logical_pipe_num, pipe->usb_pipe_handle,
pipe->ep_address, len);
usb_anchor_urb(urb, &pipe->urb_submitted);
spin_lock_bh(&ar->state_lock);
#ifndef USB_AUTO_SUSPEND
if ((ar->state == ATH6KL_STATE_DEEPSLEEP) ||
(ar->state == ATH6KL_STATE_WOW))
usb_status = -EINVAL;
else
#endif
usb_status = usb_submit_urb(urb, GFP_ATOMIC);
spin_unlock_bh(&ar->state_lock);
if (usb_status) {
pipe_st->num_tx_err++;
ath6kl_dbg(ATH6KL_DBG_USB_BULK,
"ath6kl usb : usb bulk transmit failed %d\n",
usb_status);
usb_unanchor_urb(urb);
ath6kl_usb_free_urb_to_pipe(urb_context->pipe,
urb_context);
status = -EINVAL;
}
usb_free_urb(urb);
pipe_st->num_tx++;
fail_hif_send:
#ifdef USB_AUTO_SUSPEND
if (usb_pm_increament != 0)
usb_auto_pm_enable(ar);
#elif defined(CONFIG_ANDROID)
if (PipeID != ATH6KL_USB_PIPE_TX_CTRL)
usb_autopm_put_interface_async(interface);
#endif
return status;
}
static void hif_stop(struct ath6kl *ar)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
ath6kl_usb_flush_all(device);
}
static void ath6kl_usb_get_default_pipe(struct ath6kl *ar,
u8 *ul_pipe, u8 *dl_pipe)
{
*ul_pipe = ATH6KL_USB_PIPE_TX_CTRL;
*dl_pipe = ATH6KL_USB_PIPE_RX_CTRL;
}
static int ath6kl_usb_map_service_pipe(struct ath6kl *ar, u16 svcId, u8 *ULPipe,
u8 *DLPipe)
{
int status = 0;
switch (svcId) {
case HTC_CTRL_RSVD_SVC:
case WMI_CONTROL_SVC:
*ULPipe = ATH6KL_USB_PIPE_TX_CTRL;
*DLPipe = ATH6KL_USB_PIPE_RX_DATA;
break;
case WMI_DATA_BE_SVC:
case WMI_DATA_BK_SVC:
*ULPipe = ATH6KL_USB_PIPE_TX_DATA_LP;
*DLPipe = ATH6KL_USB_PIPE_RX_DATA;
break;
case WMI_DATA_VI_SVC:
*ULPipe = ATH6KL_USB_PIPE_TX_DATA_LP;
*DLPipe = ATH6KL_USB_PIPE_RX_DATA;
break;
case WMI_DATA_VO_SVC:
*ULPipe = ATH6KL_USB_PIPE_TX_DATA_LP;
*DLPipe = ATH6KL_USB_PIPE_RX_DATA;
break;
default:
status = -EPERM;
break;
}
return status;
}
static void ath6kl_usb_register_callback(struct ath6kl *ar,
void *unused,
struct ath6kl_hif_pipe_callbacks *callbacks)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
memcpy(&device->htc_callbacks, callbacks,
sizeof(struct ath6kl_hif_pipe_callbacks));
}
static u16 ath6kl_usb_get_free_queue_number(struct ath6kl *ar, u8 PipeID)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
struct ath6kl_usb_pipe *pipe = &device->pipes[PipeID];
if (pipe->urb_cnt_thresh_out) {
if (pipe->urb_cnt_thresh_out >
(pipe->urb_alloc - pipe->urb_cnt))
return pipe->urb_cnt_thresh_out -
(pipe->urb_alloc - pipe->urb_cnt);
else
return 0;
} else
return pipe->urb_cnt;
}
static u16 ath6kl_usb_get_max_queue_number(struct ath6kl *ar, u8 PipeID)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
return device->pipes[PipeID].urb_alloc;
}
static void ath6kl_usb_set_max_queue_number(struct ath6kl *ar, bool mccEnable)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
struct ath6kl_usb_pipe *pipe;
unsigned long flags;
int i;
for (i = 0; i < ATH6KL_USB_PIPE_MAX; i++) {
pipe = &device->pipes[i];
if ((pipe->flags & ATH6KL_USB_PIPE_FLAG_TX) &&
(pipe->ep_address != ATH6KL_USB_EP_ADDR_APP_CTRL_OUT)) {
spin_lock_irqsave(&pipe->ar_usb->cs_lock, flags);
BUG_ON(pipe->urb_alloc < TX_URB_COUNT_MCC);
if (mccEnable)
pipe->urb_cnt_thresh_out = TX_URB_COUNT_MCC;
else
pipe->urb_cnt_thresh_out = 0;
spin_unlock_irqrestore(&pipe->ar_usb->cs_lock, flags);
ath6kl_dbg(ATH6KL_DBG_USB, "%s, id %d mccEnable %d\n",
__func__,
i,
mccEnable);
}
}
return;
}
static void hif_detach_htc(struct ath6kl *ar)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
ath6kl_usb_flush_all(device);
memset(&device->htc_callbacks, 0,
sizeof(struct ath6kl_hif_pipe_callbacks));
}
static int ath6kl_usb_submit_ctrl_out(struct ath6kl_usb *ar_usb,
u8 req, u16 value, u16 index, void *data,
u32 size)
{
u8 *buf = NULL;
int ret;
if (size > 0) {
buf = kmalloc(size, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
memcpy(buf, data, size);
}
/* note: if successful returns number of bytes transfered */
ret = usb_control_msg(ar_usb->udev,
usb_sndctrlpipe(ar_usb->udev, 0),
req,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, value, index, buf,
size, 1000);
if (ret < 0) {
ath6kl_dbg(ATH6KL_DBG_USB, "%s failed,result = %d\n",
__func__, ret);
}
kfree(buf);
return 0;
}
static int ath6kl_usb_submit_ctrl_in(struct ath6kl_usb *ar_usb,
u8 req, u16 value, u16 index, void *data,
u32 size)
{
u8 *buf = NULL;
int ret;
if (size > 0) {
buf = kmalloc(size, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
}
/* note: if successful returns number of bytes transfered */
ret = usb_control_msg(ar_usb->udev,
usb_rcvctrlpipe(ar_usb->udev, 0),
req,
USB_DIR_IN | USB_TYPE_VENDOR |
USB_RECIP_DEVICE, value, index, buf,
size, 2 * HZ);
if (ret < 0) {
ath6kl_dbg(ATH6KL_DBG_USB, "%s failed,result = %d\n",
__func__, ret);
}
memcpy((u8 *) data, buf, size);
kfree(buf);
return 0;
}
static int ath6kl_usb_ctrl_msg_exchange(struct ath6kl_usb *ar_usb,
u8 req_val, u8 *req_buf, u32 req_len,
u8 resp_val, u8 *resp_buf, u32 *resp_len)
{
int ret;
/* send command */
ret = ath6kl_usb_submit_ctrl_out(ar_usb, req_val, 0, 0,
req_buf, req_len);
if (ret != 0)
return ret;
if (resp_buf == NULL) {
/* no expected response */
return ret;
}
/* get response */
ret = ath6kl_usb_submit_ctrl_in(ar_usb, resp_val, 0, 0,
resp_buf, *resp_len);
return ret;
}
static int ath6kl_usb_diag_read32(struct ath6kl *ar, u32 address, u32 *data)
{
struct ath6kl_usb *ar_usb = ar->hif_priv;
struct ath6kl_usb_ctrl_diag_resp_read *resp;
struct ath6kl_usb_ctrl_diag_cmd_read *cmd;
u32 resp_len;
int ret;
cmd = (struct ath6kl_usb_ctrl_diag_cmd_read *) ar_usb->diag_cmd_buffer;
memset(cmd, 0, sizeof(*cmd));
cmd->cmd = ATH6KL_USB_CTRL_DIAG_CC_READ;
cmd->address = cpu_to_le32(address);
resp_len = sizeof(*resp);
ret = ath6kl_usb_ctrl_msg_exchange(ar_usb,
ATH6KL_USB_CONTROL_REQ_DIAG_CMD,
(u8 *) cmd,
sizeof(struct ath6kl_usb_ctrl_diag_cmd_write),
ATH6KL_USB_CONTROL_REQ_DIAG_RESP,
ar_usb->diag_resp_buffer, &resp_len);
if (ret)
return ret;
resp = (struct ath6kl_usb_ctrl_diag_resp_read *)
ar_usb->diag_resp_buffer;
*data = le32_to_cpu(resp->value);
return ret;
}
static int ath6kl_usb_diag_write32(struct ath6kl *ar, u32 address, __le32 data)
{
struct ath6kl_usb *ar_usb = ar->hif_priv;
struct ath6kl_usb_ctrl_diag_cmd_write *cmd;
cmd = (struct ath6kl_usb_ctrl_diag_cmd_write *) ar_usb->diag_cmd_buffer;
memset(cmd, 0, sizeof(struct ath6kl_usb_ctrl_diag_cmd_write));
cmd->cmd = cpu_to_le32(ATH6KL_USB_CTRL_DIAG_CC_WRITE);
cmd->address = cpu_to_le32(address);
cmd->value = data;
return ath6kl_usb_ctrl_msg_exchange(ar_usb,
ATH6KL_USB_CONTROL_REQ_DIAG_CMD,
(u8 *) cmd,
sizeof(*cmd),
0, NULL, NULL);
}
static int ath6kl_usb_bmi_read(struct ath6kl *ar, u8 *buf, u32 len)
{
struct ath6kl_usb *ar_usb = ar->hif_priv;
int ret;
/* get response */
ret = ath6kl_usb_submit_ctrl_in(ar_usb,
ATH6KL_USB_CONTROL_REQ_RECV_BMI_RESP,
0, 0, buf, len);
if (ret != 0) {
ath6kl_err("Unable to read the bmi data from the device: %d\n",
ret);
return ret;
}
return 0;
}
static int ath6kl_usb_bmi_write(struct ath6kl *ar, u8 *buf, u32 len)
{
struct ath6kl_usb *ar_usb = ar->hif_priv;
int ret;
/* send command */
ret = ath6kl_usb_submit_ctrl_out(ar_usb,
ATH6KL_USB_CONTROL_REQ_SEND_BMI_CMD,
0, 0, buf, len);
if (ret != 0) {
ath6kl_err("unable to send the bmi data to the device: %d\n",
ret);
return ret;
}
return 0;
}
static int ath6kl_usb_power_on(struct ath6kl *ar)
{
if (test_bit(USB_REMOTE_WKUP, &ar->flag)) {
struct ath6kl_usb *ar_usb = (struct ath6kl_usb *)ar->hif_priv;
usb_reset_device(ar_usb->udev);
}
hif_start(ar);
return 0;
}
static int ath6kl_usb_power_off(struct ath6kl *ar)
{
hif_detach_htc(ar);
return 0;
}
static void ath6kl_usb_stop(struct ath6kl *ar)
{
hif_stop(ar);
}
static int ath6kl_usb_pipe_stat(struct ath6kl *ar, u8 *buf, int buf_len)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
struct ath6kl_usb_pipe *pipe;
struct ath6kl_usb_pipe_stat *pipe_st;
int i, len = 0;
if ((!device) || (!buf))
return 0;
len += snprintf(buf + len, buf_len - len, "USB-PM\n");
len += snprintf(buf + len, buf_len - len, " suspend_cnt %d\n",
device->suspend_cnt);
len += snprintf(buf + len, buf_len - len, " resume_cnt %d\n",
device->resume_cnt);
len += snprintf(buf + len, buf_len - len, " pm_suspend_cnt %d\n",
device->pm_suspend_cnt);
len += snprintf(buf + len, buf_len - len, " pm_resume_cnt %d\n",
device->pm_resume_cnt);
len += snprintf(buf + len, buf_len - len, " pm_reset_resume_cnt %d\n\n",
device->pm_reset_resume_cnt);
for (i = 0; i < ATH6KL_USB_PIPE_MAX; i++) {
if ((i == ATH6KL_USB_PIPE_RX_INT) ||
(i == ATH6KL_USB_PIPE_RX_DATA2))
continue;
pipe = &device->pipes[i];
pipe_st = &device->pipes[i].usb_pipe_stat;
len += snprintf(buf + len, buf_len - len, "\nPIPE-%d", i);
len += snprintf(buf + len, buf_len - len,
" size %d flag 0x%0x ep %d logic %d num %d cur %d thr %d/%d\n",
pipe->max_packet_size,
pipe->flags,
pipe->ep_address,
pipe->logical_pipe_num,
pipe->urb_alloc,
pipe->urb_cnt,
pipe->urb_cnt_thresh,
pipe->urb_cnt_thresh_out);
len += snprintf(buf + len, buf_len - len,
" num_rx_comp : %d\n",
pipe_st->num_rx_comp);
len += snprintf(buf + len, buf_len - len,
" num_tx_comp : %d\n",
pipe_st->num_tx_comp);
len += snprintf(buf + len, buf_len - len,
" num_io_comp : %d\n",
pipe_st->num_io_comp);
len += snprintf(buf + len, buf_len - len,
" num_max_tx : %d\n",
pipe_st->num_max_tx);
len += snprintf(buf + len, buf_len - len,
" num_max_rx : %d\n",
pipe_st->num_max_rx);
len += snprintf(buf + len, buf_len - len,
" num_tx_resche : %d\n",
pipe_st->num_tx_resche);
len += snprintf(buf + len, buf_len - len,
" num_rx_resche : %d\n",
pipe_st->num_rx_resche);
len += snprintf(buf + len, buf_len - len,
" num_tx_sync : %d\n",
pipe_st->num_tx_sync);
len += snprintf(buf + len, buf_len - len,
" num_tx : %d\n",
pipe_st->num_tx);
len += snprintf(buf + len, buf_len - len,
" num_tx_err : %d\n",
pipe_st->num_tx_err);
len += snprintf(buf + len, buf_len - len,
" num_tx_err_others : %d\n",
pipe_st->num_tx_err_others);
len += snprintf(buf + len, buf_len - len,
" num_tx_comp_err : %d\n",
pipe_st->num_tx_comp_err);
len += snprintf(buf + len, buf_len - len,
" num_rx_comp_err : %d\n",
pipe_st->num_rx_comp_err);
/* Bundle mode */
if (htc_bundle_recv || htc_bundle_send) {
len += snprintf(buf + len, buf_len - len,
" num_rx_bundle_comp : %d\n",
pipe_st->num_rx_bundle_comp);
len += snprintf(buf + len, buf_len - len,
" num_tx_bundle_comp : %d\n",
pipe_st->num_tx_bundle_comp);
len += snprintf(buf + len, buf_len - len,
" num_tx_multi : %d\n",
pipe_st->num_tx_multi);
len += snprintf(buf + len, buf_len - len,
" num_tx_multi_err : %d\n",
pipe_st->num_tx_multi_err);
len += snprintf(buf + len, buf_len - len,
" num_tx_multi_err_others : %d\n",
pipe_st->num_tx_multi_err_others);
len += snprintf(buf + len, buf_len - len,
" num_rx_bundle_comp_err : %d\n",
pipe_st->num_rx_bundle_comp_err);
len += snprintf(buf + len, buf_len - len,
" num_tx_bundle_comp_err : %d\n",
pipe_st->num_tx_bundle_comp_err);
}
}
return len;
}
static int ath6kl_usb_set_max_sche(struct ath6kl *ar,
u32 max_sche_tx, u32 max_sche_rx)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
device->max_sche_tx = max_sche_tx;
device->max_sche_rx = max_sche_rx;
ath6kl_dbg(ATH6KL_DBG_USB, "max_sche_tx = %d, max_sche_rx = %d\n",
device->max_sche_tx, device->max_sche_rx);
return 0;
}
int ath6kl_usb_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
struct usb_interface *interface = device->interface;
pm_message_t message;
int ret;
ath6kl_dbg(ATH6KL_DBG_EXT_INFO1 |
ATH6KL_DBG_EXT_AUTOPM,
"usb suspend: ar->state %s\n",
_get_suspend_stat_string(ar->state));
device->suspend_cnt++;
#ifdef CONFIG_ANDROID
if (ath6kl_android_need_wow_suspend(ar)) {
#else
if (wow) {
#endif
ret = ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_WOW, wow);
if (ret)
return ret;
} else {
memset(&message, 0, sizeof(message));
ret = ath6kl_usb_pm_suspend(interface, message);
}
return ret;
}
int ath6kl_usb_resume(struct ath6kl *ar)
{
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
struct usb_interface *interface = device->interface;
ath6kl_dbg(ATH6KL_DBG_EXT_INFO1 |
ATH6KL_DBG_EXT_AUTOPM,
"usb resume: ar->state %s\n",
_get_suspend_stat_string(ar->state));
device->resume_cnt++;
return ath6kl_usb_pm_resume(interface);
}
static void ath6kl_usb_cleanup_scatter(struct ath6kl *ar)
{
ath6kl_dbg(ATH6KL_DBG_USB, "Init target fail?\n");
return;
}
static int ath6kl_usb_set_rxq_threshold(struct ath6kl *ar, u32 rxq_threshold)
{
struct ath6kl_usb *ar_usb = ath6kl_usb_priv(ar);
ar_usb->rxq_threshold = rxq_threshold;
ath6kl_dbg(ATH6KL_DBG_USB,
"rxq_threshold = %d\n", ar_usb->rxq_threshold);
return 0;
}
#ifdef CONFIG_HAS_EARLYSUSPEND
static void ath6kl_usb_early_suspend(struct ath6kl *ar)
{
#ifndef USB_AUTO_SUSPEND
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
if (!ath6kl_mod_debug_quirks(ar,
ATH6KL_MODULE_DISABLE_USB_AUTO_SUSPEND)) {
if (BOOTSTRAP_IS_HSIC(ar->bootstrap_mode)) {
struct usb_device *udev = device->udev;
pm_runtime_set_autosuspend_delay(&udev->dev,
USB_SUSPEND_DELAY_MAX);
}
usb_enable_autosuspend(device->udev);
}
#else
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
if (BOOTSTRAP_IS_HSIC(ar->bootstrap_mode)) {
struct usb_device *udev = device->udev;
pm_runtime_set_autosuspend_delay(&udev->dev,
USB_SUSPEND_DELAY_MAX);
}
usb_enable_autosuspend(device->udev);
#endif
}
static void ath6kl_usb_late_resume(struct ath6kl *ar)
{
#ifndef USB_AUTO_SUSPEND
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
if (!ath6kl_mod_debug_quirks(ar,
ATH6KL_MODULE_DISABLE_USB_AUTO_SUSPEND))
usb_disable_autosuspend(device->udev);
#else
struct ath6kl_usb *device = ath6kl_usb_priv(ar);
usb_disable_autosuspend(device->udev);
#endif /* define USB_AUTO_SUSPEND */
}
#endif
/* FIXME: revisit a proper place to issue the bus reset*/
int ath6kl_usb_reconfig(struct ath6kl *ar)
{
int ret = 0;
u32 data, addr;
/* To reenumerate the usb device if host want to enable the
* usb remote wakeup feature,
* ar6004 hw1.1, hw1.2 and hw1.3 would support this,
* hw1.6 would enable by default
*/
if (!ath6kl_mod_debug_quirks(ar,
ATH6KL_MODULE_ENABLE_USB_REMOTE_WKUP)) {
ret = 0;
ath6kl_dbg(ATH6KL_DBG_BOOT, "Disable USB remote wakeup.\n");
} else {
if (ar->version.target_ver == AR6004_HW_1_3_VERSION)
addr = 0x409754;
else if (ar->version.target_ver == AR6004_HW_1_2_VERSION)
addr = 0x4087d4;
else if (ar->version.target_ver == AR6004_HW_1_1_VERSION)
addr = 0x408304;
else
addr = 0;
if (addr) {
ath6kl_bmi_read(ar, addr, (u8 *)&data,
sizeof(unsigned long));
data |= (1<<29);
ath6kl_bmi_write(ar, addr, (u8 *)&data,
sizeof(unsigned long));
ret = 1;
ath6kl_dbg(ATH6KL_DBG_BOOT,
"Enable USB remote wakeup.\n");
} else {
ret = 0;
ath6kl_dbg(ATH6KL_DBG_BOOT,
"Hw not supporting USB remote wakeup, disable it.\n");
}
}
return ret;
}
int ath6kl_usb_diag_warm_reset(struct ath6kl *ar)
{
struct ath6kl_usb *ar_usb = ar->hif_priv;
struct ath6kl_usb_ctrl_diag_cmd_write *cmd;
u32 data, address;
address = ath6kl_get_hi_item_addr(ar, HI_ITEM(hi_reset_flag_valid));
ath6kl_usb_diag_read32(ar, address, &data);
#ifdef ATH6KL_BUS_VOTE
if (ath6kl_platform_has_vreg == 0)
data = 0x12345678;
else
data = 0;
#else
data |= 0x12345678;
#endif
ath6kl_usb_diag_write32(ar, address, data);
address = ath6kl_get_hi_item_addr(ar, HI_ITEM(hi_reset_flag));
ath6kl_usb_diag_read32(ar, address, &data);
data |= 0x20;
ath6kl_usb_diag_write32(ar, address, data);
cmd = (struct ath6kl_usb_ctrl_diag_cmd_write *)ar_usb->diag_cmd_buffer;
memset(cmd, 0, sizeof(struct ath6kl_usb_ctrl_diag_cmd_write));
cmd->cmd = cpu_to_le32(ATH6KL_USB_CTRL_DIAG_CC_WARM_RESET);
return ath6kl_usb_ctrl_msg_exchange(ar_usb,
ATH6KL_USB_CONTROL_REQ_DIAG_CMD,
(u8 *) cmd,
sizeof(*cmd),
0, NULL, NULL);
}
#ifdef ATH6KL_HSIC_RECOVER
static void ath6kl_recover_war_work(struct work_struct *work)
{
if (ath6kl_driver_unloaded != 1) {
ath6kl_info("%s do HSIC rediscovery\n", __func__);
/* check whether driver is unloaded */
if (atomic_read(&ath6kl_usb_unload_state) ==
ATH6KL_USB_UNLOAD_STATE_DRV_DEREG) {
ath6kl_info("%s driver unloaded, exit\n", __func__);
return;
}
atomic_set(&ath6kl_recover_state,
ATH6KL_RECOVER_STATE_IN_PROGRESS);
sema_init(&usb_probe_sem, 1);
down(&usb_probe_sem);
ath6kl_hsic_rediscovery();
/* change the state and wakeup event queue */
atomic_set(&ath6kl_recover_state, ATH6KL_RECOVER_STATE_DONE);
wake_up(&ath6kl_hsic_recover_wq);
}
}
/* schedule ath6kl_recover_war_work */
int ath6kl_hsic_sw_recover(struct ath6kl *ar)
{
struct ath6kl_vif *vif;
struct net_device *netdev;
if (ath6kl_driver_unloaded)
return 0;
if (atomic_read(&ath6kl_usb_unload_state) ==
ATH6KL_USB_UNLOAD_STATE_DRV_DEREG) {
ath6kl_info("%s driver unloaded, exit\n", __func__);
return 0;
}
atomic_set(&ath6kl_recover_state,
ATH6KL_RECOVER_STATE_IN_PROGRESS);
vif = ath6kl_vif_first(ar);
if (vif == NULL)
return 0;
set_bit(RECOVER_IN_PROCESS, &ar->flag);
clear_bit(WMI_READY, &ar->flag);
netdev = vif->ndev;
#ifdef CE_OLD_KERNEL_SUPPORT_2_6_23
netdev->stop(netdev);
#else
netdev->netdev_ops->ndo_stop(netdev);
#endif
ath6kl_info("%s schedule recover worker thread\n", __func__);
schedule_work(&recover_war_work);
return 0;
}
static void ath6kl_reset_war_work(struct work_struct *work)
{
struct ath6kl *ar;
if (ath6kl_driver_unloaded)
return;
if (atomic_read(&ath6kl_usb_unload_state) ==
ATH6KL_USB_UNLOAD_STATE_DRV_DEREG) {
ath6kl_info("%s driver is unloaded, just return\n", __func__);
return;
}
atomic_set(&ath6kl_recover_state,
ATH6KL_RECOVER_STATE_IN_PROGRESS);
ar = container_of(work, struct ath6kl, reset_cover_war_work);
if (ar == NULL)
return;
ath6kl_reset_device(ar, ar->target_type, true, true);
ath6kl_info("%s do HSIC rediscovery\n", __func__);
print_to_file("%s do HSIC rediscovery\n", __func__);
ath6kl_hsic_sw_recover(ar);
atomic_set(&ath6kl_recover_state, ATH6KL_RECOVER_STATE_DONE);
}
#endif
static const struct ath6kl_hif_ops ath6kl_usb_ops = {
.diag_read32 = ath6kl_usb_diag_read32,
.diag_write32 = ath6kl_usb_diag_write32,
.bmi_read = ath6kl_usb_bmi_read,
.bmi_write = ath6kl_usb_bmi_write,
.power_on = ath6kl_usb_power_on,
.power_off = ath6kl_usb_power_off,
.stop = ath6kl_usb_stop,
.get_stat = ath6kl_usb_pipe_stat,
.pipe_register_callback = ath6kl_usb_register_callback,
.pipe_send = ath6kl_usb_send,
.pipe_get_default = ath6kl_usb_get_default_pipe,
.pipe_map_service = ath6kl_usb_map_service_pipe,
.pipe_get_free_queue_number = ath6kl_usb_get_free_queue_number,
.pipe_send_bundle = ath6kl_usb_send_bundle,
.pipe_get_max_queue_number = ath6kl_usb_get_max_queue_number,
.pipe_set_max_queue_number = ath6kl_usb_set_max_queue_number,
.pipe_set_max_sche = ath6kl_usb_set_max_sche,
.suspend = ath6kl_usb_suspend,
.resume = ath6kl_usb_resume,
.cleanup_scatter = ath6kl_usb_cleanup_scatter,
.diag_warm_reset = ath6kl_usb_diag_warm_reset,
#ifdef CONFIG_HAS_EARLYSUSPEND
.early_suspend = ath6kl_usb_early_suspend,
.late_resume = ath6kl_usb_late_resume,
#endif
.bus_config = ath6kl_usb_reconfig,
#ifdef USB_AUTO_SUSPEND
.auto_pm_disable = usb_auto_pm_disable,
.auto_pm_enable = usb_auto_pm_enable,
.auto_pm_turnon = usb_auto_pm_turnon,
.auto_pm_turnoff = usb_auto_pm_turnoff,
.auto_pm_get_usage_cnt = usb_debugfs_get_pm_usage_cnt,
.auto_pm_set_delay = usb_auto_pm_set_delay,
#endif
.pipe_set_rxq_threshold = ath6kl_usb_set_rxq_threshold,
#ifdef ATH6KL_HSIC_RECOVER
.sw_recover = ath6kl_hsic_sw_recover,
#endif
};
#ifdef ATHTST_SUPPORT
static struct hif_product_info_t g_product_info;
void ath6kl_usb_get_usbinfo(void *product_info)
{
memcpy(product_info, &g_product_info, sizeof(g_product_info));
return;
}
#endif
/* ath6kl usb driver registered functions */
static int ath6kl_usb_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(interface);
struct ath6kl *ar;
struct ath6kl_usb *ar_usb = NULL;
int vendor_id, product_id;
int ret = 0;
usb_get_dev(dev);
vendor_id = le16_to_cpu(dev->descriptor.idVendor);
product_id = le16_to_cpu(dev->descriptor.idProduct);
ath6kl_dbg(ATH6KL_DBG_USB | ATH6KL_DBG_EXT_INFO1,
"usb new card added, vendor_id %04x product_id %04x\n",
vendor_id, product_id);
#ifdef ATHTST_SUPPORT
g_product_info.idVendor = vendor_id =
le16_to_cpu(dev->descriptor.idVendor);
g_product_info.idProduct = product_id =
le16_to_cpu(dev->descriptor.idProduct);
if (dev->product)
memcpy(g_product_info.product, dev->product,
sizeof(g_product_info.product));
if (dev->manufacturer)
memcpy(g_product_info.manufacturer, dev->manufacturer,
sizeof(g_product_info.manufacturer));
if (dev->serial)
memcpy(g_product_info.serial, dev->serial,
sizeof(g_product_info.serial));
#endif
if (interface->cur_altsetting)
ath6kl_dbg(ATH6KL_DBG_USB, "USB Interface %d\n",
interface->cur_altsetting->desc.bInterfaceNumber);
if (dev->speed == USB_SPEED_HIGH)
ath6kl_dbg(ATH6KL_DBG_USB, "USB 2.0 Host\n");
else
ath6kl_dbg(ATH6KL_DBG_USB, "USB 1.1 Host\n");
ar_usb = ath6kl_usb_create(interface);
#ifdef ATH6KL_BUS_VOTE
#ifdef CONFIG_ANDROID
if (ath6kl_bt_on == 1 || ath6kl_platform_has_vreg == 0 ||
machine_is_apq8064_dma() || machine_is_apq8064_bueller())
usb_reset_device(ar_usb->udev);
#endif
#endif
if (ar_usb == NULL) {
ath6kl_err("Failed to create USB interface\n");
ret = -ENOMEM;
goto err_usb_put;
}
ar = ath6kl_core_alloc(&ar_usb->udev->dev);
if (ar == NULL) {
ath6kl_err("Failed to alloc ath6kl core\n");
ret = -ENOMEM;
goto err_usb_destroy;
}
ar->hif_priv = ar_usb;
ar->hif_type = ATH6KL_HIF_TYPE_USB;
ar->hif_ops = &ath6kl_usb_ops;
ar->mbox_info.block_size = 16;
ar->bmi.max_data_size = 252;
ar_usb->ar = ar;
#ifdef CONFIG_ANDROID
if (!ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_DISABLE_USB_AUTO_PM))
usb_disable_autosuspend(ar_usb->udev);
#endif
#ifdef USB_AUTO_SUSPEND
spin_lock_init(&ar->usb_pm_lock);
INIT_LIST_HEAD(&ar->usb_pm_skb_queue.list);
pm_runtime_set_autosuspend_delay(&dev->dev, USB_SUSPEND_DELAY_MAX);
if (!ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_DISABLE_USB_AUTO_PM) &&
!(ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_TESTMODE_ENABLE) ||
ath6kl_mod_debug_quirks(ar, ATH6KL_MODULE_ENABLE_EPPING))) {
usb_enable_autosuspend(dev);
}
ar->auto_pm_cnt = 0;
ar->auto_pm_fail_cnt = 0;
ar->autopm_turn_on = 1;
ar->autopm_defer_delay_change_cnt = 0;
ar->autopm_curr_delay_time = USB_SUSPEND_DELAY_MAX;
#endif
#ifdef ATH6KL_HSIC_RECOVER
/* Initialize the worker */
INIT_WORK(&ar->reset_cover_war_work,
ath6kl_reset_war_work);
INIT_WORK(&recover_war_work,
ath6kl_recover_war_work);
/* Initialize the wait queue */
init_waitqueue_head(&ath6kl_hsic_recover_wq);
/* Initialize the state */
atomic_set(&ath6kl_recover_state,
ATH6KL_RECOVER_STATE_INITIALIZED);
#endif
ath6kl_htc_pipe_attach(ar);
ret = ath6kl_core_init(ar);
if (ret) {
ath6kl_err("Failed to init ath6kl core: %d\n", ret);
goto err_core_free;
}
#ifdef ATH6KL_BUS_VOTE
up(&usb_probe_sem);
#endif
return ret;
err_core_free:
ath6kl_core_free(ar);
err_usb_destroy:
ath6kl_usb_destroy(ar_usb);
err_usb_put:
usb_put_dev(dev);
#ifdef ATH6KL_BUS_VOTE
up(&usb_probe_sem);
#endif
return ret;
}
static void ath6kl_usb_remove(struct usb_interface *interface)
{
int war_in_progress;
struct ath6kl_usb *ar_usb;
struct ath6kl *ar;
ar_usb = usb_get_intfdata(interface);
if (ar_usb == NULL)
return;
ar = ar_usb->ar;
war_in_progress = test_bit(RECOVER_IN_PROCESS, &ar->flag);
ath6kl_dbg(ATH6KL_DBG_EXT_INFO1, "usb card removed\n");
usb_put_dev(interface_to_usbdev(interface));
ath6kl_usb_device_detached(interface);
#ifdef ATH6KL_HSIC_RECOVER
if (ath6kl_driver_unloaded == 0 && war_in_progress == 0)
schedule_work(&recover_war_work);
#endif
}
#ifdef CONFIG_PM
#ifdef CONFIG_ANDROID
static int ath6kl_usb_pm_suspend(struct usb_interface *interface,
pm_message_t message)
{
struct ath6kl_usb *device;
struct ath6kl *ar;
struct ath6kl_vif *vif;
int ret;
device = (struct ath6kl_usb *)usb_get_intfdata(interface);
ar = device->ar;
ath6kl_dbg(ATH6KL_DBG_SUSPEND |
ATH6KL_DBG_EXT_AUTOPM,
"usb pm_suspend: ar->state %s\n",
_get_suspend_stat_string(ar->state));
device->pm_suspend_cnt++;
vif = ath6kl_vif_first(ar);
if (ath6kl_android_need_wow_suspend(ar))
ret = ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_WOW, NULL);
else
ret = ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_DEEPSLEEP,
NULL);
if (ret == 0)
ath6kl_usb_flush_all(device);
return ret;
}
#else
static int ath6kl_usb_pm_suspend(struct usb_interface *interface,
pm_message_t message)
{
struct ath6kl_usb *device;
struct ath6kl *ar;
#ifdef USB_AUTO_SUSPEND
int ret;
#endif
device = (struct ath6kl_usb *)usb_get_intfdata(interface);
ar = device->ar;
ath6kl_dbg(ATH6KL_DBG_SUSPEND |
ATH6KL_DBG_EXT_AUTOPM,
"usb pm_suspend: ar->state %s\n",
_get_suspend_stat_string(ar->state));
device->pm_suspend_cnt++;
#ifdef USB_AUTO_SUSPEND
if (ath6kl_android_need_wow_suspend(ar))
ret = ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_WOW, NULL);
else
ret = ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_DEEPSLEEP,
NULL);
#else
if (ar->state != ATH6KL_STATE_WOW)
ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_DEEPSLEEP, NULL);
#endif
ath6kl_usb_flush_all(device);
return 0;
}
#endif
static int ath6kl_usb_pm_resume(struct usb_interface *interface)
{
struct ath6kl_usb *device;
struct ath6kl *ar;
device = (struct ath6kl_usb *)usb_get_intfdata(interface);
ar = device->ar;
#ifdef USB_AUTO_SUSPEND
ath6kl_dbg(ATH6KL_DBG_SUSPEND |
ATH6KL_DBG_EXT_AUTOPM,
"usb pm_resume: ar->state %s, delay_cnt %d\n",
_get_suspend_stat_string(ar->state),
ar->autopm_defer_delay_change_cnt);
#endif
device->pm_resume_cnt++;
/* re-post urbs? */
if (0) {
ath6kl_usb_post_recv_transfers(
&device->pipes[ATH6KL_USB_PIPE_RX_CTRL],
ATH6KL_USB_RX_BUFFER_SIZE);
}
if (!htc_bundle_recv) {
ath6kl_usb_post_recv_transfers(
&device->pipes[ATH6KL_USB_PIPE_RX_DATA],
ATH6KL_USB_RX_BUFFER_SIZE);
} else {
hif_usb_post_recv_bundle_transfers(
&device->pipes[ATH6KL_USB_PIPE_RX_DATA],
0 /* not allocating urb-buffer again */);
if (0)/* no need for bundle mode resume */
hif_usb_post_recv_bundle_transfers(
&device->pipes[ATH6KL_USB_PIPE_RX_DATA2],
0 /* not allocating urb-buffer again */);
}
ath6kl_cfg80211_resume(ar);
#ifdef USB_AUTO_SUSPEND
/* Back to the min. autopm delay when resume count meet delay count. */
if (ar->autopm_defer_delay_change_cnt > 0) {
ath6kl_dbg(ATH6KL_DBG_USB |
ATH6KL_DBG_EXT_AUTOPM,
"autopm pm_resume state %s delay_cnt/time %d/%d\n",
_get_suspend_stat_string(ar->state),
ar->autopm_defer_delay_change_cnt,
ar->autopm_curr_delay_time);
if (--ar->autopm_defer_delay_change_cnt == 0) {
usb_auto_pm_set_delay(ar,
USB_SUSPEND_DELAY_MIN);
}
}
#endif
return 0;
}
static int ath6kl_usb_pm_reset_resume(struct usb_interface *intf)
{
struct ath6kl_usb *device;
struct ath6kl *ar;
device = (struct ath6kl_usb *)usb_get_intfdata(intf);
ar = device->ar;
device->pm_reset_resume_cnt++;
/*
instead of call remove directly, In HSIC mode,
we call pm_resume to make usb continue to work
*/
if (BOOTSTRAP_IS_HSIC(ar->bootstrap_mode)) {
ath6kl_info("ath6kl_usb_pm_reset_resume\n");
ath6kl_usb_pm_resume(intf);
} else {
if (usb_get_intfdata(intf))
ath6kl_usb_remove(intf);
}
return 0;
}
#endif
/* table of devices that work with this driver */
static struct usb_device_id ath6kl_usb_ids[] = {
{USB_DEVICE(0x0cf3, 0x9375)},
{USB_DEVICE(0x0cf3, 0x9374)},
{USB_DEVICE(0x0cf3, 0x9372)},
{ /* Terminating entry */ },
};
MODULE_DEVICE_TABLE(usb, ath6kl_usb_ids);
static struct usb_driver ath6kl_usb_driver = {
.name = "ath6kl_usb",
.probe = ath6kl_usb_probe,
#ifdef CONFIG_PM
.suspend = ath6kl_usb_pm_suspend,
.resume = ath6kl_usb_pm_resume,
.reset_resume = ath6kl_usb_pm_reset_resume,
#endif
.disconnect = ath6kl_usb_remove,
.id_table = ath6kl_usb_ids,
.supports_autosuspend = true,
};
#ifdef CONFIG_ANDROID
static int ath6kl_usb_dev_notify(struct notifier_block *nb,
unsigned long action, void *dev)
{
struct usb_device *udev;
int ret = NOTIFY_DONE;
if (action != USB_DEVICE_REMOVE)
goto done;
udev = (struct usb_device *) dev;
if (ath6kl_usb_unload_dev_num != udev->devnum)
goto done;
if (atomic_read(&ath6kl_usb_unload_state) ==
ATH6KL_USB_UNLOAD_STATE_TARGET_RESET) {
atomic_set(&ath6kl_usb_unload_state,
ATH6KL_USB_UNLOAD_STATE_DEV_DISCONNECTED);
wake_up(&ath6kl_usb_unload_event_wq);
}
done:
return ret;
}
static struct notifier_block ath6kl_usb_dev_nb = {
.notifier_call = ath6kl_usb_dev_notify,
};
static int ath6kl_usb_init(void)
{
init_waitqueue_head(&ath6kl_usb_unload_event_wq);
atomic_set(&ath6kl_usb_unload_state, ATH6KL_USB_UNLOAD_STATE_NULL);
usb_register_notify(&ath6kl_usb_dev_nb);
#ifdef ATH6KL_BUS_VOTE
sema_init(&usb_probe_sem, 1);
down(&usb_probe_sem);
#endif
#ifdef ATH6KL_BUS_VOTE
#ifdef CONFIG_ANDROID
if (machine_is_apq8064_dma() || machine_is_apq8064_bueller()) {
ath6kl_platform_has_vreg = 1;
ath6kl_hsic_bind(1);
}
#endif
if (ath6kl_hsic_init_msm(&ath6kl_platform_has_vreg) != 0)
ath6kl_err("%s ath6kl_hsic_init_msm failed\n", __func__);
#endif
usb_register(&ath6kl_usb_driver);
#ifdef ATH6KL_BUS_VOTE
if (ath6kl_platform_has_vreg) {
u32 probe_timeout;
probe_timeout = USB_PROBE_WAIT_TIMEOUT_ENUM_WAR;
/* Waiting for usb probe callback called */
if (down_timeout(&usb_probe_sem,
msecs_to_jiffies(probe_timeout)) != 0) {
ath6kl_info("can't wait for usb probe done\n");
ath6kl_hsic_enum_war_schedule();
msleep(1000);
}
}
#endif
return 0;
}
static void ath6kl_usb_exit(void)
{
long timeleft = 0;
#if defined(ATH6KL_BUS_VOTE) || defined(ATH6KL_HSIC_RECOVER)
/* If recover is on going, wait for recover is done. */
if (atomic_read(&ath6kl_recover_state) ==
ATH6KL_RECOVER_STATE_IN_PROGRESS) {
timeleft = wait_event_interruptible_timeout(
ath6kl_hsic_recover_wq,
atomic_read(&ath6kl_recover_state) ==
ATH6KL_RECOVER_STATE_DONE,
ATH6KL_RECOVER_WAIT_TIMEOUT);
if (timeleft == 0) {
/* If timeout occurs, wait 2s more */
msleep(2000);
} else if (timeleft > 0) {
if (down_timeout(&usb_probe_sem,
msecs_to_jiffies(USB_PROBE_WAIT_TIMEOUT)) != 0)
ath6kl_info("can't wait for usb probe done, unload\n");
}
}
ath6kl_driver_unloaded = 1;
#endif
atomic_set(&ath6kl_usb_unload_state, ATH6KL_USB_UNLOAD_STATE_DRV_DEREG);
usb_deregister(&ath6kl_usb_driver);
if (atomic_read(&ath6kl_usb_unload_state) !=
ATH6KL_USB_UNLOAD_STATE_TARGET_RESET)
goto finish;
if (!machine_is_apq8064_dma() && !machine_is_apq8064_bueller()) {
timeleft = wait_event_interruptible_timeout(
ath6kl_usb_unload_event_wq,
atomic_read(&ath6kl_usb_unload_state) ==
ATH6KL_USB_UNLOAD_STATE_DEV_DISCONNECTED,
ATH6KL_USB_UNLOAD_TIMEOUT);
}
finish:
usb_unregister_notify(&ath6kl_usb_dev_nb);
#ifdef ATH6KL_BUS_VOTE
ath6kl_hsic_exit_msm();
#endif
#ifdef ATH6KL_BUS_VOTE
if ((machine_is_apq8064_dma() || machine_is_apq8064_bueller()) &&
(ath6kl_bt_on == 0))
ath6kl_hsic_bind(0);
#endif
}
#else
static int ath6kl_usb_init(void)
{
usb_register(&ath6kl_usb_driver);
return 0;
}
static void ath6kl_usb_exit(void)
{
usb_deregister(&ath6kl_usb_driver);
}
#endif
module_init(ath6kl_usb_init);
module_exit(ath6kl_usb_exit);
MODULE_AUTHOR("Atheros Communications, Inc.");
MODULE_DESCRIPTION("Driver support for Atheros AR600x USB devices");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_VERSION(DRV_VERSION);
MODULE_FIRMWARE(AR6004_HW_1_0_FW_DIR "/" AR6004_HW_1_0_FIRMWARE_FILE);
MODULE_FIRMWARE(AR6004_HW_1_0_BOARD_DATA_FILE);
MODULE_FIRMWARE(AR6004_HW_1_0_DEFAULT_BOARD_DATA_FILE);
MODULE_FIRMWARE(AR6004_HW_1_1_FW_DIR "/" AR6004_HW_1_1_FIRMWARE_FILE);
MODULE_FIRMWARE(AR6004_HW_1_1_BOARD_DATA_FILE);
MODULE_FIRMWARE(AR6004_HW_1_1_DEFAULT_BOARD_DATA_FILE);
MODULE_FIRMWARE(AR6004_HW_1_2_FW_DIR "/" AR6004_HW_1_2_FIRMWARE_FILE);
MODULE_FIRMWARE(AR6004_HW_1_2_BOARD_DATA_FILE);
MODULE_FIRMWARE(AR6004_HW_1_2_DEFAULT_BOARD_DATA_FILE);
MODULE_FIRMWARE(AR6004_HW_1_3_FW_DIR "/" AR6004_HW_1_3_FIRMWARE_FILE);
MODULE_FIRMWARE(AR6004_HW_1_3_BOARD_DATA_FILE);
MODULE_FIRMWARE(AR6004_HW_1_3_DEFAULT_BOARD_DATA_FILE);
MODULE_FIRMWARE(AR6004_HW_2_0_FW_DIR "/" AR6004_HW_2_0_FIRMWARE_FILE);
MODULE_FIRMWARE(AR6004_HW_2_0_BOARD_DATA_FILE);
MODULE_FIRMWARE(AR6004_HW_2_0_DEFAULT_BOARD_DATA_FILE);
MODULE_FIRMWARE(AR6006_HW_1_0_FW_DIR "/" AR6006_HW_1_0_FIRMWARE_FILE);
MODULE_FIRMWARE(AR6006_HW_1_0_BOARD_DATA_FILE);
MODULE_FIRMWARE(AR6006_HW_1_0_DEFAULT_BOARD_DATA_FILE);