ath6kl: implement suspend support
authorKalle Valo <kvalo@qca.qualcomm.com>
Fri, 22 Jul 2011 05:26:20 +0000 (08:26 +0300)
committerKalle Valo <kvalo@qca.qualcomm.com>
Wed, 31 Aug 2011 07:10:54 +0000 (10:10 +0300)
For now this is implemented so that if host supports power is kept in
the chip. If that's not supported, an error is returned and sdio stack
will remove the device during suspend.

Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
drivers/net/wireless/ath/ath6kl/cfg80211.c
drivers/net/wireless/ath/ath6kl/core.h
drivers/net/wireless/ath/ath6kl/hif-ops.h
drivers/net/wireless/ath/ath6kl/hif.h
drivers/net/wireless/ath/ath6kl/main.c
drivers/net/wireless/ath/ath6kl/sdio.c

index b2b70e6618f5e3a05f21fde3bfc566734cd7c0c7..9128aa3c2b63aba2feb63136ceb472e8a4a10fe8 100644 (file)
@@ -17,6 +17,7 @@
 #include "core.h"
 #include "cfg80211.h"
 #include "debug.h"
+#include "hif-ops.h"
 
 #define RATETAB_ENT(_rate, _rateid, _flags) {   \
        .bitrate    = (_rate),                  \
@@ -1424,6 +1425,16 @@ static int ath6kl_flush_pmksa(struct wiphy *wiphy, struct net_device *netdev)
        return 0;
 }
 
+#ifdef CONFIG_PM
+static int ar6k_cfg80211_suspend(struct wiphy *wiphy,
+                                struct cfg80211_wowlan *wow)
+{
+       struct ath6kl *ar = wiphy_priv(wiphy);
+
+       return ath6kl_hif_suspend(ar);
+}
+#endif
+
 static struct cfg80211_ops ath6kl_cfg80211_ops = {
        .change_virtual_intf = ath6kl_cfg80211_change_iface,
        .scan = ath6kl_cfg80211_scan,
@@ -1443,6 +1454,9 @@ static struct cfg80211_ops ath6kl_cfg80211_ops = {
        .set_pmksa = ath6kl_set_pmksa,
        .del_pmksa = ath6kl_del_pmksa,
        .flush_pmksa = ath6kl_flush_pmksa,
+#ifdef CONFIG_PM
+       .suspend = ar6k_cfg80211_suspend,
+#endif
 };
 
 struct wireless_dev *ath6kl_cfg80211_init(struct device *dev)
index a1aa2ef398f710014fe1c7c5b84627677f3f70ed..4405ab56bb87a4d3f831b95ccdb7958f288a8957 100644 (file)
@@ -544,6 +544,7 @@ void ath6kl_pspoll_event(struct ath6kl *ar, u8 aid);
 
 void ath6kl_dtimexpiry_event(struct ath6kl *ar);
 void ath6kl_disconnect(struct ath6kl *ar);
+void ath6kl_deep_sleep_enable(struct ath6kl *ar);
 void aggr_recv_delba_req_evt(struct ath6kl *ar, u8 tid);
 void aggr_recv_addba_req_evt(struct ath6kl *ar, u8 tid, u16 seq_no,
                             u8 win_sz);
index c923979776a057fe59ce139396b17c90b094d850..d6c898f3d0b325a79ed968897f6cec9bdfd968a2 100644 (file)
@@ -69,4 +69,9 @@ static inline void ath6kl_hif_cleanup_scatter(struct ath6kl *ar)
        return ar->hif_ops->cleanup_scatter(ar);
 }
 
+static inline int ath6kl_hif_suspend(struct ath6kl *ar)
+{
+       return ar->hif_ops->suspend(ar);
+}
+
 #endif
index 5ceff54775a1deef0bafee7c51e4852bde00db7a..797e2d1d9bf9f41ede26891e1ac1eef0f673be5c 100644 (file)
@@ -202,6 +202,7 @@ struct ath6kl_hif_ops {
        int (*scat_req_rw) (struct ath6kl *ar,
                            struct hif_scatter_req *scat_req);
        void (*cleanup_scatter)(struct ath6kl *ar);
+       int (*suspend)(struct ath6kl *ar);
 };
 
 #endif
index 868838bb6b881de223b222147c6e7347dc987306..b64b2a35756072d53d665cec633e05e850039399 100644 (file)
@@ -795,6 +795,41 @@ void ath6kl_disconnect(struct ath6kl *ar)
        }
 }
 
+void ath6kl_deep_sleep_enable(struct ath6kl *ar)
+{
+       switch (ar->sme_state) {
+       case SME_CONNECTING:
+               cfg80211_connect_result(ar->net_dev, ar->bssid, NULL, 0,
+                                       NULL, 0,
+                                       WLAN_STATUS_UNSPECIFIED_FAILURE,
+                                       GFP_KERNEL);
+               break;
+       case SME_CONNECTED:
+       default:
+               /*
+                * FIXME: oddly enough smeState is in DISCONNECTED during
+                * suspend, why? Need to send disconnected event in that
+                * state.
+                */
+               cfg80211_disconnected(ar->net_dev, 0, NULL, 0, GFP_KERNEL);
+               break;
+       }
+
+       if (test_bit(CONNECTED, &ar->flag) ||
+           test_bit(CONNECT_PEND, &ar->flag))
+               ath6kl_wmi_disconnect_cmd(ar->wmi);
+
+       ar->sme_state = SME_DISCONNECTED;
+
+       /* disable scanning */
+       if (ath6kl_wmi_scanparams_cmd(ar->wmi, 0xFFFF, 0, 0, 0, 0, 0, 0, 0,
+                                     0, 0) != 0)
+               printk(KERN_WARNING "ath6kl: failed to disable scan "
+                      "during suspend\n");
+
+       ath6kl_cfg80211_scan_complete_event(ar, -ECANCELED);
+}
+
 /* WMI Event handlers */
 
 static const char *get_hw_id_string(u32 id)
index f393090ecefe63fa1d8fb9639140b32fb991c0f3..852a0ccc8033398180a7ba1e5e2eedaf47b7c1fc 100644 (file)
@@ -726,6 +726,31 @@ static int ath6kl_sdio_enable_scatter(struct ath6kl *ar)
        return 0;
 }
 
+static int ath6kl_sdio_suspend(struct ath6kl *ar)
+{
+       struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar);
+       struct sdio_func *func = ar_sdio->func;
+       mmc_pm_flag_t flags;
+       int ret;
+
+       flags = sdio_get_host_pm_caps(func);
+
+       if (!(flags & MMC_PM_KEEP_POWER))
+               /* as host doesn't support keep power we need to bail out */
+               return -EINVAL;
+
+       ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
+       if (ret) {
+               printk(KERN_ERR "ath6kl: set sdio pm flags failed: %d\n",
+                      ret);
+               return ret;
+       }
+
+       ath6kl_deep_sleep_enable(ar);
+
+       return 0;
+}
+
 static const struct ath6kl_hif_ops ath6kl_sdio_ops = {
        .read_write_sync = ath6kl_sdio_read_write_sync,
        .write_async = ath6kl_sdio_write_async,
@@ -736,6 +761,7 @@ static const struct ath6kl_hif_ops ath6kl_sdio_ops = {
        .enable_scatter = ath6kl_sdio_enable_scatter,
        .scat_req_rw = ath6kl_sdio_async_rw_scatter,
        .cleanup_scatter = ath6kl_sdio_cleanup_scatter,
+       .suspend = ath6kl_sdio_suspend,
 };
 
 static int ath6kl_sdio_probe(struct sdio_func *func,