1425 lines
50 KiB
C
1425 lines
50 KiB
C
//
|
|
//
|
|
// ISC License (ISC)
|
|
//
|
|
// Copyright (c) 2004-2013, The Linux Foundation
|
|
// All rights reserved.
|
|
// Software was previously licensed under 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.
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
|
|
/*
|
|
* Implementation of system power management
|
|
*/
|
|
|
|
#include "ar6000_drv.h"
|
|
#include <linux/inetdevice.h>
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15)
|
|
#include <linux/platform_device.h>
|
|
#endif
|
|
#include "wlan_config.h"
|
|
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
#include <linux/wakelock.h>
|
|
#endif
|
|
|
|
#define WOW_ENABLE_MAX_INTERVAL 1
|
|
#define WOW_SET_SCAN_PARAMS 1
|
|
|
|
extern unsigned int wmitimeout;
|
|
extern unsigned int num_device;
|
|
#if WLAN_CONFIG_FIRST_SCAN_2G_ONLY
|
|
extern unsigned int first_scan_2g_only;
|
|
#endif
|
|
extern unsigned int psm_info;
|
|
#ifdef CONFIG_PM
|
|
struct platform_device *g_pdev = NULL;
|
|
|
|
#if PLAT_WOW_GPIO_PIN || PLAT_WLAN_CHIP_PWD_PIN
|
|
#include <linux/gpio.h>
|
|
#endif
|
|
|
|
#if defined(CONFIG_MMC_MSM) && defined(CONFIG_ARCH_MSM9615)
|
|
#include <linux/gpio.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <mach/msm_xo.h>
|
|
|
|
int wlan_reset_gpio = MSM9615_WLAN_CHIP_PWD_PIN;
|
|
|
|
module_param(wlan_reset_gpio, int, 0644);
|
|
#endif
|
|
|
|
#include "ar6000_dt.h"
|
|
|
|
#if PLAT_WOW_GPIO_PIN
|
|
static int wow_irq;
|
|
#endif /* PLAT_WOW_GPIO_PIN */
|
|
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
struct wake_lock ar6k_suspend_wake_lock;
|
|
struct wake_lock ar6k_wow_wake_lock;
|
|
#endif
|
|
#endif /* CONFIG_PM */
|
|
|
|
#undef ATH_MODULE_NAME
|
|
#define ATH_MODULE_NAME pm
|
|
#define ATH_DEBUG_PM ATH_DEBUG_MAKE_MODULE_MASK(0)
|
|
|
|
#ifdef DEBUG
|
|
static ATH_DEBUG_MASK_DESCRIPTION pm_debug_desc[] = {
|
|
{ ATH_DEBUG_PM , "System power management"},
|
|
};
|
|
|
|
ATH_DEBUG_INSTANTIATE_MODULE_VAR(pm,
|
|
"pm",
|
|
"System Power Management",
|
|
ATH_DEBUG_MASK_DEFAULTS | ATH_DEBUG_PM,
|
|
ATH_DEBUG_DESCRIPTION_COUNT(pm_debug_desc),
|
|
pm_debug_desc);
|
|
|
|
#endif /* DEBUG */
|
|
|
|
A_STATUS ar6000_exit_cut_power_state(AR_SOFTC_T *ar);
|
|
|
|
#ifdef CONFIG_PM
|
|
static void ar6k_send_asleep_event_to_app(AR_SOFTC_DEV_T *arPriv, 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(arPriv->arNetDev, IWEVCUSTOM, &wrqu, buf);
|
|
}
|
|
|
|
static void ar6000_set_host_sleep_mode_callback(void *ptr)
|
|
{
|
|
AR_SOFTC_DEV_T *arPriv = (AR_SOFTC_DEV_T *)ptr;
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("%s: set host sleep mode cmd processed event arrived", __func__));
|
|
arPriv->sleep_mode_cmd_completed = TRUE;
|
|
wake_up(&(arPriv->sleep_mode_cmd_completed_event));
|
|
}
|
|
|
|
static void ar6000_wow_resume(AR_SOFTC_T *ar)
|
|
{
|
|
HIF_DEVICE_POWER_CHANGE_TYPE config;
|
|
AR_SOFTC_DEV_T *arPriv;
|
|
AR_SOFTC_STA_T *arSta;
|
|
A_UINT8 i;
|
|
|
|
if (ar->arWowState!= WLAN_WOW_STATE_NONE) {
|
|
config = HIF_DEVICE_POWER_UP;
|
|
HIFConfigureDevice(ar->arHifDevice,
|
|
HIF_DEVICE_POWER_STATE_CHANGE,
|
|
&config,
|
|
sizeof(HIF_DEVICE_POWER_CHANGE_TYPE));
|
|
|
|
}
|
|
|
|
for(i = 0; i < num_device; i++) {
|
|
arPriv = ar->arDev[i];
|
|
arSta = &arPriv->arSta;
|
|
|
|
/*
|
|
* Free all wlan_nodes (scanned APs that we detected previously)
|
|
* because during the time we were suspended, the device may have
|
|
* been moved to a new location and these APs scanned for before
|
|
* suspend may not be valid
|
|
*/
|
|
wmi_free_allnodes(arPriv->arWmi);
|
|
|
|
if (ar->arWowState!= WLAN_WOW_STATE_NONE) {
|
|
A_UINT16 fg_start_period = (arSta->scParams.fg_start_period==0) ? 1 : arSta->scParams.fg_start_period;
|
|
A_UINT16 bg_period = (arSta->scParams.bg_period==0) ? 60 : arSta->scParams.bg_period;
|
|
WMI_SET_HOST_SLEEP_MODE_CMD hostSleepMode = {TRUE, FALSE};
|
|
ar->arWowState = WLAN_WOW_STATE_NONE;
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
wake_lock_timeout(&ar6k_wow_wake_lock, 5);
|
|
#endif
|
|
/*
|
|
* Clear the callback function. We do not need to wait while resuming.
|
|
*/
|
|
wmi_set_host_sleep_mode_event_fn_ptr(arPriv->arWmi, NULL, NULL);
|
|
if (wmi_set_host_sleep_mode_cmd(arPriv->arWmi, &hostSleepMode)!=A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to setup restore host awake\n"));
|
|
}
|
|
|
|
if (arPriv->arNetworkType!=AP_NETWORK) {
|
|
#if WOW_SET_SCAN_PARAMS
|
|
wmi_scanparams_cmd(arPriv->arWmi, fg_start_period,
|
|
arSta->scParams.fg_end_period,
|
|
bg_period,
|
|
arSta->scParams.minact_chdwell_time,
|
|
arSta->scParams.maxact_chdwell_time,
|
|
arSta->scParams.pas_chdwell_time,
|
|
arSta->scParams.shortScanRatio,
|
|
arSta->scParams.scanCtrlFlags,
|
|
arSta->scParams.max_dfsch_act_time,
|
|
arSta->scParams.maxact_scan_per_ssid);
|
|
#else
|
|
(void)fg_start_period;
|
|
(void)bg_period;
|
|
#endif
|
|
|
|
|
|
#if WOW_ENABLE_MAX_INTERVAL /* we don't do it if the power consumption is already good enough. */
|
|
if (wmi_listeninterval_cmd(arPriv->arWmi, arSta->arListenIntervalT, arSta->arListenIntervalB) != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to restore WoW listen interval %d\n", arSta->arListenIntervalT));
|
|
} else {
|
|
if (wmi_bmisstime_cmd(arPriv->arWmi, arSta->arBmissTimeT, arSta->arBmissTimeB) != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to restore WoW bmiss %d\n", arSta->arBmissTimeT));
|
|
}
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("WoW does not invoked. skip resume"));
|
|
}
|
|
#endif
|
|
}
|
|
ar6k_send_asleep_event_to_app(arPriv, FALSE);
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("Resume WoW successfully\n"));
|
|
ar->isHostAsleep = 0;
|
|
} else {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("WoW does not invoked. skip resume"));
|
|
}
|
|
}
|
|
|
|
ar->arWlanPowerState = WLAN_POWER_STATE_ON;
|
|
}
|
|
|
|
static void ar6000_wow_suspend(AR_SOFTC_T *ar)
|
|
{
|
|
struct net_device *ndev;
|
|
AR_SOFTC_DEV_T *arPriv;
|
|
AR_SOFTC_STA_T *arSta;
|
|
A_INT32 i, j;
|
|
A_STATUS status;
|
|
A_UINT32 timeremaining;
|
|
#define WOW_LIST_ID 1
|
|
/* Setup WoW for unicast & Arp 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;
|
|
HIF_DEVICE_POWER_CHANGE_TYPE config;
|
|
struct in_device *in_dev;
|
|
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,
|
|
.hostReqDelay = 500 };/*500 ms delay*/
|
|
|
|
if (ar->arWowState!= WLAN_WOW_STATE_NONE) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("System already go into wow mode!\n"));
|
|
return;
|
|
}
|
|
|
|
ar6000_TxDataCleanup(ar); /* IMPORTANT, otherwise there will be 11mA after listen interval as 1000*/
|
|
|
|
for(j = 0; j< num_device; j++)
|
|
{
|
|
arPriv = ar->arDev[j];
|
|
arSta = &arPriv->arSta;
|
|
ndev = arPriv->arNetDev;
|
|
/* clear up our WoW pattern first */
|
|
for (i=0; i<WOW_MAX_FILTERS_PER_LIST; ++i) {
|
|
delWowCmd.filter_list_id = WOW_LIST_ID;
|
|
delWowCmd.filter_id = i;
|
|
wmi_del_wow_pattern_cmd(arPriv->arWmi, &delWowCmd);
|
|
}
|
|
|
|
if (arPriv->arNetworkType == AP_NETWORK) {
|
|
/* setup all unicast IP packet pattern for WoW */
|
|
#if WLAN_CONFIG_SIMPLE_WOW_AP_MODE
|
|
/* IP packets except boradcast */
|
|
A_UINT8 allData[] = { 0x08 }; /* Either IP 0x0800, ARP 0x0806 or EAPOL-like 0x8800 */
|
|
A_UINT8 allMask[] = { 0x7f };
|
|
A_MEMZERO(&addWowCmd, sizeof(addWowCmd));
|
|
addWowCmd.filter_list_id = WOW_LIST_ID;
|
|
addWowCmd.filter_size = sizeof(allMask);
|
|
addWowCmd.filter_offset = 20;
|
|
status = wmi_add_wow_pattern_cmd(ar->arWmi, &addWowCmd, allData, allMask, addWowCmd.filter_size);
|
|
if (status != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to add WoW simple pattern for AP mode\n"));
|
|
}
|
|
#else
|
|
/* Unicast IP, EAPOL-like and ARP packets */
|
|
A_UINT8 unicastData[] = {
|
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x08, };
|
|
A_UINT8 unicastMask[] = {
|
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f };
|
|
A_UINT8 discoverData[] = { 0xe0, 0x00, 0x00, 0xf8 };
|
|
A_UINT8 discoverMask[] = { 0xf0, 0x00, 0x00, 0xf8 };
|
|
A_UINT8 arpData[] = { 0x08, 0x06 };
|
|
A_UINT8 arpMask[] = { 0xff, 0xff };
|
|
A_UINT8 dhcpData[] = {
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x08, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x43 /* port 67 */
|
|
};
|
|
A_UINT8 dhcpMask[] = {
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0xff, 0xff /* port 67 */
|
|
};
|
|
A_MEMZERO(&addWowCmd, sizeof(addWowCmd));
|
|
addWowCmd.filter_list_id = WOW_LIST_ID;
|
|
addWowCmd.filter_size = sizeof(unicastMask);
|
|
addWowCmd.filter_offset = 0;
|
|
status = wmi_add_wow_pattern_cmd(arPriv->arWmi, &addWowCmd, unicastData, unicastMask, addWowCmd.filter_size);
|
|
if (status != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to add WoW unicast IP pattern for AP mode\n"));
|
|
}
|
|
/* setup all ARP packet pattern for WoW */
|
|
A_MEMZERO(&addWowCmd, sizeof(addWowCmd));
|
|
addWowCmd.filter_list_id = WOW_LIST_ID;
|
|
addWowCmd.filter_size = sizeof(arpMask);
|
|
addWowCmd.filter_offset = 20;
|
|
status = wmi_add_wow_pattern_cmd(arPriv->arWmi, &addWowCmd, arpData, arpMask, addWowCmd.filter_size);
|
|
if (status != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to add WoW ARP pattern for AP mode\n"));
|
|
}
|
|
/* Setup multicast pattern for mDNS 224.0.0.251, SSDP 239.255.255.250 and LLMNR 224.0.0.252*/
|
|
A_MEMZERO(&addWowCmd, sizeof(addWowCmd));
|
|
addWowCmd.filter_list_id = WOW_LIST_ID;
|
|
addWowCmd.filter_size = sizeof(discoverMask);
|
|
addWowCmd.filter_offset = 38;
|
|
status = wmi_add_wow_pattern_cmd(arPriv->arWmi, &addWowCmd, discoverData, discoverMask, addWowCmd.filter_size);
|
|
if (status != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to add mDNS/SSDP/LLMNR pattern for AP mode WoW\n"));
|
|
}
|
|
/* setup all DHCP broadcast packet pattern for WoW */
|
|
A_MEMZERO(&addWowCmd, sizeof(addWowCmd));
|
|
addWowCmd.filter_list_id = WOW_LIST_ID;
|
|
addWowCmd.filter_size = sizeof(dhcpMask);
|
|
addWowCmd.filter_offset = 0;
|
|
status = wmi_add_wow_pattern_cmd(arPriv->arWmi, &addWowCmd, dhcpData, dhcpMask, addWowCmd.filter_size);
|
|
if (status != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to add WoW DHCP broadcast pattern for AP mode\n"));
|
|
}
|
|
#endif /* WLAN_CONFIG_SIMPLE_WOW_AP_MODE */
|
|
} else {
|
|
/* station mode */
|
|
A_UINT8 macMask[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
|
A_UINT16 wow_listen_interval = A_MAX_WOW_LISTEN_INTERVAL;
|
|
#if WOW_ENABLE_MAX_INTERVAL /* we don't do it if the power consumption is already good enough. */
|
|
if (wmi_listeninterval_cmd(arPriv->arWmi, wow_listen_interval, 0) != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to setup WoW listen interval %d\n", wow_listen_interval));
|
|
} else {
|
|
/*
|
|
* The default bmiss is 1500ms when listen interval is 100ms
|
|
* We set listen interval x 15 times as bmiss time here
|
|
*/
|
|
A_UINT16 bmissTime = wow_listen_interval*15;
|
|
if (bmissTime > MAX_BMISS_TIME) {
|
|
bmissTime = MAX_BMISS_TIME;
|
|
}
|
|
if (wmi_bmisstime_cmd(arPriv->arWmi, bmissTime, 0) != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to setup WoW bmiss %d\n", bmissTime));
|
|
}
|
|
}
|
|
#else
|
|
(void)wow_listen_interval;
|
|
#endif
|
|
|
|
#if WOW_SET_SCAN_PARAMS
|
|
status = wmi_scanparams_cmd(arPriv->arWmi, 0xFFFF, 0, 0xFFFF, 0, 0, 0, 0, 0, 0, 0);
|
|
#endif
|
|
/* setup unicast packet pattern for WoW */
|
|
if (ndev->dev_addr[1]) {
|
|
A_MEMZERO(&addWowCmd, sizeof(addWowCmd));
|
|
addWowCmd.filter_list_id = WOW_LIST_ID;
|
|
addWowCmd.filter_size = 6; /* MAC address */
|
|
addWowCmd.filter_offset = 0;
|
|
status = wmi_add_wow_pattern_cmd(arPriv->arWmi, &addWowCmd, ndev->dev_addr, macMask, addWowCmd.filter_size);
|
|
if (status != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to add WoW pattern\n"));
|
|
}
|
|
}
|
|
|
|
/* Setup multicast pattern for mDNS 224.0.0.251, SSDP 239.255.255.250 and LLMNR 224.0.0.252*/
|
|
if ( ndev->flags & IFF_ALLMULTI ||
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 34)
|
|
(ndev->flags & IFF_MULTICAST && ndev->mc_count>0) )
|
|
#else
|
|
(ndev->flags & IFF_MULTICAST && netdev_mc_count(ndev)>0) )
|
|
#endif
|
|
{
|
|
A_UINT8 discoverData[] = { 0xe0, 0x00, 0x00, 0xf8 };
|
|
A_UINT8 discoverMask[] = { 0xf0, 0x00, 0x00, 0xf8 };
|
|
A_MEMZERO(&addWowCmd, sizeof(addWowCmd));
|
|
addWowCmd.filter_list_id = WOW_LIST_ID;
|
|
addWowCmd.filter_size = sizeof(discoverMask);
|
|
addWowCmd.filter_offset = 38;
|
|
status = wmi_add_wow_pattern_cmd(arPriv->arWmi, &addWowCmd, discoverData, discoverMask, addWowCmd.filter_size);
|
|
if (status != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to add mDNS/SSDP/LLMNR pattern for WoW\n"));
|
|
}
|
|
}
|
|
|
|
ar6k_send_asleep_event_to_app(arPriv, TRUE);
|
|
}
|
|
|
|
ar->arWowState = WLAN_WOW_STATE_SUSPENDING;
|
|
|
|
/* setup ARP request for our own IP */
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15)
|
|
if ((in_dev = __in_dev_get_rtnl(ndev)) != NULL) {
|
|
for (ifap = &in_dev->ifa_list; (ifa = *ifap) != NULL; ifap = &ifa->ifa_next) {
|
|
if (!strcmp(ndev->name, ifa->ifa_label)) {
|
|
break; /* found */
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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(arPriv->arWmi, &ipCmd);
|
|
if (status != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to setup IP for ARP agent\n"));
|
|
}
|
|
}
|
|
|
|
#ifndef ATH6K_CONFIG_OTA_MODE
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_INFO,("AR6K: %s: psm.info is %d (0: MAX_PERF_POWER, 1:REC_POWER)\n", __FUNCTION__, psm_info));
|
|
wmi_powermode_cmd_w_psminfo(arPriv->arWmi, psm_info, REC_POWER);
|
|
#endif
|
|
|
|
status = wmi_set_wow_mode_cmd(arPriv->arWmi, &wowMode);
|
|
if (status != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to enable wow mode\n"));
|
|
}
|
|
/*
|
|
* Set the callback function for processing of the last WMI command before suspending,
|
|
* so that we can wait for the HTC credit report to come in before proceeding.
|
|
*/
|
|
arPriv->sleep_mode_cmd_completed = FALSE;
|
|
wmi_set_host_sleep_mode_event_fn_ptr(arPriv->arWmi, ar6000_set_host_sleep_mode_callback, arPriv);
|
|
status = wmi_set_host_sleep_mode_cmd(arPriv->arWmi, &hostSleepMode);
|
|
if (status != A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to set host asleep\n"));
|
|
}
|
|
|
|
/*
|
|
* Wait for the WMI_SET_HOST_SLEEP_MODE_CMD_PROCESSED_EVENTID to come in. This is
|
|
* guaranteed to happen either AFTER the credit report for the
|
|
* WMI_SET_HOST_SLEEP_MODE_CMD arrives, or simultaneously along with the event.
|
|
* This should never take longer than a few ms, so if 2 seconds have elapsed and
|
|
* we still do not get the event, there's a problem with the WLAN chip. Just continue,
|
|
* (the user will have to unload the driver manually). It's better than taking down
|
|
* the entire OS.
|
|
*/
|
|
timeremaining = wait_event_interruptible_timeout(arPriv->sleep_mode_cmd_completed_event, (arPriv->sleep_mode_cmd_completed == TRUE), 2*HZ);
|
|
if (!timeremaining) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Expected a WMI_SET_HOST_SLEEP_MODE_CMD_PROCESSED event but it never arrived\n"));
|
|
}
|
|
|
|
if (ar->arTxPending[ar->arControlEp]) {
|
|
A_UINT32 timeleft = wait_event_interruptible_timeout(arPriv->arEvent,
|
|
ar->arTxPending[ar->arControlEp] == 0, wmitimeout * HZ);
|
|
if (!timeleft || signal_pending(current)) {
|
|
/* what can I do? wow resume at once */
|
|
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to setup WoW. Pending wmi control data %d\n", ar->arTxPending[ar->arControlEp]));
|
|
}
|
|
}
|
|
}
|
|
|
|
status = hifWaitForPendingRecv(ar->arHifDevice);
|
|
|
|
config = HIF_DEVICE_POWER_DOWN;
|
|
status = HIFConfigureDevice(ar->arHifDevice,
|
|
HIF_DEVICE_POWER_STATE_CHANGE,
|
|
&config,
|
|
sizeof(HIF_DEVICE_POWER_CHANGE_TYPE));
|
|
|
|
ar->arWowState = WLAN_WOW_STATE_SUSPENDED;
|
|
ar->arWlanPowerState = WLAN_POWER_STATE_WOW;
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("Setup WoW successfully\n"));
|
|
}
|
|
|
|
A_STATUS ar6000_suspend_ev(void *context)
|
|
{
|
|
A_STATUS status = A_OK;
|
|
AR_SOFTC_T *ar = (AR_SOFTC_T *)context;
|
|
A_INT16 pmmode = ar->arSuspendConfig;
|
|
A_INT32 i;
|
|
A_BOOL needWow = FALSE;
|
|
wow_not_connected:
|
|
switch (pmmode) {
|
|
case WLAN_SUSPEND_WOW:
|
|
for(i = 0; i< num_device; i++) {
|
|
if (ar->arDev[i]->arConnected) {
|
|
needWow = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (ar->arWmiReady && ar->arWlanState==WLAN_ENABLED && needWow) {
|
|
ar6000_wow_suspend(ar);
|
|
status = A_EBUSY;
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM,("%s:Suspend for wow mode %d\n", __func__, ar->arWlanPowerState));
|
|
} else {
|
|
pmmode = ar->arWow2Config;
|
|
goto wow_not_connected;
|
|
}
|
|
break;
|
|
case WLAN_SUSPEND_CUT_PWR:
|
|
/* fall through */
|
|
case WLAN_SUSPEND_CUT_PWR_IF_BT_OFF:
|
|
/* fall through */
|
|
case WLAN_SUSPEND_DEEP_SLEEP:
|
|
/* fall through */
|
|
default:
|
|
status = ar6000_update_wlan_pwr_state(ar, WLAN_DISABLED, TRUE);
|
|
if (ar->arWlanPowerState==WLAN_POWER_STATE_ON ||
|
|
ar->arWlanPowerState==WLAN_POWER_STATE_WOW) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("Strange suspend state for not wow mode %d", ar->arWlanPowerState));
|
|
}
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM,("%s:Suspend for %d mode pwr %d status %d\n", __func__, pmmode, ar->arWlanPowerState, status));
|
|
if (ar->arWlanPowerState==WLAN_POWER_STATE_CUT_PWR) {
|
|
status = A_OK;
|
|
} else {
|
|
status = A_EBUSY; /* don't let mmc call sdio_init after resume */
|
|
}
|
|
break;
|
|
}
|
|
|
|
for(i = 0; i < num_device; i++) {
|
|
AR_SOFTC_DEV_T *arPriv = ar->arDev[i];
|
|
AR_SOFTC_STA_T *arSta = &arPriv->arSta;
|
|
if (arSta->scan_triggered) {
|
|
ar6000_scanComplete_event(arPriv, A_OK);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
A_STATUS ar6000_resume_ev(void *context)
|
|
{
|
|
AR_SOFTC_T *ar = (AR_SOFTC_T *)context;
|
|
A_UINT16 powerState = ar->arWlanPowerState;
|
|
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
wake_lock(&ar6k_suspend_wake_lock);
|
|
#endif
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("%s: enter previous state %d wowState %d\n", __func__, powerState, ar->arWowState));
|
|
switch (powerState) {
|
|
case WLAN_POWER_STATE_WOW:
|
|
ar6000_wow_resume(ar);
|
|
break;
|
|
case WLAN_POWER_STATE_CUT_PWR:
|
|
/* fall through */
|
|
case WLAN_POWER_STATE_DEEP_SLEEP:
|
|
ar6000_update_wlan_pwr_state(ar, WLAN_ENABLED, TRUE);
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM,("%s:Resume for %d mode pwr %d\n", __func__, powerState, ar->arWlanPowerState));
|
|
break;
|
|
case WLAN_POWER_STATE_ON:
|
|
break;
|
|
default:
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Strange SDIO bus power mode!!\n"));
|
|
break;
|
|
}
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
wake_unlock(&ar6k_suspend_wake_lock);
|
|
#endif
|
|
return A_OK;
|
|
}
|
|
|
|
void ar6000_check_wow_status(AR_SOFTC_T *ar, struct sk_buff *skb, A_BOOL isEvent)
|
|
{
|
|
/* EV85521 */
|
|
if (in_interrupt()) {
|
|
if (ar->arWowState == WLAN_WOW_STATE_SUSPENDED) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_WARN, ("AR6K: %s called in interrupt context\n", __func__));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (ar->arWowState != WLAN_WOW_STATE_NONE) {
|
|
if (ar->arWowState==WLAN_WOW_STATE_SUSPENDING) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM,("\n%s: Received IRQ while we are wow suspending!!!\n\n", __func__));
|
|
return;
|
|
}
|
|
/* Wow resume from irq interrupt */
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("%s: WoW resume from irq thread status %d\n", __func__, ar->arWlanPowerState));
|
|
ar6000_wow_resume(ar);
|
|
} else {
|
|
}
|
|
}
|
|
|
|
A_STATUS ar6000_power_change_ev(void *context, A_UINT32 config)
|
|
{
|
|
AR_SOFTC_T *ar = (AR_SOFTC_T *)context;
|
|
A_STATUS status = A_OK;
|
|
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("%s: power change event callback %d \n", __func__, config));
|
|
switch (config) {
|
|
case HIF_DEVICE_POWER_UP:
|
|
ar6000_restart_endpoint(ar);
|
|
status = A_OK;
|
|
break;
|
|
case HIF_DEVICE_POWER_DOWN:
|
|
case HIF_DEVICE_POWER_CUT:
|
|
status = A_OK;
|
|
break;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
#if PLAT_WOW_GPIO_PIN
|
|
static irqreturn_t
|
|
ar6000_wow_irq(int irq, void *dev_id)
|
|
{
|
|
gpio_clear_detect_status(wow_irq);
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
wake_lock_timeout(&ar6k_wow_wake_lock, 3*HZ);
|
|
#else
|
|
/* TODO: What should I do if there is no wake lock?? */
|
|
#endif
|
|
return IRQ_HANDLED;
|
|
}
|
|
#endif /* PLAT_WOW_GPIO_PIN */
|
|
|
|
#if PLAT_WLAN_CHIP_PWD_PIN
|
|
void plat_setup_power_stub(AR_SOFTC_T *ar, int on, int detect)
|
|
{
|
|
A_BOOL chip_pwd_low_val;
|
|
|
|
if (on) {
|
|
chip_pwd_low_val = 1;
|
|
} else {
|
|
chip_pwd_low_val = 0;
|
|
}
|
|
|
|
if (gpio_request(PLAT_WLAN_CHIP_PWD_PIN, "wlan_chip_pwd_l")!=0) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Cannot request CHIP_PWD GPIO"));
|
|
} else {
|
|
gpio_direction_output(PLAT_WLAN_CHIP_PWD_PIN, 0);/* WLAN_CHIP_PWD */
|
|
gpio_set_value(PLAT_WLAN_CHIP_PWD_PIN, chip_pwd_low_val);
|
|
A_MDELAY(100);
|
|
gpio_free(PLAT_WLAN_CHIP_PWD_PIN);
|
|
}
|
|
}
|
|
#endif /* PLAT_WLAN_CHIP_PWD_PIN */
|
|
|
|
#if defined(CONFIG_MMC_MSM) && defined(CONFIG_ARCH_MSM9615)
|
|
|
|
struct wlan_regulator {
|
|
const char *vreg_name; /* Regulator Name */
|
|
int min_uV; /* Minimum voltage at which AR6003 can operate */
|
|
int max_uV; /* Maximum voltage at which AR6003 can operate */
|
|
int load_uA; /* Current which will be drawn from regulator (Worst case) */
|
|
int delay_mT; /* Time from this operation to next */
|
|
struct regulator *vreg; /* Regulator Handle */
|
|
};
|
|
|
|
|
|
static struct wlan_regulator regulator_table[] = {
|
|
{"wlan_vreg", 1710000, 1890000, 86000, 5, NULL}
|
|
};
|
|
|
|
#ifdef ATH_AR6K_EXT_PMIC_CLK
|
|
static struct msm_xo_voter *xo_handle = NULL;
|
|
#endif
|
|
|
|
static void msm9615_wifi_power_down(struct wlan_regulator *regulators, A_UINT32 size)
|
|
{
|
|
int rc = 0;
|
|
int i;
|
|
|
|
if (!regulators) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("msm9615_wifi_power_down: NULL pointer passed!!!\n"));
|
|
return;
|
|
}
|
|
|
|
for (i = size - 1; i >= 0; i--) {
|
|
|
|
if (!regulators[i].vreg) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("msm9615_wifi_power_down: vreg is NULL!!!\n"));
|
|
continue;
|
|
}
|
|
|
|
rc = regulator_disable(regulators[i].vreg);
|
|
|
|
if (rc) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Failed to disable regulator: %s, rc: %d\n",
|
|
regulators[i].vreg_name, rc));
|
|
}
|
|
|
|
rc = regulator_set_voltage(regulators[i].vreg, 0, regulators[i].max_uV);
|
|
|
|
if (rc) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Failed to set regulator voltage: %s, rc: %d\n",
|
|
regulators[i].vreg_name, rc));
|
|
}
|
|
|
|
rc = regulator_set_optimum_mode(regulators[i].vreg, 0);
|
|
|
|
if (rc < 0) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Failed to set regulator optimum mode: %s, rc: %d\n",
|
|
regulators[i].vreg_name, rc));
|
|
}
|
|
|
|
regulator_put(regulators[i].vreg);
|
|
regulators[i].vreg = NULL;
|
|
}
|
|
}
|
|
|
|
static int msm9615_wifi_power_up(struct wlan_regulator *regulators, A_UINT32 size)
|
|
{
|
|
int rc = 0;
|
|
int i = 0;
|
|
|
|
if (!g_pdev) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("msm9615_wifi_power_up: Platform device is NULL!!!\n"));
|
|
rc = -ENODEV;
|
|
goto fail;
|
|
}
|
|
|
|
if (!regulators) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("msm9615_wifi_power_up: NULL pointer passed!!!"));
|
|
rc = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
for (i = 0; i < size; i++) {
|
|
|
|
regulators[i].vreg = regulator_get(&g_pdev->dev, regulators[i].vreg_name);
|
|
|
|
if (!regulators[i].vreg || IS_ERR(regulators[i].vreg)) {
|
|
rc = PTR_ERR(regulators[i].vreg);
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Failed to get regulator: %s, rc: %d\n",
|
|
regulators[i].vreg_name, rc));
|
|
regulators[i].vreg = NULL;
|
|
goto fail;
|
|
}
|
|
|
|
rc = regulator_set_voltage(regulators[i].vreg, regulators[i].min_uV, regulators[i].max_uV);
|
|
|
|
if (rc) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Failed to set regulator voltage: %s, rc: %d\n",
|
|
regulators[i].vreg_name, rc));
|
|
goto fail;
|
|
}
|
|
|
|
rc = regulator_set_optimum_mode(regulators[i].vreg, regulators[i].load_uA);
|
|
|
|
if (rc < 0) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Failed to set regulator optimum mode: %s, rc: %d\n",
|
|
regulators[i].vreg_name, rc));
|
|
goto fail;
|
|
}
|
|
|
|
rc = regulator_enable(regulators[i].vreg);
|
|
if (rc) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Failed to enable regulator: %s, rc: %d\n",
|
|
regulators[i].vreg_name, rc));
|
|
goto fail;
|
|
}
|
|
|
|
A_MDELAY(regulators[i].delay_mT);
|
|
}
|
|
|
|
return rc;
|
|
|
|
fail:
|
|
msm9615_wifi_power_down(regulators, i + 1);
|
|
return rc;
|
|
}
|
|
|
|
static struct gpio wifi_gpios[] = {
|
|
{ MSM9615_WLAN_CHIP_PWD_PIN, GPIOF_OUT_INIT_LOW, "wlan_chip_pwd_l" },
|
|
{ MSM9615_WLAN_PM_ENABLE_PIN, GPIOF_OUT_INIT_LOW, "wlan_pm_enable" },
|
|
#ifdef ATH_AR6K_EXT_PMIC_CLK
|
|
{ MSM9615_WLAN_CLK_PWR_REQ, GPIOF_OUT_INIT_LOW, "wlan_clk_pwr_req" },
|
|
#endif /* ATH_AR6K_EXT_PMIC_CLK */
|
|
};
|
|
|
|
int msm9615_wifi_power(AR_SOFTC_T *ar, int on)
|
|
{
|
|
int rc = 0;
|
|
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("msm9615_wifi_power: %s, WLAN_RESET_N: %d\n",
|
|
on ? "on" : "off", wlan_reset_gpio));
|
|
|
|
if (on) {
|
|
rc = msm9615_wifi_power_up(regulator_table, ARRAY_SIZE(regulator_table));
|
|
|
|
if (rc) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("msm9615_wifi_power: Power UP failed!!!\n"));
|
|
goto power_up_fail;
|
|
}
|
|
|
|
/* Update the WLAN_RESET_N GPIO number passed as part of module
|
|
* parameter
|
|
*/
|
|
wifi_gpios[0].gpio = wlan_reset_gpio;
|
|
|
|
rc = gpio_request_array(wifi_gpios, ARRAY_SIZE(wifi_gpios));
|
|
|
|
if (rc) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Cannot request GPIO!!!"));
|
|
goto gpio_request_fail;
|
|
}
|
|
|
|
#ifdef ATH_AR6K_EXT_PMIC_CLK
|
|
xo_handle = msm_xo_get(MSM_XO_TCXO_A1, "wlan");
|
|
|
|
if (IS_ERR(xo_handle)) {
|
|
rc = PTR_ERR(xo_handle);
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Failed to get handle to vote for TCXO A1 buffer: %d\n", rc));
|
|
|
|
goto xo_get_fail;
|
|
}
|
|
#endif /* ATH_AR6K_EXT_PMIC_CLK */
|
|
|
|
gpio_set_value(MSM9615_WLAN_PM_ENABLE_PIN, 1);
|
|
A_MDELAY(100);
|
|
gpio_set_value(wlan_reset_gpio, 1);
|
|
|
|
#ifdef ATH_AR6K_EXT_PMIC_CLK
|
|
gpio_set_value(MSM9615_WLAN_CLK_PWR_REQ, 1);
|
|
|
|
rc = msm_xo_mode_vote(xo_handle, MSM_XO_MODE_ON);
|
|
|
|
if (rc) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Failed to vote for TCXO A1 buffer: %d\n", rc));
|
|
goto xo_mode_vote_fail;
|
|
}
|
|
#endif /* ATH_AR6K_EXT_PMIC_CLK */
|
|
|
|
} else {
|
|
|
|
#ifdef ATH_AR6K_EXT_PMIC_CLK
|
|
/* msm_xo_put also removes the vote */
|
|
msm_xo_put(xo_handle);
|
|
xo_handle = NULL;
|
|
gpio_set_value(MSM9615_WLAN_CLK_PWR_REQ, 0);
|
|
#endif
|
|
gpio_set_value(wlan_reset_gpio, 0);
|
|
gpio_set_value(MSM9615_WLAN_PM_ENABLE_PIN, 0);
|
|
|
|
gpio_free_array(wifi_gpios, ARRAY_SIZE(wifi_gpios));
|
|
|
|
msm9615_wifi_power_down(regulator_table, ARRAY_SIZE(regulator_table));
|
|
}
|
|
|
|
return rc;
|
|
|
|
#ifdef ATH_AR6K_EXT_PMIC_CLK
|
|
xo_mode_vote_fail:
|
|
msm_xo_put(xo_handle);
|
|
xo_handle = NULL;
|
|
|
|
gpio_set_value(MSM9615_WLAN_CLK_PWR_REQ, 0);
|
|
gpio_set_value(wlan_reset_gpio, 0);
|
|
gpio_set_value(MSM9615_WLAN_PM_ENABLE_PIN, 0);
|
|
|
|
xo_get_fail:
|
|
gpio_free_array(wifi_gpios, ARRAY_SIZE(wifi_gpios));
|
|
#endif /* ATH_AR6K_EXT_PMIC_CLK */
|
|
|
|
gpio_request_fail:
|
|
msm9615_wifi_power_down(regulator_table, ARRAY_SIZE(regulator_table));
|
|
|
|
power_up_fail:
|
|
return rc;
|
|
}
|
|
#endif /* defined(CONFIG_MMC_MSM) && defined(CONFIG_ARCH_MSM9615) */
|
|
|
|
#ifdef CONFIG_OF
|
|
int ar6000_dt_setup_power(AR_SOFTC_T *ar, int on)
|
|
{
|
|
return ar6000_dt_power(platform_get_drvdata(g_pdev), on);
|
|
}
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15)
|
|
static int ar6000_pm_probe(struct platform_device *pdev)
|
|
{
|
|
|
|
g_pdev = pdev;
|
|
|
|
#ifdef CONFIG_OF
|
|
return ar6000_dt_probe(pdev);
|
|
|
|
#else
|
|
plat_setup_power(NULL, 1, 1);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static int ar6000_pm_remove(struct platform_device *pdev)
|
|
{
|
|
#ifdef CONFIG_OF
|
|
return ar6000_dt_probe(pdev);
|
|
|
|
#else
|
|
plat_setup_power(NULL, 0, 1);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static int ar6000_pm_suspend(struct platform_device *pdev, pm_message_t state)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int ar6000_pm_resume(struct platform_device *pdev)
|
|
{
|
|
int i;
|
|
extern struct net_device *ar6000_devices[MAX_AR6000];
|
|
|
|
for (i=0; ar6000_devices[i]; ++i) {
|
|
AR_SOFTC_DEV_T *arPriv = (AR_SOFTC_DEV_T *)ar6k_priv(ar6000_devices[i]);
|
|
AR_SOFTC_T *ar = (AR_SOFTC_T *)arPriv->arSoftc;
|
|
|
|
if (ar && ar->arPlatPowerOff) {
|
|
A_BOOL wlanOff = ar->arWlanOff;
|
|
A_UINT16 powerState = ar->arWlanPowerState;
|
|
A_BOOL btOff = ar->arBTOff;
|
|
|
|
if (!wlanOff) {
|
|
if (powerState == WLAN_POWER_STATE_CUT_PWR) {
|
|
plat_setup_power(ar, 1, 0);
|
|
ar->arPlatPowerOff = FALSE;
|
|
}
|
|
}
|
|
#ifdef CONFIG_PM
|
|
else if (wlanOff) {
|
|
A_BOOL allowCutPwr = ((!ar->arBTSharing) || btOff);
|
|
if ((powerState==WLAN_POWER_STATE_CUT_PWR) && (!allowCutPwr)) {
|
|
plat_setup_power(ar, 1, 0);
|
|
ar->arPlatPowerOff = FALSE;
|
|
}
|
|
}
|
|
#endif /* CONFIG_PM */
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id ar6000_dt_match[] = {
|
|
{.compatible = "qca,ar6003-sdio"},
|
|
{},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, ar6000_dt_match);
|
|
|
|
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",
|
|
.of_match_table = ar6000_dt_match,
|
|
},
|
|
};
|
|
#endif
|
|
#endif /* CONFIG_PM */
|
|
|
|
A_STATUS
|
|
ar6000_setup_cut_power_state(struct ar6_softc *ar, AR6000_WLAN_STATE state)
|
|
{
|
|
A_STATUS status = A_OK;
|
|
HIF_DEVICE_POWER_CHANGE_TYPE config;
|
|
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("%s: Cut power %d %d \n", __func__,state, ar->arWlanPowerState));
|
|
#ifdef CONFIG_PM
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("Wlan OFF %d BT OFf %d \n", ar->arWlanOff, ar->arBTOff));
|
|
#endif
|
|
do {
|
|
if (state == WLAN_ENABLED) {
|
|
/* Not in cut power state.. exit */
|
|
if (ar->arWlanPowerState != WLAN_POWER_STATE_CUT_PWR) {
|
|
break;
|
|
}
|
|
|
|
if (ar->arPlatPowerOff) {
|
|
plat_setup_power(ar, 1, 0);
|
|
ar->arPlatPowerOff = FALSE;
|
|
}
|
|
|
|
/* Change the state to ON */
|
|
ar->arWlanPowerState = WLAN_POWER_STATE_ON;
|
|
|
|
|
|
/* Indicate POWER_UP to HIF */
|
|
config = HIF_DEVICE_POWER_UP;
|
|
status = HIFConfigureDevice(ar->arHifDevice,
|
|
HIF_DEVICE_POWER_STATE_CHANGE,
|
|
&config,
|
|
sizeof(HIF_DEVICE_POWER_CHANGE_TYPE));
|
|
|
|
if (status == A_PENDING) {
|
|
status = A_OK;
|
|
} else if (status == A_OK) {
|
|
ar6000_restart_endpoint(ar);
|
|
status = A_OK;
|
|
}
|
|
} else if (state == WLAN_DISABLED) {
|
|
|
|
|
|
/* Already in cut power state.. exit */
|
|
if (ar->arWlanPowerState == WLAN_POWER_STATE_CUT_PWR) {
|
|
break;
|
|
}
|
|
ar6000_stop_endpoint(ar, TRUE, FALSE);
|
|
|
|
config = HIF_DEVICE_POWER_CUT;
|
|
status = HIFConfigureDevice(ar->arHifDevice,
|
|
HIF_DEVICE_POWER_STATE_CHANGE,
|
|
&config,
|
|
sizeof(HIF_DEVICE_POWER_CHANGE_TYPE));
|
|
|
|
plat_setup_power(ar, 0, 0);
|
|
ar->arPlatPowerOff = TRUE;
|
|
|
|
ar->arWlanPowerState = WLAN_POWER_STATE_CUT_PWR;
|
|
}
|
|
} while (0);
|
|
|
|
return status;
|
|
}
|
|
|
|
A_STATUS
|
|
ar6000_setup_deep_sleep_state(struct ar6_softc *ar, AR6000_WLAN_STATE state)
|
|
{
|
|
A_STATUS status = A_OK;
|
|
|
|
HIF_DEVICE_POWER_CHANGE_TYPE config;
|
|
AR_SOFTC_DEV_T *arPriv;
|
|
AR_SOFTC_STA_T *arSta;
|
|
A_UINT8 i;
|
|
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("%s: Deep sleep %d %d \n", __func__,state, ar->arWlanPowerState));
|
|
#ifdef CONFIG_PM
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("Wlan OFF %d BT OFf %d \n", ar->arWlanOff, ar->arBTOff));
|
|
#endif
|
|
do {
|
|
WMI_SET_HOST_SLEEP_MODE_CMD hostSleepMode;
|
|
|
|
if (state == WLAN_ENABLED) {
|
|
A_UINT16 fg_start_period;
|
|
|
|
/* Not in deep sleep state.. exit */
|
|
if (ar->arWlanPowerState != WLAN_POWER_STATE_DEEP_SLEEP) {
|
|
if (ar->arWlanPowerState != WLAN_POWER_STATE_ON) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Strange state when we resume from deep sleep %d\n", ar->arWlanPowerState));
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Indicate POWER_UP to HIF */
|
|
config = HIF_DEVICE_POWER_UP;
|
|
status = HIFConfigureDevice(ar->arHifDevice,
|
|
HIF_DEVICE_POWER_STATE_CHANGE,
|
|
&config,
|
|
sizeof(HIF_DEVICE_POWER_CHANGE_TYPE));
|
|
|
|
for(i = 0; i < num_device; i++)
|
|
{
|
|
arPriv = ar->arDev[i];
|
|
arSta = &arPriv->arSta;
|
|
|
|
/*
|
|
* Free all wlan_nodes (scanned APs that we detected previously)
|
|
* because during the time we were suspended, the device may have
|
|
* been moved to a new location and these APs scanned for before
|
|
* suspend may not be valid
|
|
*/
|
|
wmi_free_allnodes(arPriv->arWmi);
|
|
|
|
fg_start_period = (arSta->scParams.fg_start_period==0) ? 1 : arSta->scParams.fg_start_period;
|
|
hostSleepMode.awake = TRUE;
|
|
hostSleepMode.asleep = FALSE;
|
|
|
|
/*
|
|
* Clear the callback function. We do not need to wait while resuming.
|
|
*/
|
|
wmi_set_host_sleep_mode_event_fn_ptr(arPriv->arWmi, NULL, NULL);
|
|
if ((status=wmi_set_host_sleep_mode_cmd(arPriv->arWmi, &hostSleepMode)) != A_OK) {
|
|
break;
|
|
}
|
|
|
|
/* Change the state to ON */
|
|
ar->arWlanPowerState = WLAN_POWER_STATE_ON;
|
|
|
|
/* Enable foreground scanning */
|
|
if ((status=wmi_scanparams_cmd(arPriv->arWmi, fg_start_period,
|
|
arSta->scParams.fg_end_period,
|
|
arSta->scParams.bg_period,
|
|
arSta->scParams.minact_chdwell_time,
|
|
arSta->scParams.maxact_chdwell_time,
|
|
arSta->scParams.pas_chdwell_time,
|
|
arSta->scParams.shortScanRatio,
|
|
arSta->scParams.scanCtrlFlags,
|
|
arSta->scParams.max_dfsch_act_time,
|
|
arSta->scParams.maxact_scan_per_ssid)) != A_OK)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (arPriv->arSsidLen) {
|
|
if (ar6000_connect_to_ap(arPriv) != A_OK) {
|
|
/* no need to report error if connection failed */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (state == WLAN_DISABLED){
|
|
#ifdef CONFIG_PM
|
|
A_UINT32 timeremaining;
|
|
#endif
|
|
WMI_SET_WOW_MODE_CMD wowMode = { .enable_wow = FALSE };
|
|
|
|
/* Already in deep sleep state.. exit */
|
|
if (ar->arWlanPowerState != WLAN_POWER_STATE_ON) {
|
|
if (ar->arWlanPowerState != WLAN_POWER_STATE_DEEP_SLEEP) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Strange state when we suspend for deep sleep %d\n", ar->arWlanPowerState));
|
|
}
|
|
break;
|
|
}
|
|
|
|
for(i = 0; i < num_device; i++)
|
|
{
|
|
arPriv = ar->arDev[i];
|
|
arSta = &arPriv->arSta;
|
|
/* make sure we disable wow for deep sleep */
|
|
if ((status=wmi_set_wow_mode_cmd(arPriv->arWmi, &wowMode))!=A_OK) {
|
|
break;
|
|
}
|
|
|
|
/* Disconnect from the AP and disable foreground scanning */
|
|
AR6000_SPIN_LOCK(&arPriv->arPrivLock, 0);
|
|
if (arPriv->arConnected == TRUE || arSta->arConnectPending == TRUE) {
|
|
AR6000_SPIN_UNLOCK(&arPriv->arPrivLock, 0);
|
|
ar6000_disconnect(arPriv);
|
|
} else {
|
|
AR6000_SPIN_UNLOCK(&arPriv->arPrivLock, 0);
|
|
}
|
|
|
|
if ((status=wmi_scanparams_cmd(arPriv->arWmi, 0xFFFF, 0, 0, 0, 0, 0, 0, 0, 0, 0)) != A_OK) {
|
|
break;
|
|
}
|
|
ar6000_TxDataCleanup(ar);
|
|
#ifndef ATH6K_CONFIG_OTA_MODE
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_INFO,("AR6K: %s: psm.info is %d (0: MAX_PERF_POWER, 1:REC_POWER)\n", __FUNCTION__, psm_info));
|
|
wmi_powermode_cmd_w_psminfo(arPriv->arWmi, psm_info, REC_POWER);
|
|
#endif
|
|
|
|
hostSleepMode.awake = FALSE;
|
|
hostSleepMode.asleep = TRUE;
|
|
|
|
#ifdef CONFIG_PM
|
|
/*
|
|
* Set the callback function for processing of the last WMI command before suspending,
|
|
* so that we can wait for the HTC credit report to come in before proceeding.
|
|
*/
|
|
arPriv->sleep_mode_cmd_completed = FALSE;
|
|
wmi_set_host_sleep_mode_event_fn_ptr(arPriv->arWmi, ar6000_set_host_sleep_mode_callback, arPriv);
|
|
if ((status=wmi_set_host_sleep_mode_cmd(arPriv->arWmi, &hostSleepMode))!=A_OK) {
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Wait for the WMI_SET_HOST_SLEEP_MODE_CMD_PROCESSED_EVENTID to come in. This is
|
|
* guaranteed to happen either AFTER the credit report for the
|
|
* WMI_SET_HOST_SLEEP_MODE_CMD arrives, or simultaneously along with the event.
|
|
* This should never take longer than a few ms, so if 2 seconds have elapsed and
|
|
* we still do not get the event, there's a problem with the WLAN chip. Just continue,
|
|
* (the user will have to unload the driver manually). It's better than taking down
|
|
* the entire OS.
|
|
*/
|
|
timeremaining = wait_event_interruptible_timeout(arPriv->sleep_mode_cmd_completed_event, (arPriv->sleep_mode_cmd_completed == TRUE), 2*HZ);
|
|
if (!timeremaining) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Expected a WMI_SET_HOST_SLEEP_MODE_CMD_PROCESSED event but it never arrived\n"));
|
|
}
|
|
|
|
if (ar->arTxPending[ar->arControlEp]) {
|
|
A_UINT32 timeleft = wait_event_interruptible_timeout(arPriv->arEvent,
|
|
ar->arTxPending[ar->arControlEp] == 0, wmitimeout * HZ);
|
|
if (!timeleft || signal_pending(current)) {
|
|
status = A_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
status = hifWaitForPendingRecv(ar->arHifDevice);
|
|
|
|
config = HIF_DEVICE_POWER_DOWN;
|
|
status = HIFConfigureDevice(ar->arHifDevice,
|
|
HIF_DEVICE_POWER_STATE_CHANGE,
|
|
&config,
|
|
sizeof(HIF_DEVICE_POWER_CHANGE_TYPE));
|
|
|
|
ar->arWlanPowerState = WLAN_POWER_STATE_DEEP_SLEEP;
|
|
}
|
|
} while (0);
|
|
|
|
if (status!=A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to enter/exit deep sleep %d\n", state));
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
A_STATUS
|
|
ar6000_update_wlan_pwr_state(struct ar6_softc *ar, AR6000_WLAN_STATE state, A_BOOL pmEvent)
|
|
{
|
|
A_STATUS status = A_OK;
|
|
A_UINT16 powerState, oldPowerState;
|
|
AR6000_WLAN_STATE oldstate = ar->arWlanState;
|
|
A_BOOL wlanOff = ar->arWlanOff;
|
|
#ifdef CONFIG_PM
|
|
A_BOOL btOff = ar->arBTOff;
|
|
#endif /* CONFIG_PM */
|
|
A_UINT8 i;
|
|
AR_SOFTC_DEV_T *arPriv;
|
|
|
|
if ((state!=WLAN_DISABLED && state!=WLAN_ENABLED)) {
|
|
return A_ERROR;
|
|
}
|
|
|
|
if (ar->bIsDestroyProgress) {
|
|
return A_EBUSY;
|
|
}
|
|
|
|
if (down_interruptible(&ar->arSem)) {
|
|
return A_ERROR;
|
|
}
|
|
|
|
if (ar->bIsDestroyProgress) {
|
|
up(&ar->arSem);
|
|
return A_EBUSY;
|
|
}
|
|
|
|
ar->arWlanState = wlanOff ? WLAN_DISABLED : state;
|
|
oldPowerState = ar->arWlanPowerState;
|
|
if (state == WLAN_ENABLED) {
|
|
powerState = ar->arWlanPowerState;
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("WLAN PWR set to ENABLE^^\n"));
|
|
if (!wlanOff) {
|
|
if (powerState == WLAN_POWER_STATE_DEEP_SLEEP) {
|
|
status = ar6000_setup_deep_sleep_state(ar, WLAN_ENABLED);
|
|
} else if (powerState == WLAN_POWER_STATE_CUT_PWR) {
|
|
status = ar6000_setup_cut_power_state(ar, WLAN_ENABLED);
|
|
}
|
|
}
|
|
#ifdef CONFIG_PM
|
|
else if (pmEvent && wlanOff) {
|
|
A_BOOL allowCutPwr = ((!ar->arBTSharing) || btOff);
|
|
if ((powerState==WLAN_POWER_STATE_CUT_PWR) && (!allowCutPwr)) {
|
|
/* Come out of cut power */
|
|
ar6000_setup_cut_power_state(ar, WLAN_ENABLED);
|
|
status = ar6000_setup_deep_sleep_state(ar, WLAN_DISABLED);
|
|
}
|
|
}
|
|
#endif /* CONFIG_PM */
|
|
} else if (state == WLAN_DISABLED) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("WLAN PWR set to DISABLED~\n"));
|
|
powerState = WLAN_POWER_STATE_DEEP_SLEEP;
|
|
#ifdef CONFIG_PM
|
|
if (pmEvent) { /* disable due to suspend */
|
|
A_BOOL suspendCutPwr = (ar->arSuspendConfig == WLAN_SUSPEND_CUT_PWR ||
|
|
(ar->arSuspendConfig == WLAN_SUSPEND_WOW &&
|
|
ar->arWow2Config==WLAN_SUSPEND_CUT_PWR));
|
|
A_BOOL suspendCutIfBtOff = ((ar->arSuspendConfig ==
|
|
WLAN_SUSPEND_CUT_PWR_IF_BT_OFF ||
|
|
(ar->arSuspendConfig == WLAN_SUSPEND_WOW &&
|
|
ar->arWow2Config==WLAN_SUSPEND_CUT_PWR_IF_BT_OFF)) &&
|
|
(!ar->arBTSharing || btOff));
|
|
if ((suspendCutPwr) ||
|
|
(suspendCutIfBtOff) ||
|
|
(ar->arWlanPowerState==WLAN_POWER_STATE_CUT_PWR))
|
|
{
|
|
if (!wlanOff || ar->arWlanPowerState!=WLAN_POWER_STATE_DEEP_SLEEP) {
|
|
powerState = WLAN_POWER_STATE_CUT_PWR;
|
|
}
|
|
}
|
|
} else {
|
|
if ((wlanOff) &&
|
|
(ar->arWlanOffConfig == WLAN_OFF_CUT_PWR) &&
|
|
(!ar->arBTSharing || btOff))
|
|
{
|
|
/* For BT clock sharing designs, CUT_POWER depend on BT state */
|
|
powerState = WLAN_POWER_STATE_CUT_PWR;
|
|
}
|
|
}
|
|
#endif /* CONFIG_PM */
|
|
|
|
if (powerState == WLAN_POWER_STATE_DEEP_SLEEP) {
|
|
if (ar->arWlanPowerState == WLAN_POWER_STATE_CUT_PWR) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("Load firmware before set to deep sleep\n"));
|
|
ar6000_setup_cut_power_state(ar, WLAN_ENABLED);
|
|
}
|
|
status = ar6000_setup_deep_sleep_state(ar, WLAN_DISABLED);
|
|
} else if (powerState == WLAN_POWER_STATE_CUT_PWR) {
|
|
status = ar6000_setup_cut_power_state(ar, WLAN_DISABLED);
|
|
}
|
|
|
|
}
|
|
|
|
if (status!=A_OK) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Fail to setup WLAN state %d\n", ar->arWlanState));
|
|
ar->arWlanState = oldstate;
|
|
} else if (status == A_OK) {
|
|
WMI_REPORT_SLEEP_STATE_EVENT wmiSleepEvent, *pSleepEvent = NULL;
|
|
if ((ar->arWlanPowerState == WLAN_POWER_STATE_ON) && (oldPowerState != WLAN_POWER_STATE_ON)) {
|
|
wmiSleepEvent.sleepState = WMI_REPORT_SLEEP_STATUS_IS_AWAKE;
|
|
pSleepEvent = &wmiSleepEvent;
|
|
} else if ((ar->arWlanPowerState != WLAN_POWER_STATE_ON) && (oldPowerState == WLAN_POWER_STATE_ON)) {
|
|
wmiSleepEvent.sleepState = WMI_REPORT_SLEEP_STATUS_IS_DEEP_SLEEP;
|
|
pSleepEvent = &wmiSleepEvent;
|
|
}
|
|
if (pSleepEvent) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("SENT WLAN Sleep Event %d\n", wmiSleepEvent.sleepState));
|
|
for(i = 0; i < num_device; i++)
|
|
{
|
|
arPriv = ar->arDev[i];
|
|
ar6000_send_event_to_app(arPriv, WMI_REPORT_SLEEP_STATE_EVENTID, (A_UINT8*)pSleepEvent,
|
|
sizeof(WMI_REPORT_SLEEP_STATE_EVENTID));
|
|
}
|
|
}
|
|
}
|
|
up(&ar->arSem);
|
|
return status;
|
|
}
|
|
|
|
A_STATUS
|
|
ar6000_set_bt_hw_state(struct ar6_softc *ar, A_UINT32 enable)
|
|
{
|
|
#ifdef CONFIG_PM
|
|
A_BOOL off = (enable == 0);
|
|
A_STATUS status;
|
|
if (ar->arBTOff == off) {
|
|
return A_OK;
|
|
}
|
|
ar->arBTOff = off;
|
|
status = ar6000_update_wlan_pwr_state(ar, ar->arWlanOff ? WLAN_DISABLED : WLAN_ENABLED, FALSE);
|
|
return status;
|
|
#else
|
|
return A_OK;
|
|
#endif
|
|
}
|
|
|
|
A_STATUS
|
|
ar6000_set_wlan_state(struct ar6_softc *ar, AR6000_WLAN_STATE state)
|
|
{
|
|
A_INT32 i;
|
|
A_STATUS status;
|
|
A_BOOL off = (state == WLAN_DISABLED);
|
|
if (ar->arWlanOff == off) {
|
|
return A_OK;
|
|
}
|
|
|
|
#if WLAN_CONFIG_FIRST_SCAN_2G_ONLY
|
|
if(!off) {
|
|
first_scan_2g_only = 1;
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_PM, ("AR6K: first_scan_2g_only = 1\n"));
|
|
}
|
|
#endif
|
|
|
|
ar->arWlanOff = off;
|
|
status = ar6000_update_wlan_pwr_state(ar, state, FALSE);
|
|
for(i = 0; i < num_device; i++) {
|
|
AR_SOFTC_DEV_T *arPriv = ar->arDev[i];
|
|
AR_SOFTC_STA_T *arSta = &arPriv->arSta;
|
|
if (arSta->scan_triggered) {
|
|
ar6000_scanComplete_event(arPriv, A_OK);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
void ar6000_pm_init()
|
|
{
|
|
A_REGISTER_MODULE_DEBUG_INFO(pm);
|
|
#ifdef CONFIG_PM
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
wake_lock_init(&ar6k_suspend_wake_lock, WAKE_LOCK_SUSPEND, "ar6k_suspend");
|
|
wake_lock_init(&ar6k_wow_wake_lock, WAKE_LOCK_SUSPEND, "ar6k_wow");
|
|
#endif
|
|
/*
|
|
* Register ar6000_pm_device into system.
|
|
* We should also add platform_device into the first item of array
|
|
* of devices[] in file arch/xxx/mach-xxx/board-xxxx.c
|
|
*/
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15)
|
|
if (platform_driver_register(&ar6000_pm_device)) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("ar6000: fail to register the power control driver.\n"));
|
|
}
|
|
#endif
|
|
|
|
#if PLAT_WOW_GPIO_PIN
|
|
wow_irq = gpio_to_irq(PLAT_WOW_GPIO_PIN);
|
|
if (wow_irq) {
|
|
int ret;
|
|
ret = request_irq(wow_irq, ar6000_wow_irq,
|
|
IRQF_SHARED | IRQF_TRIGGER_RISING,
|
|
"ar6000" "sdiowakeup", &wow_irq);
|
|
if (!ret) {
|
|
ret = enable_irq_wake(wow_irq);
|
|
if (ret < 0) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Couldn't enable WoW IRQ as wakeup interrupt"));
|
|
}
|
|
}
|
|
}
|
|
#endif /* PLAT_WOW_GPIO_PIN */
|
|
#endif /* CONFIG_PM */
|
|
}
|
|
|
|
void ar6000_pm_exit()
|
|
{
|
|
#ifdef CONFIG_PM
|
|
#if PLAT_WOW_GPIO_PIN
|
|
if (wow_irq) {
|
|
if (disable_irq_wake(wow_irq)) {
|
|
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Couldn't disable hostwake IRQ wakeup mode\n"));
|
|
}
|
|
free_irq(wow_irq, &wow_irq);
|
|
wow_irq = 0;
|
|
}
|
|
#endif /* PLAT_WOW_GPIO_PIN */
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15)
|
|
platform_driver_unregister(&ar6000_pm_device);
|
|
#endif
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
wake_lock_destroy(&ar6k_suspend_wake_lock);
|
|
wake_lock_destroy(&ar6k_wow_wake_lock);
|
|
#endif
|
|
#endif /* CONFIG_PM */
|
|
}
|
|
|