#include "dhd.h"
 #include "wl_cfg80211.h"
 
+#define BRCMF_SCAN_IE_LEN_MAX          2048
+#define BRCMF_PNO_VERSION              2
+#define BRCMF_PNO_TIME                 30
+#define BRCMF_PNO_REPEAT               4
+#define BRCMF_PNO_FREQ_EXPO_MAX                3
+#define BRCMF_PNO_MAX_PFN_COUNT                16
+#define BRCMF_PNO_ENABLE_ADAPTSCAN_BIT 6
+#define BRCMF_PNO_HIDDEN_BIT           2
+#define BRCMF_PNO_WPA_AUTH_ANY         0xFFFFFFFF
+#define BRCMF_PNO_SCAN_COMPLETE                1
+#define BRCMF_PNO_SCAN_INCOMPLETE      0
+
 #define BRCMF_ASSOC_PARAMS_FIXED_SIZE \
        (sizeof(struct brcmf_assoc_params_le) - sizeof(u16))
 
                if (err)
                        WL_ERR("Scan abort  failed\n");
        }
-       if (scan_request) {
+       /*
+        * e-scan can be initiated by scheduled scan
+        * which takes precedence.
+        */
+       if (cfg_priv->sched_escan) {
+               WL_SCAN("scheduled scan completed\n");
+               cfg_priv->sched_escan = false;
+               if (!aborted)
+                       cfg80211_sched_scan_results(cfg_to_wiphy(cfg_priv));
+               brcmf_set_mpc(ndev, 1);
+       } else if (scan_request) {
                WL_SCAN("ESCAN Completed scan: %s\n",
                                aborted ? "Aborted" : "Done");
                cfg80211_scan_done(scan_request, aborted);
 
 }
 
+/*
+ * PFN result doesn't have all the info which are
+ * required by the supplicant
+ * (For e.g IEs) Do a target Escan so that sched scan results are reported
+ * via wl_inform_single_bss in the required format. Escan does require the
+ * scan request in the form of cfg80211_scan_request. For timebeing, create
+ * cfg80211_scan_request one out of the received PNO event.
+ */
+static s32
+brcmf_notify_sched_scan_results(struct brcmf_cfg80211_priv *cfg_priv,
+                               struct net_device *ndev,
+                               const struct brcmf_event_msg *e, void *data)
+{
+       struct brcmf_pno_net_info_le *netinfo, *netinfo_start;
+       struct cfg80211_scan_request *request = NULL;
+       struct cfg80211_ssid *ssid = NULL;
+       struct ieee80211_channel *channel = NULL;
+       struct wiphy *wiphy = cfg_to_wiphy(cfg_priv);
+       int err = 0;
+       int channel_req = 0;
+       int band = 0;
+       struct brcmf_pno_scanresults_le *pfn_result;
+       u32 result_count;
+       u32 status;
+
+       WL_SCAN("Enter\n");
+
+       if (e->event_type == cpu_to_be32(BRCMF_E_PFN_NET_LOST)) {
+               WL_SCAN("PFN NET LOST event. Do Nothing\n");
+               return 0;
+       }
+
+       pfn_result = (struct brcmf_pno_scanresults_le *)data;
+       result_count = le32_to_cpu(pfn_result->count);
+       status = le32_to_cpu(pfn_result->status);
+
+       /*
+        * PFN event is limited to fit 512 bytes so we may get
+        * multiple NET_FOUND events. For now place a warning here.
+        */
+       WARN_ON(status != BRCMF_PNO_SCAN_COMPLETE);
+       WL_SCAN("PFN NET FOUND event. count: %d\n", result_count);
+       if (result_count > 0) {
+               int i;
+
+               request = kzalloc(sizeof(*request), GFP_KERNEL);
+               ssid = kzalloc(sizeof(*ssid) * result_count, GFP_KERNEL);
+               channel = kzalloc(sizeof(*channel) * result_count, GFP_KERNEL);
+               if (!request || !ssid || !channel) {
+                       err = -ENOMEM;
+                       goto out_err;
+               }
+
+               request->wiphy = wiphy;
+               data += sizeof(struct brcmf_pno_scanresults_le);
+               netinfo_start = (struct brcmf_pno_net_info_le *)data;
+
+               for (i = 0; i < result_count; i++) {
+                       netinfo = &netinfo_start[i];
+                       if (!netinfo) {
+                               WL_ERR("Invalid netinfo ptr. index: %d\n", i);
+                               err = -EINVAL;
+                               goto out_err;
+                       }
+
+                       WL_SCAN("SSID:%s Channel:%d\n",
+                       netinfo->SSID, netinfo->channel);
+                       memcpy(ssid[i].ssid, netinfo->SSID, netinfo->SSID_len);
+                       ssid[i].ssid_len = netinfo->SSID_len;
+                       request->n_ssids++;
+
+                       channel_req = netinfo->channel;
+                       if (channel_req <= CH_MAX_2G_CHANNEL)
+                               band = NL80211_BAND_2GHZ;
+                       else
+                               band = NL80211_BAND_5GHZ;
+                       channel[i].center_freq =
+                               ieee80211_channel_to_frequency(channel_req,
+                                                              band);
+                       channel[i].band = band;
+                       channel[i].flags |= IEEE80211_CHAN_NO_HT40;
+                       request->channels[i] = &channel[i];
+                       request->n_channels++;
+               }
+
+               /* assign parsed ssid array */
+               if (request->n_ssids)
+                       request->ssids = &ssid[0];
+
+               if (test_bit(WL_STATUS_SCANNING, &cfg_priv->status)) {
+                       /* Abort any on-going scan */
+                       brcmf_abort_scanning(cfg_priv);
+               }
+
+               set_bit(WL_STATUS_SCANNING, &cfg_priv->status);
+               err = brcmf_do_escan(cfg_priv, wiphy, ndev, request);
+               if (err) {
+                       clear_bit(WL_STATUS_SCANNING, &cfg_priv->status);
+                       goto out_err;
+               }
+               cfg_priv->sched_escan = true;
+               cfg_priv->scan_request = request;
+       } else {
+               WL_ERR("FALSE PNO Event. (pfn_count == 0)\n");
+               goto out_err;
+       }
+
+       kfree(ssid);
+       kfree(channel);
+       kfree(request);
+       return 0;
+
+out_err:
+       kfree(ssid);
+       kfree(channel);
+       kfree(request);
+       cfg80211_sched_scan_stopped(wiphy);
+       return err;
+}
+
+#ifndef CONFIG_BRCMISCAN
+static int brcmf_dev_pno_clean(struct net_device *ndev)
+{
+       char iovbuf[128];
+       int ret;
+
+       /* Disable pfn */
+       ret = brcmf_dev_intvar_set(ndev, "pfn", 0);
+       if (ret == 0) {
+               /* clear pfn */
+               ret = brcmf_dev_iovar_setbuf(ndev, "pfnclear", NULL, 0,
+                                            iovbuf, sizeof(iovbuf));
+       }
+       if (ret < 0)
+               WL_ERR("failed code %d\n", ret);
+
+       return ret;
+}
+
+static int brcmf_dev_pno_config(struct net_device *ndev)
+{
+       struct brcmf_pno_param_le pfn_param;
+       char iovbuf[128];
+
+       memset(&pfn_param, 0, sizeof(pfn_param));
+       pfn_param.version = cpu_to_le32(BRCMF_PNO_VERSION);
+
+       /* set extra pno params */
+       pfn_param.flags = cpu_to_le16(1 << BRCMF_PNO_ENABLE_ADAPTSCAN_BIT);
+       pfn_param.repeat = BRCMF_PNO_REPEAT;
+       pfn_param.exp = BRCMF_PNO_FREQ_EXPO_MAX;
+
+       /* set up pno scan fr */
+       pfn_param.scan_freq = cpu_to_le32(BRCMF_PNO_TIME);
+
+       return brcmf_dev_iovar_setbuf(ndev, "pfn_set",
+                                     &pfn_param, sizeof(pfn_param),
+                                     iovbuf, sizeof(iovbuf));
+}
+
+static int
+brcmf_cfg80211_sched_scan_start(struct wiphy *wiphy,
+                               struct net_device *ndev,
+                               struct cfg80211_sched_scan_request *request)
+{
+       char iovbuf[128];
+       struct brcmf_cfg80211_priv *cfg_priv = wiphy_priv(wiphy);
+       struct brcmf_pno_net_param_le pfn;
+       int i;
+       int ret = 0;
+
+       WL_SCAN("Enter n_match_sets:%d   n_ssids:%d\n",
+               request->n_match_sets, request->n_ssids);
+       if (test_bit(WL_STATUS_SCANNING, &cfg_priv->status)) {
+               WL_ERR("Scanning already : status (%lu)\n", cfg_priv->status);
+               return -EAGAIN;
+       }
+
+       if (!request || !request->n_ssids || !request->n_match_sets) {
+               WL_ERR("Invalid sched scan req!! n_ssids:%d\n",
+                      request->n_ssids);
+               return -EINVAL;
+       }
+
+       if (request->n_ssids > 0) {
+               for (i = 0; i < request->n_ssids; i++) {
+                       /* Active scan req for ssids */
+                       WL_SCAN(">>> Active scan req for ssid (%s)\n",
+                               request->ssids[i].ssid);
+
+                       /*
+                        * match_set ssids is a supert set of n_ssid list,
+                        * so we need not add these set seperately.
+                        */
+               }
+       }
+
+       if (request->n_match_sets > 0) {
+               /* clean up everything */
+               ret = brcmf_dev_pno_clean(ndev);
+               if  (ret < 0) {
+                       WL_ERR("failed error=%d\n", ret);
+                       return ret;
+               }
+
+               /* configure pno */
+               ret = brcmf_dev_pno_config(ndev);
+               if (ret < 0) {
+                       WL_ERR("PNO setup failed!! ret=%d\n", ret);
+                       return -EINVAL;
+               }
+
+               /* configure each match set */
+               for (i = 0; i < request->n_match_sets; i++) {
+                       struct cfg80211_ssid *ssid;
+                       u32 ssid_len;
+
+                       ssid = &request->match_sets[i].ssid;
+                       ssid_len = ssid->ssid_len;
+
+                       if (!ssid_len) {
+                               WL_ERR("skip broadcast ssid\n");
+                               continue;
+                       }
+                       pfn.auth = cpu_to_le32(WLAN_AUTH_OPEN);
+                       pfn.wpa_auth = cpu_to_le32(BRCMF_PNO_WPA_AUTH_ANY);
+                       pfn.wsec = cpu_to_le32(0);
+                       pfn.infra = cpu_to_le32(1);
+                       pfn.flags = cpu_to_le32(1 << BRCMF_PNO_HIDDEN_BIT);
+                       pfn.ssid.SSID_len = cpu_to_le32(ssid_len);
+                       memcpy(pfn.ssid.SSID, ssid->ssid, ssid_len);
+                       ret = brcmf_dev_iovar_setbuf(ndev, "pfn_add",
+                                                    &pfn, sizeof(pfn),
+                                                    iovbuf, sizeof(iovbuf));
+                       WL_SCAN(">>> PNO filter %s for ssid (%s)\n",
+                               ret == 0 ? "set" : "failed",
+                               ssid->ssid);
+               }
+               /* Enable the PNO */
+               if (brcmf_dev_intvar_set(ndev, "pfn", 1) < 0) {
+                       WL_ERR("PNO enable failed!! ret=%d\n", ret);
+                       return -EINVAL;
+               }
+       } else {
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int brcmf_cfg80211_sched_scan_stop(struct wiphy *wiphy,
+                                         struct net_device *ndev)
+{
+       struct brcmf_cfg80211_priv *cfg_priv = wiphy_to_cfg(wiphy);
+
+       WL_SCAN("enter\n");
+       brcmf_dev_pno_clean(ndev);
+       if (cfg_priv->sched_escan)
+               brcmf_notify_escan_complete(cfg_priv, ndev, true, true);
+       return 0;
+}
+#endif /* CONFIG_BRCMISCAN */
+
 #ifdef CONFIG_NL80211_TESTMODE
 static int brcmf_cfg80211_testmode(struct wiphy *wiphy, void *data, int len)
 {
        .set_pmksa = brcmf_cfg80211_set_pmksa,
        .del_pmksa = brcmf_cfg80211_del_pmksa,
        .flush_pmksa = brcmf_cfg80211_flush_pmksa,
+#ifndef CONFIG_BRCMISCAN
+       /* scheduled scan need e-scan, which is mutual exclusive with i-scan */
+       .sched_scan_start = brcmf_cfg80211_sched_scan_start,
+       .sched_scan_stop = brcmf_cfg80211_sched_scan_stop,
+#endif
 #ifdef CONFIG_NL80211_TESTMODE
        .testmode_cmd = brcmf_cfg80211_testmode
 #endif
        return err;
 }
 
+static void brcmf_wiphy_pno_params(struct wiphy *wiphy)
+{
+#ifndef CONFIG_BRCMFISCAN
+       /* scheduled scan settings */
+       wiphy->max_sched_scan_ssids = BRCMF_PNO_MAX_PFN_COUNT;
+       wiphy->max_match_sets = BRCMF_PNO_MAX_PFN_COUNT;
+       wiphy->max_sched_scan_ie_len = BRCMF_SCAN_IE_LEN_MAX;
+       wiphy->flags |= WIPHY_FLAG_SUPPORTS_SCHED_SCAN;
+#endif
+}
+
 static struct wireless_dev *brcmf_alloc_wdev(s32 sizeof_iface,
                                          struct device *ndev)
 {
                                                                 * save mode
                                                                 * by default
                                                                 */
+       brcmf_wiphy_pno_params(wdev->wiphy);
        err = wiphy_register(wdev->wiphy);
        if (err < 0) {
                WL_ERR("Could not register wiphy device (%d)\n", err);
        el->handler[BRCMF_E_ROAM] = brcmf_notify_roaming_status;
        el->handler[BRCMF_E_MIC_ERROR] = brcmf_notify_mic_status;
        el->handler[BRCMF_E_SET_SSID] = brcmf_notify_connect_status;
+       el->handler[BRCMF_E_PFN_NET_FOUND] = brcmf_notify_sched_scan_results;
 }
 
 static void brcmf_deinit_priv_mem(struct brcmf_cfg80211_priv *cfg_priv)
        setbit(eventmask, BRCMF_E_JOIN_START);
        setbit(eventmask, BRCMF_E_SCAN_COMPLETE);
        setbit(eventmask, BRCMF_E_ESCAN_RESULT);
+       setbit(eventmask, BRCMF_E_PFN_NET_FOUND);
 
        brcmf_c_mkiovar("event_msgs", eventmask, BRCMF_EVENTING_MASK_LEN,
                        iovbuf, sizeof(iovbuf));
 
        struct net_device *ndev;
 };
 
+/**
+ * struct brcmf_pno_param_le - PNO scan configuration parameters
+ *
+ * @version: PNO parameters version.
+ * @scan_freq: scan frequency.
+ * @lost_network_timeout: #sec. to declare discovered network as lost.
+ * @flags: Bit field to control features of PFN such as sort criteria auto
+ *     enable switch and background scan.
+ * @rssi_margin: Margin to avoid jitter for choosing a PFN based on RSSI sort
+ *     criteria.
+ * @bestn: number of best networks in each scan.
+ * @mscan: number of scans recorded.
+ * @repeat: minimum number of scan intervals before scan frequency changes
+ *     in adaptive scan.
+ * @exp: exponent of 2 for maximum scan interval.
+ * @slow_freq: slow scan period.
+ */
+struct brcmf_pno_param_le {
+       __le32 version;
+       __le32 scan_freq;
+       __le32 lost_network_timeout;
+       __le16 flags;
+       __le16 rssi_margin;
+       u8 bestn;
+       u8 mscan;
+       u8 repeat;
+       u8 exp;
+       __le32 slow_freq;
+};
+
+/**
+ * struct brcmf_pno_net_param_le - scan parameters per preferred network.
+ *
+ * @ssid: ssid name and its length.
+ * @flags: bit2: hidden.
+ * @infra: BSS vs IBSS.
+ * @auth: Open vs Closed.
+ * @wpa_auth: WPA type.
+ * @wsec: wsec value.
+ */
+struct brcmf_pno_net_param_le {
+       struct brcmf_ssid_le ssid;
+       __le32 flags;
+       __le32 infra;
+       __le32 auth;
+       __le32 wpa_auth;
+       __le32 wsec;
+};
+
+/**
+ * struct brcmf_pno_net_info_le - information per found network.
+ *
+ * @bssid: BSS network identifier.
+ * @channel: channel number only.
+ * @SSID_len: length of ssid.
+ * @SSID: ssid characters.
+ * @RSSI: receive signal strength (in dBm).
+ * @timestamp: age in seconds.
+ */
+struct brcmf_pno_net_info_le {
+       u8 bssid[ETH_ALEN];
+       u8 channel;
+       u8 SSID_len;
+       u8 SSID[32];
+       __le16  RSSI;
+       __le16  timestamp;
+};
+
+/**
+ * struct brcmf_pno_scanresults_le - result returned in PNO NET FOUND event.
+ *
+ * @version: PNO version identifier.
+ * @status: indicates completion status of PNO scan.
+ * @count: amount of brcmf_pno_net_info_le entries appended.
+ */
+struct brcmf_pno_scanresults_le {
+       __le32 version;
+       __le32 status;
+       __le32 count;
+};
+
 /* dongle private data of cfg80211 interface */
 struct brcmf_cfg80211_priv {
        struct wireless_dev *wdev;      /* representing wl cfg80211 device */
        bool iscan_on;          /* iscan on/off switch */
        bool iscan_kickstart;   /* indicate iscan already started */
        bool active_scan;       /* current scan mode */
+       bool sched_escan;       /* e-scan for scheduled scan support running */
        bool ibss_starter;      /* indicates this sta is ibss starter */
        bool link_up;           /* link/connection up flag */
        bool pwr_save;          /* indicate whether dongle to support