These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / net / wireless / brcm80211 / brcmfmac / cfg80211.c
index 8a15ebb..deb5f78 100644 (file)
@@ -52,8 +52,6 @@
 #define BRCMF_PNO_SCAN_COMPLETE                1
 #define BRCMF_PNO_SCAN_INCOMPLETE      0
 
-#define BRCMF_IFACE_MAX_CNT            3
-
 #define WPA_OUI                                "\x00\x50\xF2"  /* WPA OUI */
 #define WPA_OUI_TYPE                   1
 #define RSN_OUI                                "\x00\x0F\xAC"  /* RSN OUI */
@@ -129,13 +127,47 @@ static struct ieee80211_rate __wl_rates[] = {
        RATETAB_ENT(BRCM_RATE_54M, 0),
 };
 
-#define wl_a_rates             (__wl_rates + 4)
-#define wl_a_rates_size        8
 #define wl_g_rates             (__wl_rates + 0)
-#define wl_g_rates_size        12
+#define wl_g_rates_size                ARRAY_SIZE(__wl_rates)
+#define wl_a_rates             (__wl_rates + 4)
+#define wl_a_rates_size                (wl_g_rates_size - 4)
+
+#define CHAN2G(_channel, _freq) {                              \
+       .band                   = IEEE80211_BAND_2GHZ,          \
+       .center_freq            = (_freq),                      \
+       .hw_value               = (_channel),                   \
+       .flags                  = IEEE80211_CHAN_DISABLED,      \
+       .max_antenna_gain       = 0,                            \
+       .max_power              = 30,                           \
+}
+
+#define CHAN5G(_channel) {                                     \
+       .band                   = IEEE80211_BAND_5GHZ,          \
+       .center_freq            = 5000 + (5 * (_channel)),      \
+       .hw_value               = (_channel),                   \
+       .flags                  = IEEE80211_CHAN_DISABLED,      \
+       .max_antenna_gain       = 0,                            \
+       .max_power              = 30,                           \
+}
+
+static struct ieee80211_channel __wl_2ghz_channels[] = {
+       CHAN2G(1, 2412), CHAN2G(2, 2417), CHAN2G(3, 2422), CHAN2G(4, 2427),
+       CHAN2G(5, 2432), CHAN2G(6, 2437), CHAN2G(7, 2442), CHAN2G(8, 2447),
+       CHAN2G(9, 2452), CHAN2G(10, 2457), CHAN2G(11, 2462), CHAN2G(12, 2467),
+       CHAN2G(13, 2472), CHAN2G(14, 2484)
+};
+
+static struct ieee80211_channel __wl_5ghz_channels[] = {
+       CHAN5G(34), CHAN5G(36), CHAN5G(38), CHAN5G(40), CHAN5G(42),
+       CHAN5G(44), CHAN5G(46), CHAN5G(48), CHAN5G(52), CHAN5G(56),
+       CHAN5G(60), CHAN5G(64), CHAN5G(100), CHAN5G(104), CHAN5G(108),
+       CHAN5G(112), CHAN5G(116), CHAN5G(120), CHAN5G(124), CHAN5G(128),
+       CHAN5G(132), CHAN5G(136), CHAN5G(140), CHAN5G(144), CHAN5G(149),
+       CHAN5G(153), CHAN5G(157), CHAN5G(161), CHAN5G(165)
+};
 
 /* Band templates duplicated per wiphy. The channel info
- * is filled in after querying the device.
+ * above is added to the band during setup.
  */
 static const struct ieee80211_supported_band __wl_band_2ghz = {
        .band = IEEE80211_BAND_2GHZ,
@@ -143,7 +175,7 @@ static const struct ieee80211_supported_band __wl_band_2ghz = {
        .n_bitrates = wl_g_rates_size,
 };
 
-static const struct ieee80211_supported_band __wl_band_5ghz_a = {
+static const struct ieee80211_supported_band __wl_band_5ghz = {
        .band = IEEE80211_BAND_5GHZ,
        .bitrates = wl_a_rates,
        .n_bitrates = wl_a_rates_size,
@@ -204,89 +236,6 @@ static int brcmf_roamoff;
 module_param_named(roamoff, brcmf_roamoff, int, S_IRUSR);
 MODULE_PARM_DESC(roamoff, "do not use internal roaming engine");
 
-/* Quarter dBm units to mW
- * Table starts at QDBM_OFFSET, so the first entry is mW for qdBm=153
- * Table is offset so the last entry is largest mW value that fits in
- * a u16.
- */
-
-#define QDBM_OFFSET 153                /* Offset for first entry */
-#define QDBM_TABLE_LEN 40      /* Table size */
-
-/* Smallest mW value that will round up to the first table entry, QDBM_OFFSET.
- * Value is ( mW(QDBM_OFFSET - 1) + mW(QDBM_OFFSET) ) / 2
- */
-#define QDBM_TABLE_LOW_BOUND 6493      /* Low bound */
-
-/* Largest mW value that will round down to the last table entry,
- * QDBM_OFFSET + QDBM_TABLE_LEN-1.
- * Value is ( mW(QDBM_OFFSET + QDBM_TABLE_LEN - 1) +
- * mW(QDBM_OFFSET + QDBM_TABLE_LEN) ) / 2.
- */
-#define QDBM_TABLE_HIGH_BOUND 64938    /* High bound */
-
-static const u16 nqdBm_to_mW_map[QDBM_TABLE_LEN] = {
-/* qdBm:       +0      +1      +2      +3      +4      +5      +6      +7 */
-/* 153: */ 6683, 7079, 7499, 7943, 8414, 8913, 9441, 10000,
-/* 161: */ 10593, 11220, 11885, 12589, 13335, 14125, 14962, 15849,
-/* 169: */ 16788, 17783, 18836, 19953, 21135, 22387, 23714, 25119,
-/* 177: */ 26607, 28184, 29854, 31623, 33497, 35481, 37584, 39811,
-/* 185: */ 42170, 44668, 47315, 50119, 53088, 56234, 59566, 63096
-};
-
-static u16 brcmf_qdbm_to_mw(u8 qdbm)
-{
-       uint factor = 1;
-       int idx = qdbm - QDBM_OFFSET;
-
-       if (idx >= QDBM_TABLE_LEN)
-               /* clamp to max u16 mW value */
-               return 0xFFFF;
-
-       /* scale the qdBm index up to the range of the table 0-40
-        * where an offset of 40 qdBm equals a factor of 10 mW.
-        */
-       while (idx < 0) {
-               idx += 40;
-               factor *= 10;
-       }
-
-       /* return the mW value scaled down to the correct factor of 10,
-        * adding in factor/2 to get proper rounding.
-        */
-       return (nqdBm_to_mW_map[idx] + factor / 2) / factor;
-}
-
-static u8 brcmf_mw_to_qdbm(u16 mw)
-{
-       u8 qdbm;
-       int offset;
-       uint mw_uint = mw;
-       uint boundary;
-
-       /* handle boundary case */
-       if (mw_uint <= 1)
-               return 0;
-
-       offset = QDBM_OFFSET;
-
-       /* move mw into the range of the table */
-       while (mw_uint < QDBM_TABLE_LOW_BOUND) {
-               mw_uint *= 10;
-               offset -= 40;
-       }
-
-       for (qdbm = 0; qdbm < QDBM_TABLE_LEN - 1; qdbm++) {
-               boundary = nqdBm_to_mW_map[qdbm] + (nqdBm_to_mW_map[qdbm + 1] -
-                                                   nqdBm_to_mW_map[qdbm]) / 2;
-               if (mw_uint < boundary)
-                       break;
-       }
-
-       qdbm += (u8) offset;
-
-       return qdbm;
-}
 
 static u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf,
                               struct cfg80211_chan_def *ch)
@@ -437,6 +386,36 @@ brcmf_find_wpsie(const u8 *parse, u32 len)
        return NULL;
 }
 
+static int brcmf_vif_change_validate(struct brcmf_cfg80211_info *cfg,
+                                    struct brcmf_cfg80211_vif *vif,
+                                    enum nl80211_iftype new_type)
+{
+       int iftype_num[NUM_NL80211_IFTYPES];
+       struct brcmf_cfg80211_vif *pos;
+
+       memset(&iftype_num[0], 0, sizeof(iftype_num));
+       list_for_each_entry(pos, &cfg->vif_list, list)
+               if (pos == vif)
+                       iftype_num[new_type]++;
+               else
+                       iftype_num[pos->wdev.iftype]++;
+
+       return cfg80211_check_combinations(cfg->wiphy, 1, 0, iftype_num);
+}
+
+static int brcmf_vif_add_validate(struct brcmf_cfg80211_info *cfg,
+                                 enum nl80211_iftype new_type)
+{
+       int iftype_num[NUM_NL80211_IFTYPES];
+       struct brcmf_cfg80211_vif *pos;
+
+       memset(&iftype_num[0], 0, sizeof(iftype_num));
+       list_for_each_entry(pos, &cfg->vif_list, list)
+               iftype_num[pos->wdev.iftype]++;
+
+       iftype_num[new_type]++;
+       return cfg80211_check_combinations(cfg->wiphy, 1, 0, iftype_num);
+}
 
 static void convert_key_from_CPU(struct brcmf_wsec_key *key,
                                 struct brcmf_wsec_key_le *key_le)
@@ -631,8 +610,14 @@ static struct wireless_dev *brcmf_cfg80211_add_iface(struct wiphy *wiphy,
                                                     struct vif_params *params)
 {
        struct wireless_dev *wdev;
+       int err;
 
        brcmf_dbg(TRACE, "enter: %s type %d\n", name, type);
+       err = brcmf_vif_add_validate(wiphy_to_cfg(wiphy), type);
+       if (err) {
+               brcmf_err("iface validation failed: err=%d\n", err);
+               return ERR_PTR(err);
+       }
        switch (type) {
        case NL80211_IFTYPE_ADHOC:
        case NL80211_IFTYPE_STATION:
@@ -791,8 +776,43 @@ brcmf_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev,
        s32 ap = 0;
        s32 err = 0;
 
-       brcmf_dbg(TRACE, "Enter, ndev=%p, type=%d\n", ndev, type);
+       brcmf_dbg(TRACE, "Enter, idx=%d, type=%d\n", ifp->bssidx, type);
 
+       /* WAR: There are a number of p2p interface related problems which
+        * need to be handled initially (before doing the validate).
+        * wpa_supplicant tends to do iface changes on p2p device/client/go
+        * which are not always possible/allowed. However we need to return
+        * OK otherwise the wpa_supplicant wont start. The situation differs
+        * on configuration and setup (p2pon=1 module param). The first check
+        * is to see if the request is a change to station for p2p iface.
+        */
+       if ((type == NL80211_IFTYPE_STATION) &&
+           ((vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) ||
+            (vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) ||
+            (vif->wdev.iftype == NL80211_IFTYPE_P2P_DEVICE))) {
+               brcmf_dbg(TRACE, "Ignoring cmd for p2p if\n");
+               /* Now depending on whether module param p2pon=1 was used the
+                * response needs to be either 0 or EOPNOTSUPP. The reason is
+                * that if p2pon=1 is used, but a newer supplicant is used then
+                * we should return an error, as this combination wont work.
+                * In other situations 0 is returned and supplicant will start
+                * normally. It will give a trace in cfg80211, but it is the
+                * only way to get it working. Unfortunately this will result
+                * in situation where we wont support new supplicant in
+                * combination with module param p2pon=1, but that is the way
+                * it is. If the user tries this then unloading of driver might
+                * fail/lock.
+                */
+               if (cfg->p2p.p2pdev_dynamically)
+                       return -EOPNOTSUPP;
+               else
+                       return 0;
+       }
+       err = brcmf_vif_change_validate(wiphy_to_cfg(wiphy), vif, type);
+       if (err) {
+               brcmf_err("iface validation failed: err=%d\n", err);
+               return err;
+       }
        switch (type) {
        case NL80211_IFTYPE_MONITOR:
        case NL80211_IFTYPE_WDS:
@@ -803,18 +823,6 @@ brcmf_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev,
                infra = 0;
                break;
        case NL80211_IFTYPE_STATION:
-               /* Ignore change for p2p IF. Unclear why supplicant does this */
-               if ((vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) ||
-                   (vif->wdev.iftype == NL80211_IFTYPE_P2P_GO)) {
-                       brcmf_dbg(TRACE, "Ignoring cmd for p2p if\n");
-                       /* WAR: It is unexpected to get a change of VIF for P2P
-                        * IF, but it happens. The request can not be handled
-                        * but returning EPERM causes a crash. Returning 0
-                        * without setting ieee80211_ptr->iftype causes trace
-                        * (WARN_ON) but it works with wpa_supplicant
-                        */
-                       return 0;
-               }
                infra = 1;
                break;
        case NL80211_IFTYPE_AP:
@@ -832,7 +840,6 @@ brcmf_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev,
                        err = brcmf_p2p_ifchange(cfg, BRCMF_FIL_P2P_IF_GO);
                }
                if (!err) {
-                       set_bit(BRCMF_VIF_STATUS_AP_CREATING, &vif->sme_state);
                        brcmf_dbg(INFO, "IF Type = AP\n");
                }
        } else {
@@ -1262,7 +1269,7 @@ static void brcmf_link_down(struct brcmf_cfg80211_vif *vif, u16 reason)
                }
                clear_bit(BRCMF_VIF_STATUS_CONNECTED, &vif->sme_state);
                cfg80211_disconnected(vif->wdev.netdev, reason, NULL, 0,
-                                     GFP_KERNEL);
+                                     true, GFP_KERNEL);
 
        }
        clear_bit(BRCMF_VIF_STATUS_CONNECTING, &vif->sme_state);
@@ -1928,7 +1935,7 @@ brcmf_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *ndev,
 
        clear_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state);
        clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state);
-       cfg80211_disconnected(ndev, reason_code, NULL, 0, GFP_KERNEL);
+       cfg80211_disconnected(ndev, reason_code, NULL, 0, true, GFP_KERNEL);
 
        memcpy(&scbval.ea, &profile->bssid, ETH_ALEN);
        scbval.val = cpu_to_le32(reason_code);
@@ -1945,16 +1952,14 @@ static s32
 brcmf_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev,
                            enum nl80211_tx_power_setting type, s32 mbm)
 {
-
        struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
        struct net_device *ndev = cfg_to_ndev(cfg);
        struct brcmf_if *ifp = netdev_priv(ndev);
-       u16 txpwrmw;
-       s32 err = 0;
-       s32 disable = 0;
-       s32 dbm = MBM_TO_DBM(mbm);
+       s32 err;
+       s32 disable;
+       u32 qdbm = 127;
 
-       brcmf_dbg(TRACE, "Enter\n");
+       brcmf_dbg(TRACE, "Enter %d %d\n", type, mbm);
        if (!check_vif_up(ifp->vif))
                return -EIO;
 
@@ -1963,12 +1968,20 @@ brcmf_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev,
                break;
        case NL80211_TX_POWER_LIMITED:
        case NL80211_TX_POWER_FIXED:
-               if (dbm < 0) {
+               if (mbm < 0) {
                        brcmf_err("TX_POWER_FIXED - dbm is negative\n");
                        err = -EINVAL;
                        goto done;
                }
+               qdbm =  MBM_TO_DBM(4 * mbm);
+               if (qdbm > 127)
+                       qdbm = 127;
+               qdbm |= WL_TXPWR_OVERRIDE;
                break;
+       default:
+               brcmf_err("Unsupported type %d\n", type);
+               err = -EINVAL;
+               goto done;
        }
        /* Make sure radio is off or on as far as software is concerned */
        disable = WL_RADIO_SW_DISABLE << 16;
@@ -1976,52 +1989,44 @@ brcmf_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev,
        if (err)
                brcmf_err("WLC_SET_RADIO error (%d)\n", err);
 
-       if (dbm > 0xffff)
-               txpwrmw = 0xffff;
-       else
-               txpwrmw = (u16) dbm;
-       err = brcmf_fil_iovar_int_set(ifp, "qtxpower",
-                                     (s32)brcmf_mw_to_qdbm(txpwrmw));
+       err = brcmf_fil_iovar_int_set(ifp, "qtxpower", qdbm);
        if (err)
                brcmf_err("qtxpower error (%d)\n", err);
-       cfg->conf->tx_power = dbm;
 
 done:
-       brcmf_dbg(TRACE, "Exit\n");
+       brcmf_dbg(TRACE, "Exit %d (qdbm)\n", qdbm & ~WL_TXPWR_OVERRIDE);
        return err;
 }
 
-static s32 brcmf_cfg80211_get_tx_power(struct wiphy *wiphy,
-                                      struct wireless_dev *wdev,
-                                      s32 *dbm)
+static s32
+brcmf_cfg80211_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev,
+                           s32 *dbm)
 {
        struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
-       struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg));
-       s32 txpwrdbm;
-       u8 result;
-       s32 err = 0;
+       struct net_device *ndev = cfg_to_ndev(cfg);
+       struct brcmf_if *ifp = netdev_priv(ndev);
+       s32 qdbm = 0;
+       s32 err;
 
        brcmf_dbg(TRACE, "Enter\n");
        if (!check_vif_up(ifp->vif))
                return -EIO;
 
-       err = brcmf_fil_iovar_int_get(ifp, "qtxpower", &txpwrdbm);
+       err = brcmf_fil_iovar_int_get(ifp, "qtxpower", &qdbm);
        if (err) {
                brcmf_err("error (%d)\n", err);
                goto done;
        }
-
-       result = (u8) (txpwrdbm & ~WL_TXPWR_OVERRIDE);
-       *dbm = (s32) brcmf_qdbm_to_mw(result);
+       *dbm = (qdbm & ~WL_TXPWR_OVERRIDE) / 4;
 
 done:
-       brcmf_dbg(TRACE, "Exit\n");
+       brcmf_dbg(TRACE, "Exit (0x%x %d)\n", qdbm, *dbm);
        return err;
 }
 
 static s32
 brcmf_cfg80211_config_default_key(struct wiphy *wiphy, struct net_device *ndev,
-                              u8 key_idx, bool unicast, bool multicast)
+                                 u8 key_idx, bool unicast, bool multicast)
 {
        struct brcmf_if *ifp = netdev_priv(ndev);
        u32 index;
@@ -2364,27 +2369,83 @@ brcmf_cfg80211_reconfigure_wep(struct brcmf_if *ifp)
                brcmf_err("set wsec error (%d)\n", err);
 }
 
+static void brcmf_convert_sta_flags(u32 fw_sta_flags, struct station_info *si)
+{
+       struct nl80211_sta_flag_update *sfu;
+
+       brcmf_dbg(TRACE, "flags %08x\n", fw_sta_flags);
+       si->filled |= BIT(NL80211_STA_INFO_STA_FLAGS);
+       sfu = &si->sta_flags;
+       sfu->mask = BIT(NL80211_STA_FLAG_WME) |
+                   BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+                   BIT(NL80211_STA_FLAG_ASSOCIATED) |
+                   BIT(NL80211_STA_FLAG_AUTHORIZED);
+       if (fw_sta_flags & BRCMF_STA_WME)
+               sfu->set |= BIT(NL80211_STA_FLAG_WME);
+       if (fw_sta_flags & BRCMF_STA_AUTHE)
+               sfu->set |= BIT(NL80211_STA_FLAG_AUTHENTICATED);
+       if (fw_sta_flags & BRCMF_STA_ASSOC)
+               sfu->set |= BIT(NL80211_STA_FLAG_ASSOCIATED);
+       if (fw_sta_flags & BRCMF_STA_AUTHO)
+               sfu->set |= BIT(NL80211_STA_FLAG_AUTHORIZED);
+}
+
+static void brcmf_fill_bss_param(struct brcmf_if *ifp, struct station_info *si)
+{
+       struct {
+               __le32 len;
+               struct brcmf_bss_info_le bss_le;
+       } *buf;
+       u16 capability;
+       int err;
+
+       buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL);
+       if (!buf)
+               return;
+
+       buf->len = cpu_to_le32(WL_BSS_INFO_MAX);
+       err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSS_INFO, buf,
+                                    WL_BSS_INFO_MAX);
+       if (err) {
+               brcmf_err("Failed to get bss info (%d)\n", err);
+               return;
+       }
+       si->filled |= BIT(NL80211_STA_INFO_BSS_PARAM);
+       si->bss_param.beacon_interval = le16_to_cpu(buf->bss_le.beacon_period);
+       si->bss_param.dtim_period = buf->bss_le.dtim_period;
+       capability = le16_to_cpu(buf->bss_le.capability);
+       if (capability & IEEE80211_HT_STBC_PARAM_DUAL_CTS_PROT)
+               si->bss_param.flags |= BSS_PARAM_FLAGS_CTS_PROT;
+       if (capability & WLAN_CAPABILITY_SHORT_PREAMBLE)
+               si->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_PREAMBLE;
+       if (capability & WLAN_CAPABILITY_SHORT_SLOT_TIME)
+               si->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_SLOT_TIME;
+}
+
 static s32
 brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev,
                           const u8 *mac, struct station_info *sinfo)
 {
        struct brcmf_if *ifp = netdev_priv(ndev);
-       struct brcmf_cfg80211_profile *profile = &ifp->vif->profile;
-       struct brcmf_scb_val_le scb_val;
-       int rssi;
-       s32 rate;
        s32 err = 0;
-       u8 *bssid = profile->bssid;
        struct brcmf_sta_info_le sta_info_le;
-       u32 beacon_period;
-       u32 dtim_period;
+       u32 sta_flags;
+       u32 is_tdls_peer;
+       s32 total_rssi;
+       s32 count_rssi;
+       u32 i;
 
        brcmf_dbg(TRACE, "Enter, MAC %pM\n", mac);
        if (!check_vif_up(ifp->vif))
                return -EIO;
 
-       if (brcmf_is_apmode(ifp->vif)) {
-               memcpy(&sta_info_le, mac, ETH_ALEN);
+       memset(&sta_info_le, 0, sizeof(sta_info_le));
+       memcpy(&sta_info_le, mac, ETH_ALEN);
+       err = brcmf_fil_iovar_data_get(ifp, "tdls_sta_info",
+                                      &sta_info_le,
+                                      sizeof(sta_info_le));
+       is_tdls_peer = !err;
+       if (err) {
                err = brcmf_fil_iovar_data_get(ifp, "sta_info",
                                               &sta_info_le,
                                               sizeof(sta_info_le));
@@ -2392,78 +2453,102 @@ brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev,
                        brcmf_err("GET STA INFO failed, %d\n", err);
                        goto done;
                }
-               sinfo->filled = BIT(NL80211_STA_INFO_INACTIVE_TIME);
-               sinfo->inactive_time = le32_to_cpu(sta_info_le.idle) * 1000;
-               if (le32_to_cpu(sta_info_le.flags) & BRCMF_STA_ASSOC) {
-                       sinfo->filled |= BIT(NL80211_STA_INFO_CONNECTED_TIME);
-                       sinfo->connected_time = le32_to_cpu(sta_info_le.in);
+       }
+       brcmf_dbg(TRACE, "version %d\n", le16_to_cpu(sta_info_le.ver));
+       sinfo->filled = BIT(NL80211_STA_INFO_INACTIVE_TIME);
+       sinfo->inactive_time = le32_to_cpu(sta_info_le.idle) * 1000;
+       sta_flags = le32_to_cpu(sta_info_le.flags);
+       brcmf_convert_sta_flags(sta_flags, sinfo);
+       sinfo->sta_flags.mask |= BIT(NL80211_STA_FLAG_TDLS_PEER);
+       if (is_tdls_peer)
+               sinfo->sta_flags.set |= BIT(NL80211_STA_FLAG_TDLS_PEER);
+       else
+               sinfo->sta_flags.set &= ~BIT(NL80211_STA_FLAG_TDLS_PEER);
+       if (sta_flags & BRCMF_STA_ASSOC) {
+               sinfo->filled |= BIT(NL80211_STA_INFO_CONNECTED_TIME);
+               sinfo->connected_time = le32_to_cpu(sta_info_le.in);
+               brcmf_fill_bss_param(ifp, sinfo);
+       }
+       if (sta_flags & BRCMF_STA_SCBSTATS) {
+               sinfo->filled |= BIT(NL80211_STA_INFO_TX_FAILED);
+               sinfo->tx_failed = le32_to_cpu(sta_info_le.tx_failures);
+               sinfo->filled |= BIT(NL80211_STA_INFO_TX_PACKETS);
+               sinfo->tx_packets = le32_to_cpu(sta_info_le.tx_pkts);
+               sinfo->tx_packets += le32_to_cpu(sta_info_le.tx_mcast_pkts);
+               sinfo->filled |= BIT(NL80211_STA_INFO_RX_PACKETS);
+               sinfo->rx_packets = le32_to_cpu(sta_info_le.rx_ucast_pkts);
+               sinfo->rx_packets += le32_to_cpu(sta_info_le.rx_mcast_pkts);
+               if (sinfo->tx_packets) {
+                       sinfo->filled |= BIT(NL80211_STA_INFO_TX_BITRATE);
+                       sinfo->txrate.legacy =
+                               le32_to_cpu(sta_info_le.tx_rate) / 100;
                }
-               brcmf_dbg(TRACE, "STA idle time : %d ms, connected time :%d sec\n",
-                         sinfo->inactive_time, sinfo->connected_time);
-       } else if (ifp->vif->wdev.iftype == NL80211_IFTYPE_STATION) {
-               if (memcmp(mac, bssid, ETH_ALEN)) {
-                       brcmf_err("Wrong Mac address cfg_mac-%pM wl_bssid-%pM\n",
-                                 mac, bssid);
-                       err = -ENOENT;
-                       goto done;
+               if (sinfo->rx_packets) {
+                       sinfo->filled |= BIT(NL80211_STA_INFO_RX_BITRATE);
+                       sinfo->rxrate.legacy =
+                               le32_to_cpu(sta_info_le.rx_rate) / 100;
                }
-               /* Report the current tx rate */
-               err = brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_RATE, &rate);
-               if (err) {
-                       brcmf_err("Could not get rate (%d)\n", err);
-                       goto done;
-               } else {
-                       sinfo->filled |= BIT(NL80211_STA_INFO_TX_BITRATE);
-                       sinfo->txrate.legacy = rate * 5;
-                       brcmf_dbg(CONN, "Rate %d Mbps\n", rate / 2);
+               if (le16_to_cpu(sta_info_le.ver) >= 4) {
+                       sinfo->filled |= BIT(NL80211_STA_INFO_TX_BYTES);
+                       sinfo->tx_bytes = le64_to_cpu(sta_info_le.tx_tot_bytes);
+                       sinfo->filled |= BIT(NL80211_STA_INFO_RX_BYTES);
+                       sinfo->rx_bytes = le64_to_cpu(sta_info_le.rx_tot_bytes);
                }
-
-               if (test_bit(BRCMF_VIF_STATUS_CONNECTED,
-                            &ifp->vif->sme_state)) {
-                       memset(&scb_val, 0, sizeof(scb_val));
-                       err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_RSSI,
-                                                    &scb_val, sizeof(scb_val));
-                       if (err) {
-                               brcmf_err("Could not get rssi (%d)\n", err);
-                               goto done;
-                       } else {
-                               rssi = le32_to_cpu(scb_val.val);
-                               sinfo->filled |= BIT(NL80211_STA_INFO_SIGNAL);
-                               sinfo->signal = rssi;
-                               brcmf_dbg(CONN, "RSSI %d dBm\n", rssi);
-                       }
-                       err = brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_BCNPRD,
-                                                   &beacon_period);
-                       if (err) {
-                               brcmf_err("Could not get beacon period (%d)\n",
-                                         err);
-                               goto done;
-                       } else {
-                               sinfo->bss_param.beacon_interval =
-                                       beacon_period;
-                               brcmf_dbg(CONN, "Beacon peroid %d\n",
-                                         beacon_period);
+               total_rssi = 0;
+               count_rssi = 0;
+               for (i = 0; i < BRCMF_ANT_MAX; i++) {
+                       if (sta_info_le.rssi[i]) {
+                               sinfo->chain_signal_avg[count_rssi] =
+                                       sta_info_le.rssi[i];
+                               sinfo->chain_signal[count_rssi] =
+                                       sta_info_le.rssi[i];
+                               total_rssi += sta_info_le.rssi[i];
+                               count_rssi++;
                        }
-                       err = brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_DTIMPRD,
-                                                   &dtim_period);
-                       if (err) {
-                               brcmf_err("Could not get DTIM period (%d)\n",
-                                         err);
-                               goto done;
-                       } else {
-                               sinfo->bss_param.dtim_period = dtim_period;
-                               brcmf_dbg(CONN, "DTIM peroid %d\n",
-                                         dtim_period);
-                       }
-                       sinfo->filled |= BIT(NL80211_STA_INFO_BSS_PARAM);
                }
-       } else
-               err = -EPERM;
+               if (count_rssi) {
+                       sinfo->filled |= BIT(NL80211_STA_INFO_CHAIN_SIGNAL);
+                       sinfo->chains = count_rssi;
+
+                       sinfo->filled |= BIT(NL80211_STA_INFO_SIGNAL);
+                       total_rssi /= count_rssi;
+                       sinfo->signal = total_rssi;
+               }
+       }
 done:
        brcmf_dbg(TRACE, "Exit\n");
        return err;
 }
 
+static int
+brcmf_cfg80211_dump_station(struct wiphy *wiphy, struct net_device *ndev,
+                           int idx, u8 *mac, struct station_info *sinfo)
+{
+       struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
+       struct brcmf_if *ifp = netdev_priv(ndev);
+       s32 err;
+
+       brcmf_dbg(TRACE, "Enter, idx %d\n", idx);
+
+       if (idx == 0) {
+               cfg->assoclist.count = cpu_to_le32(BRCMF_MAX_ASSOCLIST);
+               err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_ASSOCLIST,
+                                            &cfg->assoclist,
+                                            sizeof(cfg->assoclist));
+               if (err) {
+                       brcmf_err("BRCMF_C_GET_ASSOCLIST unsupported, err=%d\n",
+                                 err);
+                       cfg->assoclist.count = 0;
+                       return -EOPNOTSUPP;
+               }
+       }
+       if (idx < le32_to_cpu(cfg->assoclist.count)) {
+               memcpy(mac, cfg->assoclist.mac[idx], ETH_ALEN);
+               return brcmf_cfg80211_get_station(wiphy, ndev, mac, sinfo);
+       }
+       return -ENOENT;
+}
+
 static s32
 brcmf_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *ndev,
                           bool enabled, s32 timeout)
@@ -4165,8 +4250,8 @@ brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev,
 
                brcmf_dbg(TRACE, "GO mode configuration complete\n");
        }
-       clear_bit(BRCMF_VIF_STATUS_AP_CREATING, &ifp->vif->sme_state);
        set_bit(BRCMF_VIF_STATUS_AP_CREATED, &ifp->vif->sme_state);
+       brcmf_net_setcarrier(ifp, true);
 
 exit:
        if ((err) && (!mbss)) {
@@ -4230,8 +4315,8 @@ static int brcmf_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev)
        }
        brcmf_set_mpc(ifp, 1);
        brcmf_configure_arp_offload(ifp, true);
-       set_bit(BRCMF_VIF_STATUS_AP_CREATING, &ifp->vif->sme_state);
        clear_bit(BRCMF_VIF_STATUS_AP_CREATED, &ifp->vif->sme_state);
+       brcmf_net_setcarrier(ifp, false);
 
        return err;
 }
@@ -4563,6 +4648,7 @@ static struct cfg80211_ops wl_cfg80211_ops = {
        .join_ibss = brcmf_cfg80211_join_ibss,
        .leave_ibss = brcmf_cfg80211_leave_ibss,
        .get_station = brcmf_cfg80211_get_station,
+       .dump_station = brcmf_cfg80211_dump_station,
        .set_tx_power = brcmf_cfg80211_set_tx_power,
        .get_tx_power = brcmf_cfg80211_get_tx_power,
        .add_key = brcmf_cfg80211_add_key,
@@ -4647,7 +4733,8 @@ void brcmf_cfg80211_free_netdev(struct net_device *ndev)
        ifp = netdev_priv(ndev);
        vif = ifp->vif;
 
-       brcmf_free_vif(vif);
+       if (vif)
+               brcmf_free_vif(vif);
        free_netdev(ndev);
 }
 
@@ -4883,7 +4970,7 @@ brcmf_notify_connect_status_ap(struct brcmf_cfg80211_info *cfg,
                brcmf_dbg(CONN, "AP mode link down\n");
                complete(&cfg->vif_disabled);
                if (ifp->vif->mbss)
-                       brcmf_remove_interface(ifp->drvr, ifp->bssidx);
+                       brcmf_remove_interface(ifp);
                return 0;
        }
 
@@ -4939,6 +5026,7 @@ brcmf_notify_connect_status(struct brcmf_if *ifp,
                                &ifp->vif->sme_state);
                } else
                        brcmf_bss_connect_done(cfg, ndev, e, true);
+               brcmf_net_setcarrier(ifp, true);
        } else if (brcmf_is_linkdown(e)) {
                brcmf_dbg(CONN, "Linkdown\n");
                if (!brcmf_is_ibssmode(ifp->vif)) {
@@ -4948,6 +5036,7 @@ brcmf_notify_connect_status(struct brcmf_if *ifp,
                brcmf_init_prof(ndev_to_prof(ndev));
                if (ndev != cfg_to_ndev(cfg))
                        complete(&cfg->vif_disabled);
+               brcmf_net_setcarrier(ifp, false);
        } else if (brcmf_is_nonetwork(cfg, e)) {
                if (brcmf_is_ibssmode(ifp->vif))
                        clear_bit(BRCMF_VIF_STATUS_CONNECTING,
@@ -5253,40 +5342,6 @@ dongle_scantime_out:
        return err;
 }
 
-/* Filter the list of channels received from firmware counting only
- * the 20MHz channels. The wiphy band data only needs those which get
- * flagged to indicate if they can take part in higher bandwidth.
- */
-static void brcmf_count_20mhz_channels(struct brcmf_cfg80211_info *cfg,
-                                      struct brcmf_chanspec_list *chlist,
-                                      u32 chcnt[])
-{
-       u32 total = le32_to_cpu(chlist->count);
-       struct brcmu_chan ch;
-       int i;
-
-       for (i = 0; i < total; i++) {
-               ch.chspec = (u16)le32_to_cpu(chlist->element[i]);
-               cfg->d11inf.decchspec(&ch);
-
-               /* Firmware gives a ordered list. We skip non-20MHz
-                * channels is 2G. For 5G we can abort upon reaching
-                * a non-20MHz channel in the list.
-                */
-               if (ch.bw != BRCMU_CHAN_BW_20) {
-                       if (ch.band == BRCMU_CHAN_BAND_5G)
-                               break;
-                       else
-                               continue;
-               }
-
-               if (ch.band == BRCMU_CHAN_BAND_2G)
-                       chcnt[0] += 1;
-               else if (ch.band == BRCMU_CHAN_BAND_5G)
-                       chcnt[1] += 1;
-       }
-}
-
 static void brcmf_update_bw40_channel_flag(struct ieee80211_channel *channel,
                                           struct brcmu_chan *ch)
 {
@@ -5322,7 +5377,6 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg,
        u32 i, j;
        u32 total;
        u32 chaninfo;
-       u32 chcnt[2] = { 0, 0 };
        u32 index;
 
        pbuf = kzalloc(BRCMF_DCMD_MEDLEN, GFP_KERNEL);
@@ -5339,42 +5393,15 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg,
                goto fail_pbuf;
        }
 
-       brcmf_count_20mhz_channels(cfg, list, chcnt);
        wiphy = cfg_to_wiphy(cfg);
-       if (chcnt[0]) {
-               band = kmemdup(&__wl_band_2ghz, sizeof(__wl_band_2ghz),
-                              GFP_KERNEL);
-               if (band == NULL) {
-                       err = -ENOMEM;
-                       goto fail_pbuf;
-               }
-               band->channels = kcalloc(chcnt[0], sizeof(*channel),
-                                        GFP_KERNEL);
-               if (band->channels == NULL) {
-                       kfree(band);
-                       err = -ENOMEM;
-                       goto fail_pbuf;
-               }
-               band->n_channels = 0;
-               wiphy->bands[IEEE80211_BAND_2GHZ] = band;
-       }
-       if (chcnt[1]) {
-               band = kmemdup(&__wl_band_5ghz_a, sizeof(__wl_band_5ghz_a),
-                              GFP_KERNEL);
-               if (band == NULL) {
-                       err = -ENOMEM;
-                       goto fail_band2g;
-               }
-               band->channels = kcalloc(chcnt[1], sizeof(*channel),
-                                        GFP_KERNEL);
-               if (band->channels == NULL) {
-                       kfree(band);
-                       err = -ENOMEM;
-                       goto fail_band2g;
-               }
-               band->n_channels = 0;
-               wiphy->bands[IEEE80211_BAND_5GHZ] = band;
-       }
+       band = wiphy->bands[IEEE80211_BAND_2GHZ];
+       if (band)
+               for (i = 0; i < band->n_channels; i++)
+                       band->channels[i].flags = IEEE80211_CHAN_DISABLED;
+       band = wiphy->bands[IEEE80211_BAND_5GHZ];
+       if (band)
+               for (i = 0; i < band->n_channels; i++)
+                       band->channels[i].flags = IEEE80211_CHAN_DISABLED;
 
        total = le32_to_cpu(list->count);
        for (i = 0; i < total; i++) {
@@ -5389,6 +5416,8 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg,
                        brcmf_err("Invalid channel Spec. 0x%x.\n", ch.chspec);
                        continue;
                }
+               if (!band)
+                       continue;
                if (!(bw_cap[band->band] & WLC_BW_40MHZ_BIT) &&
                    ch.bw == BRCMU_CHAN_BW_40)
                        continue;
@@ -5416,9 +5445,9 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg,
                } else if (ch.bw == BRCMU_CHAN_BW_40) {
                        brcmf_update_bw40_channel_flag(&channel[index], &ch);
                } else {
-                       /* disable other bandwidths for now as mentioned
-                        * order assure they are enabled for subsequent
-                        * chanspecs.
+                       /* enable the channel and disable other bandwidths
+                        * for now as mentioned order assure they are enabled
+                        * for subsequent chanspecs.
                         */
                        channel[index].flags = IEEE80211_CHAN_NO_HT40 |
                                               IEEE80211_CHAN_NO_80MHZ;
@@ -5437,16 +5466,8 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg,
                                                IEEE80211_CHAN_NO_IR;
                        }
                }
-               if (index == band->n_channels)
-                       band->n_channels++;
        }
-       kfree(pbuf);
-       return 0;
 
-fail_band2g:
-       kfree(wiphy->bands[IEEE80211_BAND_2GHZ]->channels);
-       kfree(wiphy->bands[IEEE80211_BAND_2GHZ]);
-       wiphy->bands[IEEE80211_BAND_2GHZ] = NULL;
 fail_pbuf:
        kfree(pbuf);
        return err;
@@ -5674,53 +5695,6 @@ static int brcmf_setup_wiphybands(struct wiphy *wiphy)
        return 0;
 }
 
-static const struct ieee80211_iface_limit brcmf_iface_limits_mbss[] = {
-       {
-               .max = 1,
-               .types = BIT(NL80211_IFTYPE_STATION) |
-                        BIT(NL80211_IFTYPE_ADHOC)
-       },
-       {
-               .max = 4,
-               .types = BIT(NL80211_IFTYPE_AP)
-       },
-       {
-               .max = 1,
-               .types = BIT(NL80211_IFTYPE_P2P_CLIENT) |
-                        BIT(NL80211_IFTYPE_P2P_GO)
-       },
-       {
-               .max = 1,
-               .types = BIT(NL80211_IFTYPE_P2P_DEVICE)
-       }
-};
-
-static const struct ieee80211_iface_limit brcmf_iface_limits_sbss[] = {
-       {
-               .max = 2,
-               .types = BIT(NL80211_IFTYPE_STATION) |
-                        BIT(NL80211_IFTYPE_ADHOC) |
-                        BIT(NL80211_IFTYPE_AP)
-       },
-       {
-               .max = 1,
-               .types = BIT(NL80211_IFTYPE_P2P_CLIENT) |
-                        BIT(NL80211_IFTYPE_P2P_GO)
-       },
-       {
-               .max = 1,
-               .types = BIT(NL80211_IFTYPE_P2P_DEVICE)
-       }
-};
-static struct ieee80211_iface_combination brcmf_iface_combos[] = {
-       {
-                .max_interfaces = BRCMF_IFACE_MAX_CNT,
-                .num_different_channels = 1,
-                .n_limits = ARRAY_SIZE(brcmf_iface_limits_sbss),
-                .limits = brcmf_iface_limits_sbss,
-       }
-};
-
 static const struct ieee80211_txrx_stypes
 brcmf_txrx_stypes[NUM_NL80211_IFTYPES] = {
        [NL80211_IFTYPE_STATION] = {
@@ -5750,6 +5724,136 @@ brcmf_txrx_stypes[NUM_NL80211_IFTYPES] = {
        }
 };
 
+/**
+ * brcmf_setup_ifmodes() - determine interface modes and combinations.
+ *
+ * @wiphy: wiphy object.
+ * @ifp: interface object needed for feat module api.
+ *
+ * The interface modes and combinations are determined dynamically here
+ * based on firmware functionality.
+ *
+ * no p2p and no mbss:
+ *
+ *     #STA <= 1, #AP <= 1, channels = 1, 2 total
+ *
+ * no p2p and mbss:
+ *
+ *     #STA <= 1, #AP <= 1, channels = 1, 2 total
+ *     #AP <= 4, matching BI, channels = 1, 4 total
+ *
+ * p2p, no mchan, and mbss:
+ *
+ *     #STA <= 1, #P2P-DEV <= 1, #{P2P-CL, P2P-GO} <= 1, channels = 1, 3 total
+ *     #STA <= 1, #P2P-DEV <= 1, #AP <= 1, #P2P-CL <= 1, channels = 1, 4 total
+ *     #AP <= 4, matching BI, channels = 1, 4 total
+ *
+ * p2p, mchan, and mbss:
+ *
+ *     #STA <= 1, #P2P-DEV <= 1, #{P2P-CL, P2P-GO} <= 1, channels = 2, 3 total
+ *     #STA <= 1, #P2P-DEV <= 1, #AP <= 1, #P2P-CL <= 1, channels = 1, 4 total
+ *     #AP <= 4, matching BI, channels = 1, 4 total
+ */
+static int brcmf_setup_ifmodes(struct wiphy *wiphy, struct brcmf_if *ifp)
+{
+       struct ieee80211_iface_combination *combo = NULL;
+       struct ieee80211_iface_limit *c0_limits = NULL;
+       struct ieee80211_iface_limit *p2p_limits = NULL;
+       struct ieee80211_iface_limit *mbss_limits = NULL;
+       bool mbss, p2p;
+       int i, c, n_combos;
+
+       mbss = brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MBSS);
+       p2p = brcmf_feat_is_enabled(ifp, BRCMF_FEAT_P2P);
+
+       n_combos = 1 + !!p2p + !!mbss;
+       combo = kcalloc(n_combos, sizeof(*combo), GFP_KERNEL);
+       if (!combo)
+               goto err;
+
+       c0_limits = kcalloc(p2p ? 3 : 2, sizeof(*c0_limits), GFP_KERNEL);
+       if (!c0_limits)
+               goto err;
+
+       if (p2p) {
+               p2p_limits = kcalloc(4, sizeof(*p2p_limits), GFP_KERNEL);
+               if (!p2p_limits)
+                       goto err;
+       }
+
+       if (mbss) {
+               mbss_limits = kcalloc(1, sizeof(*mbss_limits), GFP_KERNEL);
+               if (!mbss_limits)
+                       goto err;
+       }
+
+       wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
+                                BIT(NL80211_IFTYPE_ADHOC) |
+                                BIT(NL80211_IFTYPE_AP);
+
+       c = 0;
+       i = 0;
+       combo[c].num_different_channels = 1;
+       c0_limits[i].max = 1;
+       c0_limits[i++].types = BIT(NL80211_IFTYPE_STATION);
+       if (p2p) {
+               if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MCHAN))
+                       combo[c].num_different_channels = 2;
+               wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_CLIENT) |
+                                         BIT(NL80211_IFTYPE_P2P_GO) |
+                                         BIT(NL80211_IFTYPE_P2P_DEVICE);
+               c0_limits[i].max = 1;
+               c0_limits[i++].types = BIT(NL80211_IFTYPE_P2P_DEVICE);
+               c0_limits[i].max = 1;
+               c0_limits[i++].types = BIT(NL80211_IFTYPE_P2P_CLIENT) |
+                                      BIT(NL80211_IFTYPE_P2P_GO);
+       } else {
+               c0_limits[i].max = 1;
+               c0_limits[i++].types = BIT(NL80211_IFTYPE_AP);
+       }
+       combo[c].max_interfaces = i;
+       combo[c].n_limits = i;
+       combo[c].limits = c0_limits;
+
+       if (p2p) {
+               c++;
+               i = 0;
+               combo[c].num_different_channels = 1;
+               p2p_limits[i].max = 1;
+               p2p_limits[i++].types = BIT(NL80211_IFTYPE_STATION);
+               p2p_limits[i].max = 1;
+               p2p_limits[i++].types = BIT(NL80211_IFTYPE_AP);
+               p2p_limits[i].max = 1;
+               p2p_limits[i++].types = BIT(NL80211_IFTYPE_P2P_CLIENT);
+               p2p_limits[i].max = 1;
+               p2p_limits[i++].types = BIT(NL80211_IFTYPE_P2P_DEVICE);
+               combo[c].max_interfaces = i;
+               combo[c].n_limits = i;
+               combo[c].limits = p2p_limits;
+       }
+
+       if (mbss) {
+               c++;
+               combo[c].beacon_int_infra_match = true;
+               combo[c].num_different_channels = 1;
+               mbss_limits[0].max = 4;
+               mbss_limits[0].types = BIT(NL80211_IFTYPE_AP);
+               combo[c].max_interfaces = 4;
+               combo[c].n_limits = 1;
+               combo[c].limits = mbss_limits;
+       }
+       wiphy->n_iface_combinations = n_combos;
+       wiphy->iface_combinations = combo;
+       return 0;
+
+err:
+       kfree(c0_limits);
+       kfree(p2p_limits);
+       kfree(mbss_limits);
+       kfree(combo);
+       return -ENOMEM;
+}
+
 static void brcmf_wiphy_pno_params(struct wiphy *wiphy)
 {
        /* scheduled scan settings */
@@ -5779,28 +5883,40 @@ static void brcmf_wiphy_wowl_params(struct wiphy *wiphy)
 
 static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp)
 {
-       struct ieee80211_iface_combination ifc_combo;
+       struct brcmf_pub *drvr = ifp->drvr;
+       const struct ieee80211_iface_combination *combo;
+       struct ieee80211_supported_band *band;
+       u16 max_interfaces = 0;
+       __le32 bandlist[3];
+       u32 n_bands;
+       int err, i;
+
        wiphy->max_scan_ssids = WL_NUM_SCAN_MAX;
        wiphy->max_scan_ie_len = BRCMF_SCAN_IE_LEN_MAX;
        wiphy->max_num_pmkids = WL_NUM_PMKIDS_MAX;
-       wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
-                                BIT(NL80211_IFTYPE_ADHOC) |
-                                BIT(NL80211_IFTYPE_AP) |
-                                BIT(NL80211_IFTYPE_P2P_CLIENT) |
-                                BIT(NL80211_IFTYPE_P2P_GO) |
-                                BIT(NL80211_IFTYPE_P2P_DEVICE);
-       /* need VSDB firmware feature for concurrent channels */
-       ifc_combo = brcmf_iface_combos[0];
-       if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MCHAN))
-               ifc_combo.num_different_channels = 2;
-       if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MBSS)) {
-               ifc_combo.n_limits = ARRAY_SIZE(brcmf_iface_limits_mbss),
-               ifc_combo.limits = brcmf_iface_limits_mbss;
-       }
-       wiphy->iface_combinations = kmemdup(&ifc_combo,
-                                           sizeof(ifc_combo),
-                                           GFP_KERNEL);
-       wiphy->n_iface_combinations = ARRAY_SIZE(brcmf_iface_combos);
+
+       err = brcmf_setup_ifmodes(wiphy, ifp);
+       if (err)
+               return err;
+
+       for (i = 0, combo = wiphy->iface_combinations;
+            i < wiphy->n_iface_combinations; i++, combo++) {
+               max_interfaces = max(max_interfaces, combo->max_interfaces);
+       }
+
+       for (i = 0; i < max_interfaces && i < ARRAY_SIZE(drvr->addresses);
+            i++) {
+               u8 *addr = drvr->addresses[i].addr;
+
+               memcpy(addr, drvr->mac, ETH_ALEN);
+               if (i) {
+                       addr[0] |= BIT(1);
+                       addr[ETH_ALEN - 1] ^= i;
+               }
+       }
+       wiphy->addresses = drvr->addresses;
+       wiphy->n_addresses = i;
+
        wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
        wiphy->cipher_suites = __wl_cipher_suites;
        wiphy->n_cipher_suites = ARRAY_SIZE(__wl_cipher_suites);
@@ -5812,7 +5928,8 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp)
                wiphy->flags |= WIPHY_FLAG_SUPPORTS_FW_ROAM;
        wiphy->mgmt_stypes = brcmf_txrx_stypes;
        wiphy->max_remain_on_channel_duration = 5000;
-       brcmf_wiphy_pno_params(wiphy);
+       if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO))
+               brcmf_wiphy_pno_params(wiphy);
 
        /* vendor commands/events support */
        wiphy->vendor_commands = brcmf_vendor_cmds;
@@ -5821,7 +5938,52 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp)
        if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL))
                brcmf_wiphy_wowl_params(wiphy);
 
-       return brcmf_setup_wiphybands(wiphy);
+       err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BANDLIST, &bandlist,
+                                    sizeof(bandlist));
+       if (err) {
+               brcmf_err("could not obtain band info: err=%d\n", err);
+               return err;
+       }
+       /* first entry in bandlist is number of bands */
+       n_bands = le32_to_cpu(bandlist[0]);
+       for (i = 1; i <= n_bands && i < ARRAY_SIZE(bandlist); i++) {
+               if (bandlist[i] == cpu_to_le32(WLC_BAND_2G)) {
+                       band = kmemdup(&__wl_band_2ghz, sizeof(__wl_band_2ghz),
+                                      GFP_KERNEL);
+                       if (!band)
+                               return -ENOMEM;
+
+                       band->channels = kmemdup(&__wl_2ghz_channels,
+                                                sizeof(__wl_2ghz_channels),
+                                                GFP_KERNEL);
+                       if (!band->channels) {
+                               kfree(band);
+                               return -ENOMEM;
+                       }
+
+                       band->n_channels = ARRAY_SIZE(__wl_2ghz_channels);
+                       wiphy->bands[IEEE80211_BAND_2GHZ] = band;
+               }
+               if (bandlist[i] == cpu_to_le32(WLC_BAND_5G)) {
+                       band = kmemdup(&__wl_band_5ghz, sizeof(__wl_band_5ghz),
+                                      GFP_KERNEL);
+                       if (!band)
+                               return -ENOMEM;
+
+                       band->channels = kmemdup(&__wl_5ghz_channels,
+                                                sizeof(__wl_5ghz_channels),
+                                                GFP_KERNEL);
+                       if (!band->channels) {
+                               kfree(band);
+                               return -ENOMEM;
+                       }
+
+                       band->n_channels = ARRAY_SIZE(__wl_5ghz_channels);
+                       wiphy->bands[IEEE80211_BAND_5GHZ] = band;
+               }
+       }
+       err = brcmf_setup_wiphybands(wiphy);
+       return err;
 }
 
 static s32 brcmf_config_dongle(struct brcmf_cfg80211_info *cfg)
@@ -6007,11 +6169,24 @@ static void brcmf_cfg80211_reg_notifier(struct wiphy *wiphy,
        memset(&ccreq, 0, sizeof(ccreq));
        ccreq.rev = cpu_to_le32(-1);
        memcpy(ccreq.ccode, req->alpha2, sizeof(req->alpha2));
-       brcmf_fil_iovar_data_set(ifp, "country", &ccreq, sizeof(ccreq));
+       if (brcmf_fil_iovar_data_set(ifp, "country", &ccreq, sizeof(ccreq))) {
+               brcmf_err("firmware rejected country setting\n");
+               return;
+       }
+       brcmf_setup_wiphybands(wiphy);
 }
 
 static void brcmf_free_wiphy(struct wiphy *wiphy)
 {
+       int i;
+
+       if (!wiphy)
+               return;
+
+       if (wiphy->iface_combinations) {
+               for (i = 0; i < wiphy->n_iface_combinations; i++)
+                       kfree(wiphy->iface_combinations[i].limits);
+       }
        kfree(wiphy->iface_combinations);
        if (wiphy->bands[IEEE80211_BAND_2GHZ]) {
                kfree(wiphy->bands[IEEE80211_BAND_2GHZ]->channels);
@@ -6025,9 +6200,10 @@ static void brcmf_free_wiphy(struct wiphy *wiphy)
 }
 
 struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr,
-                                                 struct device *busdev)
+                                                 struct device *busdev,
+                                                 bool p2pdev_forced)
 {
-       struct net_device *ndev = drvr->iflist[0]->ndev;
+       struct net_device *ndev = brcmf_get_ifp(drvr, 0)->ndev;
        struct brcmf_cfg80211_info *cfg;
        struct wiphy *wiphy;
        struct brcmf_cfg80211_vif *vif;
@@ -6047,6 +6223,7 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr,
                brcmf_err("Could not allocate wiphy device\n");
                return NULL;
        }
+       memcpy(wiphy->perm_addr, drvr->mac, ETH_ALEN);
        set_wiphy_dev(wiphy, busdev);
 
        cfg = wiphy_priv(wiphy);
@@ -6115,8 +6292,19 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr,
                else
                        *cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
        }
+       /* p2p might require that "if-events" get processed by fweh. So
+        * activate the already registered event handlers now and activate
+        * the rest when initialization has completed. drvr->config needs to
+        * be assigned before activating events.
+        */
+       drvr->config = cfg;
+       err = brcmf_fweh_activate_events(ifp);
+       if (err) {
+               brcmf_err("FWEH activation failed (%d)\n", err);
+               goto wiphy_unreg_out;
+       }
 
-       err = brcmf_p2p_attach(cfg);
+       err = brcmf_p2p_attach(cfg, p2pdev_forced);
        if (err) {
                brcmf_err("P2P initilisation failed (%d)\n", err);
                goto wiphy_unreg_out;
@@ -6137,6 +6325,13 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr,
                                    brcmf_notify_tdls_peer_event);
        }
 
+       /* (re-) activate FWEH event handling */
+       err = brcmf_fweh_activate_events(ifp);
+       if (err) {
+               brcmf_err("FWEH activation failed (%d)\n", err);
+               goto wiphy_unreg_out;
+       }
+
        return cfg;
 
 wiphy_unreg_out:
@@ -6144,6 +6339,7 @@ wiphy_unreg_out:
 priv_out:
        wl_deinit_priv(cfg);
        brcmf_free_vif(vif);
+       ifp->vif = NULL;
 wiphy_out:
        brcmf_free_wiphy(wiphy);
        return NULL;
@@ -6154,10 +6350,8 @@ void brcmf_cfg80211_detach(struct brcmf_cfg80211_info *cfg)
        if (!cfg)
                return;
 
-       WARN_ON(!list_empty(&cfg->vif_list));
-       wiphy_unregister(cfg->wiphy);
        brcmf_btcoex_detach(cfg);
-       brcmf_p2p_detach(&cfg->p2p);
+       wiphy_unregister(cfg->wiphy);
        wl_deinit_priv(cfg);
        brcmf_free_wiphy(cfg->wiphy);
 }