/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
 /*
- * Copyright (C) 2012-2014, 2018-2019 Intel Corporation
+ * Copyright (C) 2012-2014, 2018-2020 Intel Corporation
  * Copyright (C) 2013-2015 Intel Mobile Communications GmbH
  * Copyright (C) 2016-2017 Intel Deutschland GmbH
  */
         */
        CHEST_COLLECTOR_FILTER_CONFIG_CMD = 0x14,
 
+       /**
+        * @MONITOR_NOTIF: Datapath monitoring notification, using
+        *      &struct iwl_datapath_monitor_notif
+        */
+       MONITOR_NOTIF = 0xF4,
+
        /**
         * @RX_NO_DATA_NOTIF: &struct iwl_rx_no_data
         */
        __le64 frame_types;
 } __packed; /* CHEST_COLLECTOR_FILTER_CMD_API_S_VER_1 */
 
+enum iwl_datapath_monitor_notif_type {
+       IWL_DP_MON_NOTIF_TYPE_EXT_CCA,
+};
+
+struct iwl_datapath_monitor_notif {
+       __le32 type;
+       u8 mac_id;
+       u8 reserved[3];
+} __packed; /* MONITOR_NTF_API_S_VER_1 */
+
 #endif /* __iwl_fw_api_datapath_h__ */
 
        mvmvif->he_ru_2mhz_block = !iter_data.tolerated;
 }
 
+static void iwl_mvm_reset_cca_40mhz_workaround(struct iwl_mvm *mvm,
+                                              struct ieee80211_vif *vif)
+{
+       struct ieee80211_supported_band *sband;
+       const struct ieee80211_sta_he_cap *he_cap;
+
+       if (vif->type != NL80211_IFTYPE_STATION)
+               return;
+
+       if (!mvm->cca_40mhz_workaround)
+               return;
+
+       /* decrement and check that we reached zero */
+       mvm->cca_40mhz_workaround--;
+       if (mvm->cca_40mhz_workaround)
+               return;
+
+       sband = mvm->hw->wiphy->bands[NL80211_BAND_2GHZ];
+
+       sband->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+
+       he_cap = ieee80211_get_he_iftype_cap(sband,
+                                            ieee80211_vif_type_p2p(vif));
+
+       if (he_cap) {
+               /* we know that ours is writable */
+               struct ieee80211_sta_he_cap *he = (void *)he_cap;
+
+               he->he_cap_elem.phy_cap_info[0] |=
+                       IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G;
+       }
+}
+
 static int iwl_mvm_mac_sta_state(struct ieee80211_hw *hw,
                                 struct ieee80211_vif *vif,
                                 struct ieee80211_sta *sta,
                 * No need to make sure deferred TX indication is off since the
                 * worker will already remove it if it was on
                 */
+
+               /*
+                * Additionally, reset the 40 MHz capability if we disconnected
+                * from the AP now.
+                */
+               iwl_mvm_reset_cca_40mhz_workaround(mvm, vif);
        }
 
        mutex_lock(&mvm->mutex);
 
        bool hw_registered;
        bool rfkill_safe_init_done;
 
+       u8 cca_40mhz_workaround;
+
        u32 ampdu_ref;
        bool ampdu_toggle;
 
                                     u32 size);
 void iwl_mvm_reorder_timer_expired(struct timer_list *t);
 struct ieee80211_vif *iwl_mvm_get_bss_vif(struct iwl_mvm *mvm);
+struct ieee80211_vif *iwl_mvm_get_vif_by_macid(struct iwl_mvm *mvm, u32 macid);
 bool iwl_mvm_is_vif_assoc(struct iwl_mvm *mvm);
 
 #define MVM_TCM_PERIOD_MSEC 500
 
                                       ~APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS);
 }
 
+static void iwl_mvm_rx_monitor_notif(struct iwl_mvm *mvm,
+                                    struct iwl_rx_cmd_buffer *rxb)
+{
+       struct iwl_rx_packet *pkt = rxb_addr(rxb);
+       struct iwl_datapath_monitor_notif *notif = (void *)pkt->data;
+       struct ieee80211_supported_band *sband;
+       const struct ieee80211_sta_he_cap *he_cap;
+       struct ieee80211_vif *vif;
+
+       if (notif->type != cpu_to_le32(IWL_DP_MON_NOTIF_TYPE_EXT_CCA))
+               return;
+
+       vif = iwl_mvm_get_vif_by_macid(mvm, notif->mac_id);
+       if (!vif || vif->type != NL80211_IFTYPE_STATION)
+               return;
+
+       if (!vif->bss_conf.chandef.chan ||
+           vif->bss_conf.chandef.chan->band != NL80211_BAND_2GHZ ||
+           vif->bss_conf.chandef.width < NL80211_CHAN_WIDTH_40)
+               return;
+
+       if (!vif->bss_conf.assoc)
+               return;
+
+       /* this shouldn't happen *again*, ignore it */
+       if (mvm->cca_40mhz_workaround)
+               return;
+
+       /*
+        * We'll decrement this on disconnect - so set to 2 since we'll
+        * still have to disconnect from the current AP first.
+        */
+       mvm->cca_40mhz_workaround = 2;
+
+       /*
+        * This capability manipulation isn't really ideal, but it's the
+        * easiest choice - otherwise we'd have to do some major changes
+        * in mac80211 to support this, which isn't worth it. This does
+        * mean that userspace may have outdated information, but that's
+        * actually not an issue at all.
+        */
+       sband = mvm->hw->wiphy->bands[NL80211_BAND_2GHZ];
+
+       WARN_ON(!sband->ht_cap.ht_supported);
+       WARN_ON(!(sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40));
+       sband->ht_cap.cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+
+       he_cap = ieee80211_get_he_iftype_cap(sband,
+                                            ieee80211_vif_type_p2p(vif));
+
+       if (he_cap) {
+               /* we know that ours is writable */
+               struct ieee80211_sta_he_cap *he = (void *)he_cap;
+
+               WARN_ON(!he->has_he);
+               WARN_ON(!(he->he_cap_elem.phy_cap_info[0] &
+                               IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G));
+               he->he_cap_elem.phy_cap_info[0] &=
+                       ~IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G;
+       }
+
+       ieee80211_disconnect(vif, true);
+}
+
 /**
  * enum iwl_rx_handler_context context for Rx handler
  * @RX_HANDLER_SYNC : this means that it will be called in the Rx path
        RX_HANDLER_GRP(MAC_CONF_GROUP, CHANNEL_SWITCH_NOA_NOTIF,
                       iwl_mvm_channel_switch_noa_notif,
                       RX_HANDLER_SYNC, struct iwl_channel_switch_noa_notif),
+       RX_HANDLER_GRP(DATA_PATH_GROUP, MONITOR_NOTIF,
+                      iwl_mvm_rx_monitor_notif, RX_HANDLER_ASYNC_LOCKED,
+                      struct iwl_datapath_monitor_notif),
 };
 #undef RX_HANDLER
 #undef RX_HANDLER_GRP
        HCMD_NAME(RFH_QUEUE_CONFIG_CMD),
        HCMD_NAME(TLC_MNG_CONFIG_CMD),
        HCMD_NAME(CHEST_COLLECTOR_FILTER_CONFIG_CMD),
+       HCMD_NAME(MONITOR_NOTIF),
        HCMD_NAME(STA_PM_NOTIF),
        HCMD_NAME(MU_GROUP_MGMT_NOTIF),
        HCMD_NAME(RX_QUEUES_NOTIFICATION),
 
        return bss_iter_data.vif;
 }
 
+struct iwl_bss_find_iter_data {
+       struct ieee80211_vif *vif;
+       u32 macid;
+};
+
+static void iwl_mvm_bss_find_iface_iterator(void *_data, u8 *mac,
+                                           struct ieee80211_vif *vif)
+{
+       struct iwl_bss_find_iter_data *data = _data;
+       struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
+
+       if (mvmvif->id == data->macid)
+               data->vif = vif;
+}
+
+struct ieee80211_vif *iwl_mvm_get_vif_by_macid(struct iwl_mvm *mvm, u32 macid)
+{
+       struct iwl_bss_find_iter_data data = {
+               .macid = macid,
+       };
+
+       lockdep_assert_held(&mvm->mutex);
+
+       ieee80211_iterate_active_interfaces_atomic(
+               mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
+               iwl_mvm_bss_find_iface_iterator, &data);
+
+       return data.vif;
+}
+
 struct iwl_sta_iter_data {
        bool assoc;
 };