diff options
Diffstat (limited to 'drivers/net/wireless/ath6kl/os/linux/ar6000_android.c')
-rw-r--r-- | drivers/net/wireless/ath6kl/os/linux/ar6000_android.c | 621 |
1 files changed, 621 insertions, 0 deletions
diff --git a/drivers/net/wireless/ath6kl/os/linux/ar6000_android.c b/drivers/net/wireless/ath6kl/os/linux/ar6000_android.c new file mode 100644 index 000000000000..9d0c3773d4d7 --- /dev/null +++ b/drivers/net/wireless/ath6kl/os/linux/ar6000_android.c @@ -0,0 +1,621 @@ +/* + * + * Copyright (c) 2004-2010 Atheros Communications Inc. + * All rights reserved. + * + * +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation; +// +// Software distributed under the License is distributed on an "AS +// IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +// implied. See the License for the specific language governing +// rights and limitations under the License. +// +// + * + */ +#include "ar6000_drv.h" +#include "htc.h" +#include <linux/vmalloc.h> + +#include <linux/fs.h> +#ifdef CONFIG_PM + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) +#include <linux/wakelock.h> +#endif +enum { + WLAN_PWR_CTRL_UP = 0, + WLAN_PWR_CTRL_CUT_PWR, + WLAN_PWR_CTRL_DEEP_SLEEP, + WLAN_PWR_CTRL_WOW +}; +#include <linux/platform_device.h> +#include <linux/inetdevice.h> + +#define IS_MAC_NULL(mac) (mac[0]==0 && mac[1]==0 && mac[2]==0 && mac[3]==0 && mac[4]==0 && mac[5]==0) +#define MAX_BUF (8*1024) + +#ifdef DEBUG + +#define ATH_DEBUG_DBG_LOG ATH_DEBUG_MAKE_MODULE_MASK(0) + +static ATH_DEBUG_MASK_DESCRIPTION android_debug_desc[] = { + { ATH_DEBUG_DBG_LOG , "Android Debug Logs"}, +}; + +ATH_DEBUG_INSTANTIATE_MODULE_VAR(android, + "android", + "Android Driver Interface", + ATH_DEBUG_MASK_DEFAULTS | ATH_DEBUG_DBG_LOG, + ATH_DEBUG_DESCRIPTION_COUNT(android_debug_desc), + android_debug_desc); + +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) +char fwpath[256] = "/lib/firmware/ath6k/AR6102"; +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) */ +int buspm = WLAN_PWR_CTRL_CUT_PWR; +int wow2mode = WLAN_PWR_CTRL_CUT_PWR; +#define HAVE_WLAN_PWR_IMPL 0 +#if HAVE_WLAN_PWR_IMPL +/** @brief Disable SDIO clock source and mask all interrupt */ +extern void plat_disable_wlan_slot(void); +/** @brief Enable SDIO clock source and unmask all interrupt */ +extern void plat_enable_wlan_slot(void); +#else +#define plat_disable_wlan_slot() +#define plat_enable_wlan_slot() +#endif + +#endif /* CONFIG_PM */ +extern int bmienable; +extern int wlaninitmode; +extern unsigned int wmitimeout; +extern wait_queue_head_t arEvent; +extern struct net_device *ar6000_devices[]; +#ifdef CONFIG_HOST_TCMD_SUPPORT +extern unsigned int testmode; +#endif +extern char ifname[]; + +const char def_ifname[] = "wlan0"; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) +module_param_string(fwpath, fwpath, sizeof(fwpath), 0644); +module_param(buspm, int, 0644); +#else +#define __user +/* for linux 2.4 and lower */ +MODULE_PARM(buspm,"i"); +#endif + +struct wake_lock ar6k_init_wake_lock; +struct wake_lock ar6k_wow_wake_lock; +static A_STATUS (*ar6000_avail_ev_p)(void *, void *); + +extern int ar6000_init(struct net_device *dev); +extern A_STATUS ar6000_configure_target(AR_SOFTC_T *ar); +extern void ar6000_stop_endpoint(struct net_device *dev, A_BOOL keepprofile); +extern A_STATUS ar6000_sysfs_bmi_get_config(AR_SOFTC_T *ar, A_UINT32 mode); +extern void ar6000_destroy(struct net_device *dev, unsigned int unregister); + +static void ar6000_enable_mmchost_detect_change(int enable); +static void ar6000_restart_endpoint(struct net_device *dev); + +#if defined(CONFIG_PM) +static A_STATUS ar6000_suspend_ev(void *context); + +static A_STATUS ar6000_resume_ev(void *context); +#endif + +int android_request_firmware(const struct firmware **firmware_p, const char *name, + struct device *device) +{ + struct file *filp = (struct file *)-ENOENT; + int ret = 0; + mm_segment_t oldfs; + struct firmware *firmware; + char filename[2048]; +#if 0 + const char *raw_filename = strrchr(name, '/'); +#else + const char *raw_filename = NULL; +#endif + *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); + if (!firmware) + return -ENOMEM; + if (raw_filename) + ++raw_filename; + else + raw_filename = name; + sprintf(filename, "%s/%s", fwpath, raw_filename); + // Open file + oldfs = get_fs(); + set_fs(KERNEL_DS); + do { + size_t length, bufsize, bmisize; + struct inode *inode; + filp = filp_open(filename, O_RDONLY, S_IRUSR); + if (IS_ERR(filp) || !filp->f_op) { + printk("%s: file %s filp_open error\n", __FUNCTION__, filename); + ret = -1; + break; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) + inode = filp->f_path.dentry->d_inode; +#else + inode = filp->f_dentry->d_inode; +#endif + if (!inode) { + printk("%s: Get inode from filp failed\n", __FUNCTION__); + ret = -1; + break; + } + length = i_size_read(inode->i_mapping->host); + bufsize = ALIGN(length, PAGE_SIZE); + bmisize = A_ROUND_UP(length, 4); + bufsize = max(bmisize, bufsize); + firmware->data = vmalloc(bufsize); + firmware->size = bmisize; + if (!firmware->data) { + printk("Cannot allocate buffer for firmware\n"); + ret = -ENOMEM; + break; + } + if (filp->f_op->read(filp, (char*)firmware->data, length, &filp->f_pos) != length) { + printk("%s: file read error, remaining=%d\n", __FUNCTION__, length); + ret = -1; + break; + } + } while (0); + + if (!IS_ERR(filp)) { + filp_close(filp, NULL); + } + set_fs(oldfs); + + if (ret!=0) { + if (firmware) { + if (firmware->data) + vfree(firmware->data); + kfree(firmware); + } + *firmware_p = NULL; + } + return ret; +} + +void android_release_firmware(const struct firmware *firmware) +{ + if (firmware) { + if (firmware->data) + vfree(firmware->data); + kfree(firmware); + } +} + +#if defined(CONFIG_PM) +static void ar6k_send_asleep_event_to_app(AR_SOFTC_T *ar, A_BOOL asleep) +{ + char buf[128]; + union iwreq_data wrqu; + + snprintf(buf, sizeof(buf), "HOST_ASLEEP=%s", asleep ? "asleep" : "awake"); + A_MEMZERO(&wrqu, sizeof(wrqu)); + wrqu.data.length = strlen(buf); + wireless_send_event(ar->arNetDev, IWEVCUSTOM, &wrqu, buf); +} + +static void ar6000_wow_resume(AR_SOFTC_T *ar) +{ + if (ar->arWowState) { + WMI_SET_HOST_SLEEP_MODE_CMD hostSleepMode = {TRUE, FALSE}; + ar->arWowState = 0; + wmi_set_host_sleep_mode_cmd(ar->arWmi, &hostSleepMode); + wmi_scanparams_cmd(ar->arWmi, 0,0,60,0,0,0,0,0,0,0); + //wmi_set_keepalive_cmd(ar->arWmi, 0); + +#if 1 /* we don't do it if the power consumption is already good enough. */ + if (wmi_listeninterval_cmd(ar->arWmi, ar->arListenInterval, 0) == A_OK) { + } +#endif + ar6k_send_asleep_event_to_app(ar, FALSE); + if (ar->arTxPending[ar->arControlEp]) { + long timeleft = wait_event_interruptible_timeout(arEvent, + ar->arTxPending[ar->arControlEp] == 0, wmitimeout * HZ); + if (!timeleft || signal_pending(current)) { + printk("Failed to Resume Wow!!!!!!!!!!!!!!!!!!!!\n"); + } else { + printk("Resume WoW successfully\n"); + } + } + } else { + printk("WoW does not invoked. skip resume"); + } +} + +static void ar6000_wow_suspend(AR_SOFTC_T *ar) +{ +#define ANDROID_WOW_LIST_ID 1 + if (ar->arNetworkType != AP_NETWORK) { + /* Setup WoW for unicast & Aarp request for our own IP + disable background scan. Set listen interval into 1000 TUs + Enable keepliave for 110 seconds + */ + struct in_ifaddr **ifap = NULL; + struct in_ifaddr *ifa = NULL; + struct in_device *in_dev; + A_UINT8 macMask[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + A_STATUS status; + WMI_ADD_WOW_PATTERN_CMD addWowCmd = { .filter = { 0 } }; + WMI_DEL_WOW_PATTERN_CMD delWowCmd; + WMI_SET_HOST_SLEEP_MODE_CMD hostSleepMode = {FALSE, TRUE}; + WMI_SET_WOW_MODE_CMD wowMode = { .enable_wow = TRUE }; + + ar6000_TxDataCleanup(ar); /* IMPORTANT, otherwise there will be 11mA after listen interval as 1000*/ + +#if 1 /* we don't do it if the power consumption is already good enough. */ + if (wmi_listeninterval_cmd(ar->arWmi, A_MAX_WOW_LISTEN_INTERVAL, 0) == A_OK) { + } +#endif + +// wmi_set_keepalive_cmd(ar->arWmi, 110); /* keepalive otherwise, we will be disconnected*/ + status = wmi_scanparams_cmd(ar->arWmi, 0,0,0xffff,0,0,0,0,0,0,0); + wmi_set_wow_mode_cmd(ar->arWmi, &wowMode); + + /* clear up our WoW pattern first */ + delWowCmd.filter_list_id = ANDROID_WOW_LIST_ID; + delWowCmd.filter_id = 0; + wmi_del_wow_pattern_cmd(ar->arWmi, &delWowCmd); + + /* setup unicast packet pattern for WoW */ + if (ar->arNetDev->dev_addr[1]) { + addWowCmd.filter_list_id = ANDROID_WOW_LIST_ID; + addWowCmd.filter_size = 6; /* MAC address */ + addWowCmd.filter_offset = 2; + status = wmi_add_wow_pattern_cmd(ar->arWmi, &addWowCmd, ar->arNetDev->dev_addr, macMask, addWowCmd.filter_size); + } + /* setup ARP request for our own IP */ + if ((in_dev = __in_dev_get_rtnl(ar->arNetDev)) != NULL) { + for (ifap = &in_dev->ifa_list; (ifa = *ifap) != NULL; ifap = &ifa->ifa_next) { + if (!strcmp(ar->arNetDev->name, ifa->ifa_label)) { + break; /* found */ + } + } + } + if (ifa && ifa->ifa_local) { + WMI_SET_IP_CMD ipCmd; + memset(&ipCmd, 0, sizeof(ipCmd)); + ipCmd.ips[0] = ifa->ifa_local; + status = wmi_set_ip_cmd(ar->arWmi, &ipCmd); + } + ar6k_send_asleep_event_to_app(ar, TRUE); + wmi_set_host_sleep_mode_cmd(ar->arWmi, &hostSleepMode); + if (ar->arTxPending[ar->arControlEp]) { + long timeleft = wait_event_interruptible_timeout(arEvent, + ar->arTxPending[ar->arControlEp] == 0, wmitimeout * HZ); + if (!timeleft || signal_pending(current)) { + /* what can I do? wow resume at once */ + printk("Fail to setup WoW\n"); + } else { + ar->arWowState = 1; + printk("Setup WoW successfully\n"); + } + } + mdelay(10); + } else { + printk("Not allowed to go to WOW at this moment.\n"); + } +} + +static void ar6000_pwr_on(AR_SOFTC_T *ar) +{ + if (ar == NULL) { + /* turn on for all cards */ + } + printk("%s --enter\n", __func__); + +} + +static void ar6000_pwr_down(AR_SOFTC_T *ar) +{ + if (ar == NULL) { + /* shutdown for all cards */ + } + printk("%s --enter\n", __func__); + +} + +static A_STATUS ar6000_suspend_ev(void *context) +{ + int pmmode = buspm; + AR_SOFTC_T *ar = (AR_SOFTC_T *)context; + printk("%s: enter ar %p devices %p\n", __func__, ar, ar6000_devices[0]? netdev_priv(ar6000_devices[0]) : NULL); + +wow_not_connected: + + switch (pmmode) { + case WLAN_PWR_CTRL_DEEP_SLEEP: + ar6000_set_wlan_state(ar, WLAN_DISABLED); + ar->arOsPowerCtrl = WLAN_PWR_CTRL_DEEP_SLEEP; + return A_EBUSY; + case WLAN_PWR_CTRL_WOW: + if (ar->arWmiReady && ar->arWlanState==WLAN_ENABLED && ar->arConnected) { + ar->arOsPowerCtrl = WLAN_PWR_CTRL_WOW; + /* leave for pm_device to setup wow */ + return A_EBUSY; + } else { + pmmode = wow2mode; + goto wow_not_connected; + } + break; + case WLAN_PWR_CTRL_CUT_PWR: + /* fall through */ + default: + ar->arOsPowerCtrl = WLAN_PWR_CTRL_CUT_PWR; + ar6000_stop_endpoint(ar->arNetDev, TRUE); + ar->arWlanState = WLAN_DISABLED; + break; + } + return A_OK; +} + +static A_STATUS ar6000_resume_ev(void *context) +{ + AR_SOFTC_T *ar = (AR_SOFTC_T *)context; + A_UINT16 powerCtrl = ar->arOsPowerCtrl; + wake_lock(&ar6k_init_wake_lock); + printk("%s: enter\n", __func__); + ar->arOsPowerCtrl = WLAN_PWR_CTRL_UP; + switch (powerCtrl) { + case WLAN_PWR_CTRL_WOW: + printk("Warning! resume but osPowerCtl is not clear\n"); + break; + case WLAN_PWR_CTRL_CUT_PWR: + ar6000_restart_endpoint(ar->arNetDev); + break; + case WLAN_PWR_CTRL_DEEP_SLEEP: + ar6000_set_wlan_state(ar, WLAN_ENABLED); + break; + default: + printk("Strange SDIO bus power mode!!\n"); + break; + } + wake_unlock(&ar6k_init_wake_lock); + return A_OK; +} + +static A_STATUS ar6000_android_avail_ev(void *context, void *hif_handle) +{ + A_STATUS ret; + wake_lock(&ar6k_init_wake_lock); + ret = ar6000_avail_ev_p(context, hif_handle); + wake_unlock(&ar6k_init_wake_lock); + return ret; +} + + +static int ar6000_pm_suspend(struct platform_device *dev, pm_message_t state) +{ + int i; + for (i = 0; i < MAX_AR6000; i++) { + AR_SOFTC_T *ar; + + if (ar6000_devices[i] == NULL) + continue; + ar = (AR_SOFTC_T*)netdev_priv(ar6000_devices[i]); + printk("%s: enter\n", __func__); + switch (ar->arOsPowerCtrl) { + case WLAN_PWR_CTRL_CUT_PWR: + ar6000_pwr_down(ar); + break; + case WLAN_PWR_CTRL_WOW: + ar6000_wow_suspend(ar); + plat_disable_wlan_slot(); + break; + case WLAN_PWR_CTRL_DEEP_SLEEP: + /* nothing to do. keep the power on */ + break; + default: + printk("Something is strange for ar6000_pm_suspend %d\n", ar->arOsPowerCtrl); + break; + } + } + return 0; +} + +static int ar6000_pm_resume(struct platform_device *dev) +{ + int i; + for (i = 0; i < MAX_AR6000; i++) { + AR_SOFTC_T *ar; + + if (ar6000_devices[i] == NULL) + continue; + printk("%s: enter\n", __func__); + ar = (AR_SOFTC_T*)netdev_priv(ar6000_devices[i]); + switch (ar->arOsPowerCtrl) { + case WLAN_PWR_CTRL_CUT_PWR: + ar6000_pwr_on(ar); + break; + case WLAN_PWR_CTRL_WOW: + wake_lock_timeout(&ar6k_wow_wake_lock, 3*HZ); + plat_enable_wlan_slot(); + ar6000_wow_resume(ar); + ar->arOsPowerCtrl = WLAN_PWR_CTRL_UP; + break; + case WLAN_PWR_CTRL_DEEP_SLEEP: + /* nothing to do. keep the power on */ + break; + default: + printk("Something is strange for ar6000_pm_resume %d\n", ar->arOsPowerCtrl); + break; + } + } + return 0; +} + +static int ar6000_pm_probe(struct platform_device *pdev) +{ + ar6000_pwr_on(NULL); + return 0; +} + +static int ar6000_pm_remove(struct platform_device *pdev) +{ + ar6000_pwr_down(NULL); + return 0; +} + +static struct platform_driver ar6000_pm_device = { + .probe = ar6000_pm_probe, + .remove = ar6000_pm_remove, + .suspend = ar6000_pm_suspend, + .resume = ar6000_pm_resume, + .driver = { + .name = "wlan_ar6000_pm_dev", + }, +}; +#endif /* CONFIG_PM */ + +/* Useful for qualcom platform to detect our wlan card for mmc stack */ +static void ar6000_enable_mmchost_detect_change(int enable) +{ +#ifdef CONFIG_MMC_MSM + mm_segment_t oldfs; + struct file *filp = (struct file*)-ENOENT; + int length; + oldfs = get_fs(); + set_fs(KERNEL_DS); + do { + char buf[3]; + filp = filp_open("/sys/devices/platform/msm_sdcc.2/detect_change", O_RDWR, S_IRUSR); + if (IS_ERR(filp) || !filp->f_op) + break; + length = snprintf(buf, sizeof(buf), "%d\n", enable ? 1 : 0); + if (filp->f_op->write(filp, buf, length, &filp->f_pos) != length) { + break; + } + } while (0); + if (!IS_ERR(filp)) { + filp_close(filp, NULL); + } + set_fs(oldfs); +#endif +} + +static void +ar6000_restart_endpoint(struct net_device *dev) +{ + A_STATUS status = A_OK; + AR_SOFTC_T *ar = (AR_SOFTC_T*)netdev_priv(dev); + if (down_interruptible(&ar->arSem)) { + AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("%s(): down_interruptible failed \n", __func__)); + return ; + } + if (ar->bIsDestroyProgress) { + up(&ar->arSem); + return; + } + BMIInit(); + do { + A_BOOL rtnl_lock_held_on_entry; + if ( (status=ar6000_configure_target(ar))!=A_OK) + break; + if ( (status=ar6000_sysfs_bmi_get_config(ar, wlaninitmode)) != A_OK) + { + AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("ar6000_avail: ar6000_sysfs_bmi_get_config failed\n")); + break; + } + rtnl_lock_held_on_entry = rtnl_trylock(); + status = (ar6000_init(dev)==0) ? A_OK : A_ERROR; + if (rtnl_lock_held_on_entry) { + rtnl_unlock(); + } + if (status!=A_OK) { + break; + } + ar->arWlanState = WLAN_ENABLED; + if (ar->arSsidLen) { + ar6000_connect_to_ap(ar); + } + } while (0); + + up(&ar->arSem); + if (status==A_OK) { + return; + } + + ar6000_devices[ar->arDeviceIndex] = NULL; + ar6000_destroy(ar->arNetDev, 1); +} + +void android_module_init(OSDRV_CALLBACKS *osdrvCallbacks) +{ + ar6000_enable_mmchost_detect_change(1); + bmienable = 1; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + if (ifname[0] == '\0') + strcpy(ifname, def_ifname); +#endif + if (wow2mode!=WLAN_PWR_CTRL_CUT_PWR && wow2mode!=WLAN_PWR_CTRL_DEEP_SLEEP) { + wow2mode=WLAN_PWR_CTRL_CUT_PWR; + } + + wake_lock_init(&ar6k_init_wake_lock, WAKE_LOCK_SUSPEND, "ar6k_init"); + wake_lock_init(&ar6k_wow_wake_lock, WAKE_LOCK_SUSPEND, "ar6k_wow"); + +#if defined(CONFIG_PM) + osdrvCallbacks->deviceSuspendHandler = ar6000_suspend_ev; + osdrvCallbacks->deviceResumeHandler = ar6000_resume_ev; +#endif + ar6000_avail_ev_p = osdrvCallbacks->deviceInsertedHandler; + osdrvCallbacks->deviceInsertedHandler = ar6000_android_avail_ev; + +#if defined(CONFIG_PM) + /* Register ar6000_pm_device into system. + * We should also add platform_device into the first item of array devices[] in + * file arch/xxx/mach-xxx/board-xxxx.c + * Otherwise, WoW may not work properly since we may trigger WoW GPIO before system suspend + */ + if (platform_driver_register(&ar6000_pm_device)) + printk("ar6000: fail to register the power control driver.\n"); +#endif +} + +void android_module_exit(void) +{ + wake_lock_destroy(&ar6k_wow_wake_lock); + wake_lock_destroy(&ar6k_init_wake_lock); + +#ifdef CONFIG_PM + platform_driver_unregister(&ar6000_pm_device); +#endif + ar6000_enable_mmchost_detect_change(1); +} + +A_BOOL android_ar6k_endpoint_is_stop(AR_SOFTC_T *ar) +{ +#ifdef CONFIG_PM + return ar->arOsPowerCtrl == WLAN_PWR_CTRL_CUT_PWR; +#else + return FALSE; +#endif +} + +void android_ar6k_check_wow_status(AR_SOFTC_T *ar) +{ +#ifdef CONFIG_PM + if (ar->arWowState) { + ar6000_wow_resume(ar); + } +#endif /* CONFIG_PM */ +} + +A_STATUS android_ar6k_start(AR_SOFTC_T *ar) +{ + return A_OK; +} |