3264 lines
82 KiB
C
3264 lines
82 KiB
C
|
/*
|
||
|
* 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);
|