blob: b934ac2c256700c999fcdd39a0f022fd42862816 [file] [log] [blame]
/*
* Copyright (c) 2013-2016 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, 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.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
#include <osdep.h>
#include <linux/usb.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/if_arp.h>
#include <linux/usb/hcd.h>
#include "if_usb.h"
#include "hif_usb_internal.h"
#include "bmi_msg.h" /* TARGET_TYPE_ */
#include "regtable.h"
#include "ol_fw.h"
#include <osapi_linux.h>
#include "vos_api.h"
#include "wma_api.h"
#include "wlan_hdd_main.h"
#include "epping_main.h"
#include "vos_sched.h"
#ifndef REMOVE_PKT_LOG
#include "ol_txrx_types.h"
#include "pktlog_ac_api.h"
#include "pktlog_ac.h"
#endif
#define VENDOR_ATHR 0x0CF3
#define AR9888_DEVICE_ID (0x003c)
#define AR6320_DEVICE_ID (0x9378)
#define DELAY_FOR_TARGET_READY 200 /* 200ms */
#define DELAY_INT_FOR_HDD_REMOVE 200 /* 200ms */
#define HIFDiagWriteCOLDRESET(hifdevice) HIFDiagWriteAccess(sc->hif_device, \
(ROME_USB_SOC_RESET_CONTROL_COLD_RST_LSB | \
ROME_USB_RTC_SOC_BASE_ADDRESS), \
SOC_RESET_CONTROL_COLD_RST_SET(1))
unsigned int msienable;
module_param(msienable, int, 0644);
#define HIF_USB_UNLOAD_TIMEOUT (2*HZ)
enum hif_usb_drv_unload_state {
HIF_USB_UNLOAD_STATE_NULL = 0,
HIF_USB_UNLOAD_STATE_DRV_DEREG,
HIF_USB_UNLOAD_STATE_TARGET_RESET,
HIF_USB_UNLOAD_STATE_DEV_DISCONNECTED,
};
static int hif_usb_unload_dev_num = -1;
static wait_queue_head_t hif_usb_unload_event_wq;
static atomic_t hif_usb_unload_state;
struct hif_usb_softc *usb_sc = NULL;
static int hif_usb_resume(struct usb_interface *interface);
static int
hif_usb_configure(struct hif_usb_softc *sc, hif_handle_t *hif_hdl,
struct usb_interface *interface)
{
int ret = 0;
struct usb_device *dev = interface_to_usbdev(interface);
if (HIF_USBDeviceInserted(interface, sc)) {
pr_err("ath: %s: Target probe failed.\n", __func__);
ret = -EIO;
goto err_stalled;
}
if (athdiag_procfs_init(sc) != 0) {
pr_err("athdiag_procfs_init failed\n");
return A_ERROR;
}
hif_usb_unload_dev_num = dev->devnum;
*hif_hdl = sc->hif_device;
return 0;
err_stalled:
return ret;
}
static void hif_nointrs(struct hif_usb_softc *sc)
{
}
static int hif_usb_reboot(struct notifier_block *nb, unsigned long val,
void *v)
{
struct hif_usb_softc *sc;
sc = container_of(nb, struct hif_usb_softc, reboot_notifier);
/* do cold reset */
HIFDiagWriteCOLDRESET(sc->hif_device);
return NOTIFY_DONE;
}
/*
* Disable lpm feature of usb2.0.
*/
static int hif_usb_disable_lpm(struct usb_device *udev)
{
struct usb_hcd *hcd;
int ret = -EPERM;
pr_info("Enter:%s,Line:%d\n", __func__, __LINE__);
if (!udev || !udev->bus) {
pr_err("Invalid input parameters\n");
} else {
hcd = bus_to_hcd(udev->bus);
if (udev->usb2_hw_lpm_enabled) {
if (hcd->driver->set_usb2_hw_lpm) {
ret = hcd->driver->set_usb2_hw_lpm(hcd,
udev, FALSE);
if (!ret) {
udev->usb2_hw_lpm_enabled = FALSE;
udev->usb2_hw_lpm_capable = FALSE;
pr_info("%s: LPM is disabled\n",
__func__);
} else {
pr_info("%s: Fail to disable LPM\n",
__func__);
}
} else {
pr_info("%s: hcd doesn't support LPM\n",
__func__);
}
} else {
pr_info("%s: LPM isn't enabled\n", __func__);
}
}
pr_info("Exit:%s,Line:%d\n", __func__, __LINE__);
return ret;
}
static int
hif_usb_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
int ret = 0;
struct hif_usb_softc *sc;
struct ol_softc *ol_sc;
struct usb_device *pdev = interface_to_usbdev(interface);
int vendor_id, product_id;
pr_info("hif_usb_probe\n");
usb_get_dev(pdev);
vendor_id = le16_to_cpu(pdev->descriptor.idVendor);
product_id = le16_to_cpu(pdev->descriptor.idProduct);
ret = 0;
sc = A_MALLOC(sizeof(*sc));
if (!sc) {
ret = -ENOMEM;
goto err_alloc;
}
OS_MEMZERO(sc, sizeof(*sc));
sc->pdev = (void *)pdev;
sc->dev = &pdev->dev;
sc->aps_osdev.bdev = pdev;
sc->aps_osdev.device = &pdev->dev;
sc->aps_osdev.bc.bc_bustype = HAL_BUS_TYPE_AHB;
sc->devid = id->idProduct;
adf_os_spinlock_init(&sc->target_lock);
ol_sc = A_MALLOC(sizeof(*ol_sc));
if (!ol_sc)
goto err_attach;
OS_MEMZERO(ol_sc, sizeof(*ol_sc));
ol_sc->sc_osdev = &sc->aps_osdev;
ol_sc->hif_sc = (void *)sc;
sc->ol_sc = ol_sc;
if ((usb_control_msg(pdev, usb_sndctrlpipe(pdev, 0),
USB_REQ_SET_CONFIGURATION, 0, 1, 0, NULL, 0,
HZ)) < 0) {
pr_info("%s[%d]\n\r", __func__, __LINE__);
}
usb_set_interface(pdev, 0, 0);
/* disable lpm to avoid usb2.0 probe timeout */
hif_usb_disable_lpm(pdev);
if (hif_usb_configure(sc, &ol_sc->hif_hdl, interface))
goto err_config;
ol_sc->enableuartprint = 1;
ol_sc->enablefwlog = 0;
ol_sc->enablesinglebinary = FALSE;
ol_sc->max_no_of_peers = 1;
init_waitqueue_head(&ol_sc->sc_osdev->event_queue);
ret = hif_init_adf_ctx(ol_sc);
if (ret == 0)
ret = hdd_wlan_startup(&pdev->dev, ol_sc);
if (ret) {
hif_nointrs(sc);
if (sc->hif_device != NULL) {
((HIF_DEVICE_USB *)(sc->hif_device))->sc = NULL;
}
athdiag_procfs_remove();
goto err_config;
}
atomic_set(&sc->hdd_removed, -1);
atomic_set(&sc->hdd_removed_processing, 0);
sc->hdd_removed_wait_cnt = 0;
sc->interface = interface;
sc->reboot_notifier.notifier_call = hif_usb_reboot;
register_reboot_notifier(&sc->reboot_notifier);
usb_sc = sc;
return 0;
err_config:
hif_deinit_adf_ctx(ol_sc);
HIFDiagWriteCOLDRESET(sc->hif_device);
A_FREE(ol_sc);
err_attach:
ret = -EIO;
usb_sc = NULL;
A_FREE(sc);
err_alloc:
usb_put_dev(pdev);
return ret;
}
static void hif_usb_remove(struct usb_interface *interface)
{
HIF_DEVICE_USB *device = usb_get_intfdata(interface);
struct usb_device *udev = interface_to_usbdev(interface);
struct hif_usb_softc *sc = device->sc;
struct ol_softc *scn;
/* Attach did not succeed, all resources have been
* freed in error handler
*/
if (!sc)
return;
pr_info("Try to remove hif_usb!\n");
/* wait __hdd_wlan_exit until finished and no more than 4 seconds*/
while(atomic_read(&usb_sc->hdd_removed_processing) == 1 &&
usb_sc->hdd_removed_wait_cnt < 20) {
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(msecs_to_jiffies(DELAY_INT_FOR_HDD_REMOVE));
set_current_state(TASK_RUNNING);
usb_sc->hdd_removed_wait_cnt ++;
}
atomic_set(&usb_sc->hdd_removed_processing, 1);
vos_set_shutdown_in_progress(VOS_MODULE_ID_HIF, TRUE);
/* disable lpm to avoid following cold reset will
*cause xHCI U1/U2 timeout
*/
usb_disable_lpm(udev);
/* wait for disable lpm */
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(msecs_to_jiffies(DELAY_FOR_TARGET_READY));
set_current_state(TASK_RUNNING);
/* do cold reset */
HIFDiagWriteCOLDRESET(sc->hif_device);
unregister_reboot_notifier(&sc->reboot_notifier);
usb_put_dev(interface_to_usbdev(interface));
if (atomic_read(&hif_usb_unload_state) ==
HIF_USB_UNLOAD_STATE_DRV_DEREG)
atomic_set(&hif_usb_unload_state,
HIF_USB_UNLOAD_STATE_TARGET_RESET);
scn = sc->ol_sc;
/* The logp is set by target failure's ol_ramdump_handler.
* Coldreset occurs and do this disconnect cb, try to issue
* offline uevent to restart driver.
*/
if (vos_is_logp_in_progress(VOS_MODULE_ID_VOSS, NULL)) {
/* dispatch 'offline' uevent to restart module */
kobject_uevent(&scn->adf_dev->dev->kobj, KOBJ_OFFLINE);
vos_set_logp_in_progress(VOS_MODULE_ID_VOSS, FALSE);
}
if (atomic_inc_and_test(&usb_sc->hdd_removed)) {
#ifndef REMOVE_PKT_LOG
if (vos_get_conparam() != VOS_FTM_MODE &&
!WLAN_IS_EPPING_ENABLED(vos_get_conparam()))
pktlogmod_exit(scn);
#endif
__hdd_wlan_exit();
pr_info("Exit HDD wlan... done by %s\n", __func__);
}
hif_nointrs(sc);
HIF_USBDeviceDetached(interface, 1);
vos_set_shutdown_in_progress(VOS_MODULE_ID_HIF, FALSE);
atomic_set(&usb_sc->hdd_removed_processing, 0);
hif_deinit_adf_ctx(scn);
A_FREE(scn);
A_FREE(sc);
usb_sc = NULL;
pr_info("hif_usb_remove!!!!!!\n");
}
#ifdef WLAN_LINK_UMAC_SUSPEND_WITH_BUS_SUSPEND
void hdd_suspend_wlan(void (*callback) (void *callbackContext),
void *callbackContext);
#endif
static int hif_usb_suspend(struct usb_interface *interface, pm_message_t state)
{
HIF_DEVICE_USB *device = usb_get_intfdata(interface);
struct hif_usb_softc *sc = device->sc;
void *vos = vos_get_global_context(VOS_MODULE_ID_HIF, NULL);
v_VOID_t * temp_module;
printk("Enter:%s,Line:%d\n", __func__,__LINE__);
temp_module = vos_get_context(VOS_MODULE_ID_WDA, vos);
if (!temp_module) {
printk("%s: WDA module is NULL\n", __func__);
return (-1);
}
if (wma_check_scan_in_progress(temp_module)) {
printk("%s: Scan in progress. Aborting suspend\n", __func__);
return (-1);
}
sc->suspend_state = 1;
usb_hif_flush_all(device);
printk("Exit:%s,Line:%d\n", __func__,__LINE__);
return 0;
}
#ifdef WLAN_LINK_UMAC_SUSPEND_WITH_BUS_SUSPEND
void hdd_resume_wlan(void);
#endif
static int hif_usb_resume(struct usb_interface *interface)
{
HIF_DEVICE_USB *device = usb_get_intfdata(interface);
struct hif_usb_softc *sc = device->sc;
void *vos = vos_get_global_context(VOS_MODULE_ID_HIF, NULL);
v_VOID_t * temp_module;
printk("Enter:%s,Line:%d\n", __func__,__LINE__);
temp_module = vos_get_context(VOS_MODULE_ID_WDA, vos);
if (!temp_module) {
printk("%s: WDA module is NULL\n", __func__);
return (-1);
}
sc->suspend_state = 0;
usb_hif_start_recv_pipes(device);
#ifdef USB_HIF_TEST_INTERRUPT_IN
usb_hif_post_recv_transfers(&device->pipes[HIF_RX_INT_PIPE],
HIF_USB_RX_BUFFER_SIZE);
#endif
printk("Exit:%s,Line:%d\n", __func__,__LINE__);
return 0;
}
static int hif_usb_reset_resume(struct usb_interface *intf)
{
HIF_DEVICE_USB *device = usb_get_intfdata(intf);
struct hif_usb_softc *sc = device->sc;
printk("Enter:%s,Line:%d \n\r", __func__,__LINE__);
HIFDiagWriteCOLDRESET(sc->hif_device);
printk("Exit:%s,Line:%d \n\r", __func__,__LINE__);
return 0;
}
static struct usb_device_id hif_usb_id_table[] = {
{USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ATHR, 0x9378, 0xFF, 0xFF, 0xFF)},
{USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ATHR, 0x9379, 0xFF, 0xFF, 0xFF)},
{} /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, hif_usb_id_table);
struct usb_driver hif_usb_drv_id = {
.name = "hif_usb",
.id_table = hif_usb_id_table,
.probe = hif_usb_probe,
.disconnect = hif_usb_remove,
#ifdef ATH_BUS_PM
.suspend = hif_usb_suspend,
.resume = hif_usb_resume,
.reset_resume = hif_usb_reset_resume,
#endif
.supports_autosuspend = true,
};
int hif_init_adf_ctx(void *ol_sc)
{
adf_os_device_t adf_ctx;
v_CONTEXT_t pVosContext = NULL;
struct ol_softc *sc = (struct ol_softc *)ol_sc;
struct hif_usb_softc *hif_sc = (struct hif_usb_softc *)sc->hif_sc;
pVosContext = vos_get_global_context(VOS_MODULE_ID_SYS, NULL);
if(pVosContext == NULL)
return -EFAULT;
adf_ctx = vos_mem_malloc(sizeof(*adf_ctx));
if (!adf_ctx)
return -ENOMEM;
vos_mem_zero(adf_ctx, sizeof(*adf_ctx));
adf_ctx->drv = &hif_sc->aps_osdev;
adf_ctx->drv_hdl = hif_sc->aps_osdev.bdev;
adf_ctx->dev = hif_sc->aps_osdev.device;
sc->adf_dev = adf_ctx;
((VosContextType*)(pVosContext))->adf_ctx = adf_ctx;
return 0;
}
void hif_deinit_adf_ctx(void *ol_sc)
{
struct ol_softc *sc = (struct ol_softc *)ol_sc;
if (sc == NULL)
return;
if (sc->adf_dev) {
v_CONTEXT_t pVosContext = NULL;
pVosContext = vos_get_global_context(VOS_MODULE_ID_SYS, NULL);
vos_mem_free(sc->adf_dev);
sc->adf_dev = NULL;
if (pVosContext)
((VosContextType*)(pVosContext))->adf_ctx = NULL;
}
}
static int hif_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 (hif_usb_unload_dev_num != udev->devnum)
goto done;
if (atomic_read(&hif_usb_unload_state) ==
HIF_USB_UNLOAD_STATE_TARGET_RESET) {
atomic_set(&hif_usb_unload_state,
HIF_USB_UNLOAD_STATE_DEV_DISCONNECTED);
wake_up(&hif_usb_unload_event_wq);
}
done:
return ret;
}
static struct notifier_block hif_usb_dev_nb = {
.notifier_call = hif_usb_dev_notify,
};
static int is_usb_driver_register = 0;
int hif_register_driver(void)
{
int status = 0;
int probe_wait_cnt = 0;
is_usb_driver_register = 1;
init_waitqueue_head(&hif_usb_unload_event_wq);
atomic_set(&hif_usb_unload_state, HIF_USB_UNLOAD_STATE_NULL);
usb_register_notify(&hif_usb_dev_nb);
status = usb_register(&hif_usb_drv_id);
/* wait for usb probe done, 2s at most*/
while(!usb_sc && probe_wait_cnt < 10) {
A_MSLEEP(200);
probe_wait_cnt++;
}
if (usb_sc && status == 0)
return 0;
else
return -1;
}
void hif_unregister_driver(void)
{
if (is_usb_driver_register) {
long timeleft = 0;
pr_info("Try to unregister hif_driver\n");
if (usb_sc != NULL) {
/* wait __hdd_wlan_exit until finished and no more than
* 4 seconds
*/
while(usb_sc &&
atomic_read(&usb_sc->hdd_removed_processing) == 1 &&
usb_sc->hdd_removed_wait_cnt < 20) {
usb_sc->hdd_removed_wait_cnt ++;
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(msecs_to_jiffies(
DELAY_INT_FOR_HDD_REMOVE));
set_current_state(TASK_RUNNING);
}
/* usb_sc is freed by hif_usb_remove */
if (!usb_sc)
goto deregister;
atomic_set(&usb_sc->hdd_removed_processing, 1);
if (usb_sc->suspend_state) {
hif_usb_resume(usb_sc->interface);
}
if (atomic_inc_and_test(&usb_sc->hdd_removed)) {
#ifndef REMOVE_PKT_LOG
if (vos_get_conparam() != VOS_FTM_MODE &&
!WLAN_IS_EPPING_ENABLED(vos_get_conparam()))
pktlogmod_exit(usb_sc->ol_sc);
#endif
__hdd_wlan_exit();
pr_info("Exit HDD wlan... done by %s\n", __func__);
}
atomic_set(&usb_sc->hdd_removed_processing, 0);
}
deregister:
is_usb_driver_register = 0;
atomic_set(&hif_usb_unload_state,
HIF_USB_UNLOAD_STATE_DRV_DEREG);
usb_deregister(&hif_usb_drv_id);
if (atomic_read(&hif_usb_unload_state) !=
HIF_USB_UNLOAD_STATE_TARGET_RESET)
goto finish;
timeleft = wait_event_interruptible_timeout(
hif_usb_unload_event_wq,
atomic_read(&hif_usb_unload_state) ==
HIF_USB_UNLOAD_STATE_DEV_DISCONNECTED,
HIF_USB_UNLOAD_TIMEOUT);
if (timeleft <= 0)
pr_err("Fail to wait from DRV_DEREG to DISCONNECT,"
"timeleft = %ld \n\r",
timeleft);
finish:
usb_unregister_notify(&hif_usb_dev_nb);
pr_info("hif_unregister_driver!!!!!!\n");
}
}
/* Function to set the TXRX handle in the ol_sc context */
void hif_init_pdev_txrx_handle(void *ol_sc, void *txrx_handle)
{
struct ol_softc *sc = (struct ol_softc *)ol_sc;
sc->pdev_txrx_handle = txrx_handle;
}
void hif_disable_isr(void *ol_sc)
{
/* TODO */
}
/* Function to reset SoC */
void hif_reset_soc(void *ol_sc)
{
/* TODO */
}
void hif_get_hw_info(void *ol_sc, u32 *version, u32 *revision)
{
u_int32_t hif_type, target_type;
A_STATUS rv;
A_INT32 ret = 0;
A_UINT32 chip_id;
struct hif_usb_softc *sc;
sc = ((struct ol_softc *)ol_sc)->hif_sc;
if (sc->hostdef == NULL && sc->targetdef == NULL) {
switch (((struct ol_softc *)ol_sc)->target_type)
{
case TARGET_TYPE_AR6320:
switch(((struct ol_softc *)ol_sc)->target_version) {
case AR6320_REV1_VERSION:
case AR6320_REV1_1_VERSION:
case AR6320_REV1_3_VERSION:
hif_type = HIF_TYPE_AR6320;
target_type = TARGET_TYPE_AR6320;
break;
case AR6320_REV2_1_VERSION:
case AR6320_REV3_VERSION:
case AR6320_REV3_2_VERSION:
case QCA9377_REV1_1_VERSION:
case QCA9379_REV1_VERSION:
hif_type = HIF_TYPE_AR6320V2;
target_type = TARGET_TYPE_AR6320V2;
break;
default:
ret = -1;
break;
}
break;
default:
ret = -1;
break;
}
if (!ret) {
/* assign target register table if we find corresponding type */
hif_register_tbl_attach(sc, hif_type);
target_register_tbl_attach(sc, target_type);
/* read the chip revision*/
rv = HIFDiagReadAccess(sc->hif_device, (CHIP_ID_ADDRESS | RTC_SOC_BASE_ADDRESS), &chip_id);
if (rv != A_OK) {
pr_err("ath: HIF_PCIDeviceProbed get chip id val (%d)\n", rv);
}
((struct ol_softc *)ol_sc)->target_revision = CHIP_ID_REVISION_GET(chip_id);
}
}
/* we need to get chip revision here */
*version = ((struct ol_softc *)ol_sc)->target_version;
/* Chip version should be supported, set to 0 for now */
*revision = ((struct ol_softc *)ol_sc)->target_revision;
}
void hif_set_fw_info(void *ol_sc, u32 target_fw_version)
{
((struct ol_softc *)ol_sc)->target_fw_version = target_fw_version;
}
MODULE_LICENSE("Dual BSD/GPL");