| //------------------------------------------------------------------------------ |
| // Copyright (c) 2004-2010 Atheros Communications Inc. |
| // All rights reserved. |
| // |
| // |
| // |
| // 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. |
| // |
| // |
| // |
| // Author(s): ="Atheros" |
| //------------------------------------------------------------------------------ |
| #include "ar6000_drv.h" |
| #include "htc.h" |
| #include <linux/vmalloc.h> |
| #include <linux/fs.h> |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| #include <linux/earlysuspend.h> |
| #endif |
| |
| bool enable_mmc_host_detect_change = false; |
| static void ar6000_enable_mmchost_detect_change(int enable); |
| |
| |
| char fwpath[256] = "/system/wifi"; |
| int wowledon; |
| unsigned int enablelogcat; |
| |
| extern int bmienable; |
| extern struct net_device *ar6000_devices[]; |
| extern char ifname[]; |
| |
| const char def_ifname[] = "wlan0"; |
| module_param_string(fwpath, fwpath, sizeof(fwpath), 0644); |
| module_param(enablelogcat, uint, 0644); |
| module_param(wowledon, int, 0644); |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| static int screen_is_off; |
| static struct early_suspend ar6k_early_suspend; |
| #endif |
| |
| static int (*ar6000_avail_ev_p)(void *, void *); |
| |
| #if defined(CONFIG_ANDROID_LOGGER) && (!defined(CONFIG_MMC_MSM)) |
| int logger_write(const enum logidx index, |
| const unsigned char prio, |
| const char __kernel * const tag, |
| const char __kernel * const fmt, |
| ...) |
| { |
| int ret = 0; |
| va_list vargs; |
| struct file *filp = (struct file *)-ENOENT; |
| mm_segment_t oldfs; |
| struct iovec vec[3]; |
| int tag_bytes = strlen(tag) + 1, msg_bytes; |
| char *msg; |
| va_start(vargs, fmt); |
| msg = kvasprintf(GFP_ATOMIC, fmt, vargs); |
| va_end(vargs); |
| if (!msg) |
| return -ENOMEM; |
| if (in_interrupt()) { |
| /* we have no choice since aio_write may be blocked */ |
| printk(KERN_ALERT "%s", msg); |
| goto out_free_message; |
| } |
| msg_bytes = strlen(msg) + 1; |
| if (msg_bytes <= 1) /* empty message? */ |
| goto out_free_message; /* don't bother, then */ |
| if ((msg_bytes + tag_bytes + 1) > 2048) { |
| ret = -E2BIG; |
| goto out_free_message; |
| } |
| |
| vec[0].iov_base = (unsigned char *) &prio; |
| vec[0].iov_len = 1; |
| vec[1].iov_base = (void *) tag; |
| vec[1].iov_len = strlen(tag) + 1; |
| vec[2].iov_base = (void *) msg; |
| vec[2].iov_len = strlen(msg) + 1; |
| |
| oldfs = get_fs(); |
| set_fs(KERNEL_DS); |
| do { |
| filp = filp_open("/dev/log/main", O_WRONLY, S_IRUSR); |
| if (IS_ERR(filp) || !filp->f_op) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("%s: filp_open /dev/log/main error\n", __FUNCTION__)); |
| ret = -ENOENT; |
| break; |
| } |
| |
| if (filp->f_op->aio_write) { |
| int nr_segs = sizeof(vec) / sizeof(vec[0]); |
| int len = vec[0].iov_len + vec[1].iov_len + vec[2].iov_len; |
| struct kiocb kiocb; |
| init_sync_kiocb(&kiocb, filp); |
| kiocb.ki_pos = 0; |
| kiocb.ki_left = len; |
| kiocb.ki_nbytes = len; |
| ret = filp->f_op->aio_write(&kiocb, vec, nr_segs, kiocb.ki_pos); |
| } |
| |
| } while (0); |
| |
| if (!IS_ERR(filp)) { |
| filp_close(filp, NULL); |
| } |
| set_fs(oldfs); |
| out_free_message: |
| kfree(msg); |
| return ret; |
| } |
| #endif |
| |
| int android_logger_lv(void *module, int mask) |
| { |
| switch (mask) { |
| case ATH_DEBUG_ERR: |
| return 6; |
| case ATH_DEBUG_INFO: |
| return 4; |
| case ATH_DEBUG_WARN: |
| return 5; |
| case ATH_DEBUG_TRC: |
| return 3; |
| default: |
| #ifdef DEBUG |
| if (!module) { |
| return 3; |
| } else if (module == &GET_ATH_MODULE_DEBUG_VAR_NAME(driver)) { |
| return (mask <=ATH_DEBUG_MAKE_MODULE_MASK(3)) ? 3 : 2; |
| } else if (module == &GET_ATH_MODULE_DEBUG_VAR_NAME(htc)) { |
| return 2; |
| } else { |
| return 3; |
| } |
| #else |
| return 3; /* DEBUG */ |
| #endif |
| } |
| } |
| |
| static int android_readwrite_file(const char *filename, char *rbuf, const char *wbuf, size_t length) |
| { |
| int ret = 0; |
| struct file *filp = (struct file *)-ENOENT; |
| mm_segment_t oldfs; |
| oldfs = get_fs(); |
| set_fs(KERNEL_DS); |
| do { |
| int mode = (wbuf) ? O_RDWR : O_RDONLY; |
| filp = filp_open(filename, mode, S_IRUSR); |
| if (IS_ERR(filp) || !filp->f_op) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("%s: file %s filp_open error\n", __FUNCTION__, filename)); |
| ret = -ENOENT; |
| break; |
| } |
| |
| if (length==0) { |
| /* Read the length of the file only */ |
| struct inode *inode; |
| |
| inode = GET_INODE_FROM_FILEP(filp); |
| if (!inode) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("%s: Get inode from %s failed\n", __FUNCTION__, filename)); |
| ret = -ENOENT; |
| break; |
| } |
| ret = i_size_read(inode->i_mapping->host); |
| break; |
| } |
| |
| if (wbuf) { |
| if ( (ret=filp->f_op->write(filp, wbuf, length, &filp->f_pos)) < 0) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("%s: Write %u bytes to file %s error %d\n", __FUNCTION__, |
| length, filename, ret)); |
| break; |
| } |
| } else { |
| if ( (ret=filp->f_op->read(filp, rbuf, length, &filp->f_pos)) < 0) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("%s: Read %u bytes from file %s error %d\n", __FUNCTION__, |
| length, filename, ret)); |
| break; |
| } |
| } |
| } while (0); |
| |
| if (!IS_ERR(filp)) { |
| filp_close(filp, NULL); |
| } |
| set_fs(oldfs); |
| |
| return ret; |
| } |
| |
| int android_request_firmware(const struct firmware **firmware_p, const char *name, |
| struct device *device) |
| { |
| int ret = 0; |
| struct firmware *firmware; |
| char filename[256]; |
| const char *raw_filename = name; |
| *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); |
| if (!firmware) |
| return -ENOMEM; |
| sprintf(filename, "%s/%s", fwpath, raw_filename); |
| do { |
| size_t length, bufsize, bmisize; |
| |
| if ( (ret=android_readwrite_file(filename, NULL, NULL, 0)) < 0) { |
| break; |
| } else { |
| length = ret; |
| } |
| |
| bufsize = ALIGN(length, PAGE_SIZE); |
| bmisize = A_ROUND_UP(length, 4); |
| bufsize = max(bmisize, bufsize); |
| firmware->data = vmalloc(bufsize); |
| firmware->size = length; |
| if (!firmware->data) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("%s: Cannot allocate buffer for firmware\n", __FUNCTION__)); |
| ret = -ENOMEM; |
| break; |
| } |
| |
| if ( (ret=android_readwrite_file(filename, (char*)firmware->data, NULL, length)) != length) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("%s: file read error, ret %d request %d\n", __FUNCTION__, ret, length)); |
| ret = -1; |
| break; |
| } |
| |
| } while (0); |
| |
| if (ret<0) { |
| if (firmware) { |
| if (firmware->data) |
| vfree(firmware->data); |
| kfree(firmware); |
| } |
| *firmware_p = NULL; |
| } else { |
| ret = 0; |
| } |
| return ret; |
| } |
| |
| void android_release_firmware(const struct firmware *firmware) |
| { |
| if (firmware) { |
| if (firmware->data) |
| vfree(firmware->data); |
| kfree(firmware); |
| } |
| } |
| |
| static int ar6000_android_avail_ev(void *context, void *hif_handle) |
| { |
| int ret; |
| ar6000_enable_mmchost_detect_change(0); |
| ret = ar6000_avail_ev_p(context, hif_handle); |
| return ret; |
| } |
| |
| /* 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 |
| #define MMC_MSM_DEV "msm_sdcc.1" |
| char buf[3]; |
| int length; |
| |
| if (!enable_mmc_host_detect_change) { |
| return; |
| } |
| length = snprintf(buf, sizeof(buf), "%d\n", enable ? 1 : 0); |
| if (android_readwrite_file("/sys/devices/platform/" MMC_MSM_DEV "/detect_change", |
| NULL, buf, length) < 0) { |
| /* fall back to polling */ |
| android_readwrite_file("/sys/devices/platform/" MMC_MSM_DEV "/polling", NULL, buf, length); |
| } |
| #endif |
| } |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| static void android_early_suspend(struct early_suspend *h) |
| { |
| screen_is_off = 1; |
| } |
| |
| static void android_late_resume(struct early_suspend *h) |
| { |
| screen_is_off = 0; |
| } |
| #endif |
| |
| void android_module_init(OSDRV_CALLBACKS *osdrvCallbacks) |
| { |
| bmienable = 1; |
| if (ifname[0] == '\0') |
| strcpy(ifname, def_ifname); |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| ar6k_early_suspend.suspend = android_early_suspend; |
| ar6k_early_suspend.resume = android_late_resume; |
| ar6k_early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN; |
| register_early_suspend(&ar6k_early_suspend); |
| #endif |
| |
| ar6000_avail_ev_p = osdrvCallbacks->deviceInsertedHandler; |
| osdrvCallbacks->deviceInsertedHandler = ar6000_android_avail_ev; |
| |
| ar6000_enable_mmchost_detect_change(1); |
| } |
| |
| void android_module_exit(void) |
| { |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| unregister_early_suspend(&ar6k_early_suspend); |
| #endif |
| ar6000_enable_mmchost_detect_change(1); |
| } |
| |
| #ifdef CONFIG_PM |
| void android_ar6k_check_wow_status(struct ar6_softc *ar, struct sk_buff *skb, bool isEvent) |
| { |
| if ( |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| screen_is_off && |
| #endif |
| skb && ar->arConnected) { |
| bool needWake = false; |
| if (isEvent) { |
| if (A_NETBUF_LEN(skb) >= sizeof(u16)) { |
| u16 cmd = *(const u16 *)A_NETBUF_DATA(skb); |
| switch (cmd) { |
| case WMI_CONNECT_EVENTID: |
| case WMI_DISCONNECT_EVENTID: |
| needWake = true; |
| break; |
| default: |
| /* dont wake lock the system for other event */ |
| break; |
| } |
| } |
| } else if (A_NETBUF_LEN(skb) >= sizeof(ATH_MAC_HDR)) { |
| ATH_MAC_HDR *datap = (ATH_MAC_HDR *)A_NETBUF_DATA(skb); |
| if (!IEEE80211_IS_MULTICAST(datap->dstMac)) { |
| switch (A_BE2CPU16(datap->typeOrLen)) { |
| case 0x0800: /* IP */ |
| case 0x888e: /* EAPOL */ |
| case 0x88c7: /* RSN_PREAUTH */ |
| case 0x88b4: /* WAPI */ |
| needWake = true; |
| break; |
| case 0x0806: /* ARP is not important to hold wake lock */ |
| default: |
| break; |
| } |
| } |
| } |
| if (needWake) { |
| /* keep host wake up if there is any event and packate comming in*/ |
| if (wowledon) { |
| char buf[32]; |
| int len = sprintf(buf, "on"); |
| android_readwrite_file("/sys/power/state", NULL, buf, len); |
| |
| len = sprintf(buf, "%d", 127); |
| android_readwrite_file("/sys/class/leds/lcd-backlight/brightness", |
| NULL, buf,len); |
| } |
| } |
| } |
| } |
| #endif /* CONFIG_PM */ |