On Android the gscan functionality is initiated by the wifi framework
whereas the scheduled scan can be initiated by wpa_supplicant. As such
it is required that these two scan types can run simultaneously.

Reviewed-by: Hante Meuleman <[email protected]>
Reviewed-by: Pieter-Paul Giesberts <[email protected]>
Reviewed-by: Franky Lin <[email protected]>
Signed-off-by: Arend van Spriel <[email protected]>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c         |  12 +-
 .../broadcom/brcm80211/brcmfmac/cfg80211.h         |   2 +
 .../wireless/broadcom/brcm80211/brcmfmac/core.c    |   5 +-
 .../wireless/broadcom/brcm80211/brcmfmac/debug.h   |   2 +
 .../net/wireless/broadcom/brcm80211/brcmfmac/pno.c | 278 ++++++++++++++++-----
 .../net/wireless/broadcom/brcm80211/brcmfmac/pno.h |  28 +++
 6 files changed, 267 insertions(+), 60 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c 
b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 61636ad..7749610 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -3432,7 +3432,7 @@ static int brcmf_cfg80211_sched_scan_stop(struct wiphy 
*wiphy,
        struct brcmf_if *ifp = netdev_priv(ndev);

        brcmf_dbg(SCAN, "enter\n");
-       brcmf_pno_clean(ifp);
+       brcmf_pno_stop_sched_scan(ifp);
        if (cfg->escan_rid)
                brcmf_notify_escan_complete(cfg, ifp, true, true);
        return 0;
@@ -5256,7 +5256,7 @@ static int brcmf_cfg80211_stop_gscan(struct wiphy *wiphy,

        brcmf_dbg(TRACE, "Enter, bssidx=%d\n", ifp->bsscfgidx);

-       return brcmf_pno_clean(ifp);
+       return brcmf_pno_stop_gscan(ifp);
 }

 #ifdef CONFIG_PM
@@ -7076,6 +7076,13 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct 
brcmf_pub *drvr,
                brcmf_p2p_detach(&cfg->p2p);
                goto wiphy_unreg_out;
        }
+       err = brcmf_pno_attach(ifp);
+       if (err) {
+               brcmf_err("PNO initialisation failed (%d)\n", err);
+               brcmf_btcoex_detach(cfg);
+               brcmf_p2p_detach(&cfg->p2p);
+               goto wiphy_unreg_out;
+       }

        if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_TDLS)) {
                err = brcmf_fil_iovar_int_set(ifp, "tdls_enable", 1);
@@ -7108,6 +7115,7 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct 
brcmf_pub *drvr,
        return cfg;

 detach:
+       brcmf_pno_detach(ifp);
        brcmf_btcoex_detach(cfg);
        brcmf_p2p_detach(&cfg->p2p);
 wiphy_unreg_out:
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h 
b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
index ff65970..b9f9375 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
@@ -293,6 +293,7 @@ enum brcmf_internal_escan_requestor {
  * @vif_cnt: number of vif instances.
  * @vif_event: vif event signalling.
  * @wowl: wowl related information.
+ * @pi: information of pno module.
  */
 struct brcmf_cfg80211_info {
        struct wiphy *wiphy;
@@ -326,6 +327,7 @@ struct brcmf_cfg80211_info {
        struct brcmu_d11inf d11inf;
        struct brcmf_assoclist_le assoclist;
        struct brcmf_cfg80211_wowl wowl;
+       struct brcmf_pno_info *pi;
 };

 /**
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c 
b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index 9e6f60a..c72653d1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -30,6 +30,7 @@
 #include "debug.h"
 #include "fwil_types.h"
 #include "p2p.h"
+#include "pno.h"
 #include "cfg80211.h"
 #include "fwil.h"
 #include "fwsignal.h"
@@ -1118,8 +1119,10 @@ void brcmf_detach(struct device *dev)

        /* stop firmware event handling */
        brcmf_fweh_detach(drvr);
-       if (drvr->config)
+       if (drvr->config) {
+               brcmf_pno_detach(bus_if->drvr->iflist[0]);
                brcmf_p2p_detach(&drvr->config->p2p);
+       }

        brcmf_bus_change_state(bus_if, BRCMF_BUS_DOWN);

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h 
b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
index 6687812..efc17b9 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
@@ -82,6 +82,7 @@
 #define BRCMF_EVENT_ON()       (brcmf_msg_level & BRCMF_EVENT_VAL)
 #define BRCMF_FIL_ON()         (brcmf_msg_level & BRCMF_FIL_VAL)
 #define BRCMF_FWCON_ON()       (brcmf_msg_level & BRCMF_FWCON_VAL)
+#define BRCMF_SCAN_ON()                (brcmf_msg_level & BRCMF_SCAN_VAL)

 #else /* defined(DEBUG) || defined(CONFIG_BRCM_TRACING) */

@@ -95,6 +96,7 @@
 #define BRCMF_EVENT_ON()       0
 #define BRCMF_FIL_ON()         0
 #define BRCMF_FWCON_ON()       0
+#define BRCMF_SCAN_ON()                0

 #endif /* defined(DEBUG) || defined(CONFIG_BRCM_TRACING) */

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c 
b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
index b868997..c687f21 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
@@ -14,6 +14,7 @@
  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 #include <linux/netdevice.h>
+#include <linux/gcd.h>
 #include <net/cfg80211.h>

 #include "core.h"
@@ -38,6 +39,13 @@
 #define GSCAN_BATCH_NO_THR_SET                 101
 #define GSCAN_RETRY_THRESHOLD                  3

+struct brcmf_pno_info {
+       struct cfg80211_sched_scan_request *sched;
+       struct cfg80211_gscan_request *gscan;
+};
+
+#define ifp_to_pno(_ifp)       (_ifp)->drvr->config->pi
+
 static int brcmf_pno_channel_config(struct brcmf_if *ifp,
                                    struct brcmf_pno_config_le *cfg)
 {
@@ -182,14 +190,46 @@ int brcmf_pno_clean(struct brcmf_if *ifp)
        return ret;
 }

+static void brcmf_pno_config_ssids(struct brcmf_if *ifp,
+                                  struct cfg80211_sched_scan_request *req)
+{
+       struct cfg80211_ssid *ssid;
+       int ret, i;
+
+       /* configure each match set */
+       for (i = 0; i < req->n_match_sets; i++) {
+
+               ssid = &req->match_sets[i].ssid;
+
+               if (!ssid->ssid_len) {
+                       brcmf_err("skip broadcast ssid\n");
+                       continue;
+               }
+
+               ret = brcmf_pno_add_ssid(ifp, ssid,
+                                        brcmf_is_ssid_active(ssid, req));
+               if (ret < 0)
+                       brcmf_dbg(SCAN, ">>> PNO filter %s for ssid (%s)\n",
+                                 ret == 0 ? "set" : "failed", ssid->ssid);
+       }
+}
+
 int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,
                               struct cfg80211_sched_scan_request *req)
 {
+       struct brcmf_pno_info *pi;
        struct brcmf_pno_config_le pno_cfg;
-       struct cfg80211_ssid *ssid;
        u16 chan;
        int i, ret;

+       pi = ifp_to_pno(ifp);
+
+       /* g-scan + scheduled scan is handled separately */
+       if (pi->gscan) {
+               pi->sched = req;
+               return brcmf_pno_start_gscan(ifp, pi->gscan);
+       }
+
        /* clean up everything */
        ret = brcmf_pno_clean(ifp);
        if  (ret < 0) {
@@ -217,31 +257,49 @@ int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,
        }
        if (req->n_channels) {
                pno_cfg.channel_num = cpu_to_le32(req->n_channels);
-               brcmf_pno_channel_config(ifp, &pno_cfg);
+               ret = brcmf_pno_channel_config(ifp, &pno_cfg);
+               if (ret < 0)
+                       return ret;
        }

-       /* configure each match set */
-       for (i = 0; i < req->n_match_sets; i++) {
-               ssid = &req->match_sets[i].ssid;
-               if (!ssid->ssid_len) {
-                       brcmf_err("skip broadcast ssid\n");
-                       continue;
-               }
+       brcmf_pno_config_ssids(ifp, req);

-               ret = brcmf_pno_add_ssid(ifp, ssid,
-                                        brcmf_is_ssid_active(ssid, req));
-               if (ret < 0)
-                       brcmf_dbg(SCAN, ">>> PNO filter %s for ssid (%s)\n",
-                                 ret == 0 ? "set" : "failed", ssid->ssid);
-       }
        /* Enable the PNO */
        ret = brcmf_fil_iovar_int_set(ifp, "pfn", 1);
-       if (ret < 0)
+       if (ret < 0) {
                brcmf_err("PNO enable failed!! ret=%d\n", ret);
+               return ret;
+       }

+       /* keep reference of request */
+       ifp_to_pno(ifp)->sched = req;
        return ret;
 }

+int brcmf_pno_stop_sched_scan(struct brcmf_if *ifp)
+{
+       struct brcmf_cfg80211_info *cfg;
+       int ret;
+
+       cfg = ifp->drvr->config;
+       if (!cfg->pi)
+               return 0;
+
+       /* may need to reconfigure gscan */
+       cfg->pi->sched = NULL;
+       if (cfg->pi->gscan) {
+               brcmf_dbg(SCAN, "reconfigure gscan\n");
+               ret = brcmf_pno_start_gscan(ifp, cfg->pi->gscan);
+               if (ret < 0) {
+                       brcmf_err("gscan reconfiguration failed: err=%d\n", 
ret);
+                       cfg80211_gscan_stopped_rtnl(cfg->wiphy);
+               }
+       } else {
+               brcmf_pno_clean(ifp);
+       }
+       return 0;
+}
+
 static int brcmf_pno_get_bucket_channels(struct brcmf_if *ifp,
                                         struct cfg80211_gscan_bucket *b,
                                         struct brcmf_pno_config_le *pno_cfg)
@@ -260,8 +318,7 @@ static int brcmf_pno_get_bucket_channels(struct brcmf_if 
*ifp,
                        goto done;
                }
                chan = b->channels[i].ch->hw_value;
-               brcmf_dbg(INFO, "[%d] Chan : %u\n",
-                         n_chan, chan);
+               brcmf_dbg(INFO, "[%d] Chan : %u\n", n_chan, chan);
                pno_cfg->channel_list[n_chan++] = cpu_to_le16(chan);
        }
        if (b->band & NL80211_BUCKET_BAND_2GHZ) {
@@ -274,8 +331,7 @@ static int brcmf_pno_get_bucket_channels(struct brcmf_if 
*ifp,
                                goto done;
                        }
                        chan = band->channels[i].hw_value;
-                       brcmf_dbg(INFO, "[%d] Chan : %u\n",
-                                 n_chan, chan);
+                       brcmf_dbg(INFO, "[%d] Chan : %u\n", n_chan, chan);
                        pno_cfg->channel_list[n_chan++] = cpu_to_le16(chan);
                }
        }
@@ -296,8 +352,7 @@ static int brcmf_pno_get_bucket_channels(struct brcmf_if 
*ifp,
                                goto done;
                        }
                        chan = band->channels[i].hw_value;
-                       brcmf_dbg(INFO, "[%d] Chan : %u\n",
-                                 n_chan, chan);
+                       brcmf_dbg(INFO, "[%d] Chan : %u\n", n_chan, chan);
                        pno_cfg->channel_list[n_chan++] = cpu_to_le16(chan);
                }
        }
@@ -311,14 +366,31 @@ static int brcmf_pno_get_bucket_channels(struct brcmf_if 
*ifp,
 static int brcmf_pno_prepare_gscan(struct brcmf_if *ifp,
                                   struct cfg80211_gscan_request *req,
                                   struct brcmf_pno_config_le *pno_cfg,
-                                  struct brcmf_gscan_bucket_config **buckets)
+                                  struct brcmf_gscan_bucket_config **buckets,
+                                  u32 *scan_freq)
 {
+       struct cfg80211_sched_scan_request *sr;
        struct brcmf_gscan_bucket_config *fw_buckets;
        struct cfg80211_gscan_bucket *bucket;
-       int i, err, chidx;
+       int i, err, chidx, n_fw_buckets;
+       u32 n_chan;
+       u16 chan;
+
+       sr = ifp_to_pno(ifp)->sched;
+       *scan_freq = req->base_period;
+       n_fw_buckets = req->n_buckets;
+       /*
+        * scheduled scan uses an additional bucket in firmware
+        * and the actual scan period must be the gcd.
+        */
+       if (sr) {
+               *scan_freq = gcd(sr->scan_plans[0].interval, *scan_freq);
+               n_fw_buckets++;
+               brcmf_dbg(SCAN, "g-scan+scheduled: period=%u\n", *scan_freq);
+       }

        *buckets = NULL;
-       fw_buckets = kcalloc(req->n_buckets, sizeof(*buckets[0]), GFP_KERNEL);
+       fw_buckets = kcalloc(n_fw_buckets, sizeof(*buckets[0]), GFP_KERNEL);
        if (!fw_buckets)
                return -ENOMEM;

@@ -331,14 +403,46 @@ static int brcmf_pno_prepare_gscan(struct brcmf_if *ifp,
                        goto fail;
                }
                fw_buckets[i].bucket_end_index = chidx - 1;
-               fw_buckets[i].bucket_freq_multiple = bucket->period / 
req->base_period;
+               fw_buckets[i].bucket_freq_multiple =
+                       bucket->period / *scan_freq;
                fw_buckets[i].repeat = cpu_to_le16(bucket->step_count);
-               fw_buckets[i].max_freq_multiple = 
cpu_to_le16(bucket->max_period / req->base_period);
-               fw_buckets[i].flag = bucket->report_events ^ 
NL80211_BUCKET_REPORT_NO_BATCH;
+               fw_buckets[i].max_freq_multiple =
+                       cpu_to_le16(bucket->max_period / *scan_freq);
+               fw_buckets[i].flag =
+                       bucket->report_events ^ NL80211_BUCKET_REPORT_NO_BATCH;
                bucket++;
        }
+
+       /* additional scheduled scan bucket */
+       if (sr) {
+               fw_buckets[i].bucket_freq_multiple =
+                       sr->scan_plans[0].interval / *scan_freq;
+               n_chan = le32_to_cpu(pno_cfg->channel_num);
+               for (chidx = 0; chidx < sr->n_channels; chidx++) {
+                       if (n_chan >= BRCMF_NUMCHANNELS) {
+                               err = -ENOSPC;
+                               goto fail;
+                       }
+                       chan = sr->channels[chidx]->hw_value;
+                       brcmf_dbg(INFO, "[%d] Chan : %u\n", n_chan, chan);
+                       pno_cfg->channel_list[n_chan++] = cpu_to_le16(chan);
+               }
+               pno_cfg->channel_num = cpu_to_le32(n_chan);
+               fw_buckets[i].bucket_end_index = n_chan - 1;
+       }
+
+       if (BRCMF_SCAN_ON()) {
+               brcmf_err("base period=%u\n", *scan_freq);
+               for (i = 0; i < n_fw_buckets; i++) {
+                       brcmf_err("[%d] period %u max %u repeat %u flag %x idx 
%u\n",
+                                 i, fw_buckets[i].bucket_freq_multiple,
+                                 le16_to_cpu(fw_buckets[i].max_freq_multiple),
+                                 fw_buckets[i].repeat, fw_buckets[i].flag,
+                                 fw_buckets[i].bucket_end_index);
+               }
+       }
        *buckets = fw_buckets;
-       return 0;
+       return n_fw_buckets;

 fail:
        kfree(fw_buckets);
@@ -348,11 +452,25 @@ static int brcmf_pno_prepare_gscan(struct brcmf_if *ifp,
 int brcmf_pno_start_gscan(struct brcmf_if *ifp,
                          struct cfg80211_gscan_request *req)
 {
+       struct brcmf_pno_info *pi;
        struct brcmf_gscan_config *gscan_cfg;
        struct brcmf_gscan_bucket_config *buckets;
        struct brcmf_pno_config_le pno_cfg;
-       size_t gscan_cfg_size;
-       int err;
+       size_t gsz;
+       u32 scan_freq;
+       int err, n_buckets;
+
+       n_buckets = brcmf_pno_prepare_gscan(ifp, req, &pno_cfg, &buckets,
+                                           &scan_freq);
+       if (n_buckets < 0)
+               return n_buckets;
+
+       gsz = sizeof(*gscan_cfg) + (n_buckets - 1) * sizeof(*buckets);
+       gscan_cfg = kzalloc(gsz, GFP_KERNEL);
+       if (!gscan_cfg) {
+               err = -ENOMEM;
+               goto free_buckets;
+       }

        /* clean up everything */
        err = brcmf_pno_clean(ifp);
@@ -362,31 +480,11 @@ int brcmf_pno_start_gscan(struct brcmf_if *ifp,
        }

        /* configure pno */
-       err = brcmf_pno_config(ifp, req->base_period / 1000,
-                              req->report_threshold_num_scans,
+       err = brcmf_pno_config(ifp, scan_freq, req->report_threshold_num_scans,
                               req->max_ap_per_scan);
        if (err < 0)
                return err;

-       /* configure random mac */
-       if (req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) {
-               err = brcmf_pno_set_random(ifp, req->mac, req->mac_mask);
-               if (err < 0)
-                       return err;
-       }
-
-       err = brcmf_pno_prepare_gscan(ifp, req, &pno_cfg, &buckets);
-       if (err < 0)
-               return err;
-
-       gscan_cfg_size = sizeof(*gscan_cfg) +
-                        (req->n_buckets - 1) * sizeof(*buckets);
-       gscan_cfg = kzalloc(gscan_cfg_size, GFP_KERNEL);
-       if (!gscan_cfg) {
-               err = -ENOMEM;
-               goto free_buckets;
-       }
-
        err = brcmf_pno_channel_config(ifp, &pno_cfg);
        if (err < 0)
                goto free_gscan;
@@ -401,19 +499,35 @@ int brcmf_pno_start_gscan(struct brcmf_if *ifp,

        gscan_cfg->flags = BRCMF_GSCAN_CFG_ALL_BUCKETS_IN_1ST_SCAN;

-       gscan_cfg->count_of_channel_buckets = req->n_buckets;
+       gscan_cfg->count_of_channel_buckets = n_buckets;
        memcpy(&gscan_cfg->bucket[0], buckets,
-              req->n_buckets * sizeof(*buckets));
+              n_buckets * sizeof(*buckets));

-       err = brcmf_fil_iovar_data_set(ifp, "pfn_gscan_cfg", gscan_cfg,
-                                      gscan_cfg_size);
+       err = brcmf_fil_iovar_data_set(ifp, "pfn_gscan_cfg", gscan_cfg, gsz);
        if (err < 0)
                goto free_gscan;

+       /* configure random mac */
+       if (req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) {
+               err = brcmf_pno_set_random(ifp, req->mac, req->mac_mask);
+               if (err < 0)
+                       return err;
+       }
+
+       pi = ifp_to_pno(ifp);
+       if (pi->sched)
+               brcmf_pno_config_ssids(ifp, pi->sched);
+
        /* Enable the PNO */
        err = brcmf_fil_iovar_int_set(ifp, "pfn", 1);
-       if (err < 0)
+       if (err < 0) {
                brcmf_err("PNO enable failed!! ret=%d\n", err);
+               goto free_gscan;
+       }
+
+       /* keep reference of request */
+       pi->gscan = req;
+
 free_gscan:
        kfree(gscan_cfg);
 free_buckets:
@@ -421,3 +535,53 @@ int brcmf_pno_start_gscan(struct brcmf_if *ifp,
        return err;
 }

+int brcmf_pno_stop_gscan(struct brcmf_if *ifp)
+{
+       struct brcmf_cfg80211_info *cfg;
+       int ret;
+
+       cfg = ifp->drvr->config;
+       if (!cfg->pi)
+               return 0;
+
+       cfg->pi->gscan = NULL;
+       if (cfg->pi->sched) {
+               brcmf_dbg(SCAN, "reconfigure scheduled scan\n");
+               ret = brcmf_pno_start_sched_scan(ifp, cfg->pi->sched);
+               if (ret < 0) {
+                       brcmf_err("scheduled scan reconfiguration failed: 
err=%d\n",
+                                 ret);
+                       cfg80211_sched_scan_stopped_rtnl(cfg->wiphy);
+               }
+       } else {
+               brcmf_pno_clean(ifp);
+       }
+       return 0;
+}
+
+int brcmf_pno_attach(struct brcmf_if *ifp)
+{
+       struct brcmf_pno_info *pi;
+
+       brcmf_err("enter\n");
+       pi = kzalloc(sizeof(*pi), GFP_KERNEL);
+       if (!pi)
+               return -ENOMEM;
+
+       ifp_to_pno(ifp) = pi;
+       return 0;
+}
+
+void brcmf_pno_detach(struct brcmf_if *ifp)
+{
+       struct brcmf_cfg80211_info *cfg;
+       struct brcmf_pno_info *pi;
+
+       brcmf_err("enter\n");
+       cfg = ifp->drvr->config;
+       pi = cfg->pi;
+       cfg->pi = NULL;
+
+       kfree(pi);
+}
+
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h 
b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h
index 06ad3b0..5690ac2 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h
@@ -38,6 +38,13 @@ int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,
                               struct cfg80211_sched_scan_request *req);

 /**
+ * brcmf_pno_stop_sched_scan - terminate scheduled scan on device.
+ *
+ * @ifp: interface object used.
+ */
+int brcmf_pno_stop_sched_scan(struct brcmf_if *ifp);
+
+/**
  * brcmf_pno_start_gscan - initiate gscan on device.
  *
  * @ifp: interface object used.
@@ -46,4 +53,25 @@ int brcmf_pno_start_sched_scan(struct brcmf_if *ifp,
 int brcmf_pno_start_gscan(struct brcmf_if *ifp,
                          struct cfg80211_gscan_request *req);

+/**
+ * brcmf_pno_stop_gscan - terminate gscan on device.
+ *
+ * @ifp: interface object used.
+ */
+int brcmf_pno_stop_gscan(struct brcmf_if *ifp);
+
+/**
+ * brcmf_pno_attach - allocate and attach module information.
+ *
+ * @ifp: interface object used.
+ */
+int brcmf_pno_attach(struct brcmf_if *ifp);
+
+/**
+ * brcmf_pno_detach - detach and free module information.
+ *
+ * @ifp: interface object used.
+ */
+void brcmf_pno_detach(struct brcmf_if *ifp);
+
 #endif /* _BRCMF_PNO_H */
--
1.9.1

Reply via email to