* @IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE: extended DTS measurement
  * @IWL_UCODE_TLV_CAPA_SHORT_PM_TIMEOUTS: supports short PM timeouts
  * @IWL_UCODE_TLV_CAPA_BT_MPLUT_SUPPORT: supports bt-coex Multi-priority LUT
+ * @IWL_UCODE_TLV_CAPA_CSA_AND_TBTT_OFFLOAD: the firmware supports CSA
+ *     countdown offloading. Beacon notifications are not sent to the host.
+ *     The fw also offloads TBTT alignment.
  * @IWL_UCODE_TLV_CAPA_BEACON_ANT_SELECTION: firmware will decide on what
  *     antenna the beacon should be transmitted
  * @IWL_UCODE_TLV_CAPA_BEACON_STORING: firmware will store the latest beacon
        IWL_UCODE_TLV_CAPA_SHORT_PM_TIMEOUTS            = (__force iwl_ucode_tlv_capa_t)65,
        IWL_UCODE_TLV_CAPA_BT_MPLUT_SUPPORT             = (__force iwl_ucode_tlv_capa_t)67,
        IWL_UCODE_TLV_CAPA_MULTI_QUEUE_RX_SUPPORT       = (__force iwl_ucode_tlv_capa_t)68,
+       IWL_UCODE_TLV_CAPA_CSA_AND_TBTT_OFFLOAD         = (__force iwl_ucode_tlv_capa_t)70,
        IWL_UCODE_TLV_CAPA_BEACON_ANT_SELECTION         = (__force iwl_ucode_tlv_capa_t)71,
        IWL_UCODE_TLV_CAPA_BEACON_STORING               = (__force iwl_ucode_tlv_capa_t)72,
        IWL_UCODE_TLV_CAPA_LAR_SUPPORT_V2               = (__force iwl_ucode_tlv_capa_t)73,
 
        u8 reserved1;
 } __packed;
 
-/*
- * struct iwl_mac_beacon_cmd - beacon template command
+/**
+ * struct iwl_mac_beacon_cmd_v6 - beacon template command
  * @tx: the tx commands associated with the beacon frame
  * @template_id: currently equal to the mac context id of the coresponding
  *  mac.
  * @tim_size: the length of the tim IE
  * @frame: the template of the beacon frame
  */
+struct iwl_mac_beacon_cmd_v6 {
+       struct iwl_tx_cmd tx;
+       __le32 template_id;
+       __le32 tim_idx;
+       __le32 tim_size;
+       struct ieee80211_hdr frame[0];
+} __packed; /* BEACON_TEMPLATE_CMD_API_S_VER_6 */
+
+/**
+ * struct iwl_mac_beacon_cmd - beacon template command with offloaded CSA
+ * @tx: the tx commands associated with the beacon frame
+ * @template_id: currently equal to the mac context id of the coresponding
+ *  mac.
+ * @tim_idx: the offset of the tim IE in the beacon
+ * @tim_size: the length of the tim IE
+ * @ecsa_offset: offset to the ECSA IE if present
+ * @csa_offset: offset to the CSA IE if present
+ * @frame: the template of the beacon frame
+ */
 struct iwl_mac_beacon_cmd {
        struct iwl_tx_cmd tx;
        __le32 template_id;
        __le32 tim_idx;
        __le32 tim_size;
+       __le32 ecsa_offset;
+       __le32 csa_offset;
        struct ieee80211_hdr frame[0];
-} __packed;
+} __packed; /* BEACON_TEMPLATE_CMD_API_S_VER_7 */
 
 struct iwl_beacon_notif {
        struct iwl_mvm_tx_resp beacon_notify_hdr;
 
 enum iwl_mac_conf_subcmd_ids {
        LINK_QUALITY_MEASUREMENT_CMD = 0x1,
        LINK_QUALITY_MEASUREMENT_COMPLETE_NOTIF = 0xFE,
+       CHANNEL_SWITCH_NOA_NOTIF = 0xFF,
 };
 
 enum iwl_phy_ops_subcmd_ids {
 
        /* P2P GO Events */
        TE_P2P_GO_ASSOC_PROT,
-       TE_P2P_GO_REPETITIVE_NOA,
+       TE_P2P_GO_REPETITIVET_NOA,
        TE_P2P_GO_CT_WINDOW,
 
        /* WiDi Sync Events */
        __le32 reserved[3];
 } __packed; /* LQM_MEASUREMENT_COMPLETE_NTF_API_S_VER1 */
 
+/**
+ * Channel switch NOA notification
+ *
+ * @id_and_color: ID and color of the MAC
+ */
+struct iwl_channel_switch_noa_notif {
+       __le32 id_and_color;
+} __packed; /* CHANNEL_SWITCH_START_NTFY_API_S_VER_1 */
+
 #endif /* __fw_api_h__ */
 
 }
 
 static void iwl_mvm_mac_ctxt_set_tim(struct iwl_mvm *mvm,
-                                    struct iwl_mac_beacon_cmd *beacon_cmd,
+                                    struct iwl_mac_beacon_cmd_v6 *beacon_cmd,
                                     u8 *beacon, u32 frame_size)
 {
        u32 tim_idx;
        }
 }
 
+static u32 iwl_mvm_find_ie_offset(u8 *beacon, u8 eid, u32 frame_size)
+{
+       struct ieee80211_mgmt *mgmt = (void *)beacon;
+       const u8 *ie;
+
+       if (WARN_ON_ONCE(frame_size <= (mgmt->u.beacon.variable - beacon)))
+               return 0;
+
+       frame_size -= mgmt->u.beacon.variable - beacon;
+
+       ie = cfg80211_find_ie(eid, mgmt->u.beacon.variable, frame_size);
+       if (!ie)
+               return 0;
+
+       return ie - beacon;
+}
+
 static int iwl_mvm_mac_ctxt_send_beacon(struct iwl_mvm *mvm,
                                        struct ieee80211_vif *vif,
                                        struct sk_buff *beacon)
                .id = BEACON_TEMPLATE_CMD,
                .flags = CMD_ASYNC,
        };
-       struct iwl_mac_beacon_cmd beacon_cmd = {};
+       union {
+               struct iwl_mac_beacon_cmd_v6 beacon_cmd_v6;
+               struct iwl_mac_beacon_cmd beacon_cmd;
+       } u = {};
        struct ieee80211_tx_info *info;
        u32 beacon_skb_len;
        u32 rate, tx_flags;
 
        /* TODO: for now the beacon template id is set to be the mac context id.
         * Might be better to handle it as another resource ... */
-       beacon_cmd.template_id = cpu_to_le32((u32)mvmvif->id);
+       u.beacon_cmd_v6.template_id = cpu_to_le32((u32)mvmvif->id);
        info = IEEE80211_SKB_CB(beacon);
 
        /* Set up TX command fields */
-       beacon_cmd.tx.len = cpu_to_le16((u16)beacon_skb_len);
-       beacon_cmd.tx.sta_id = mvmvif->bcast_sta.sta_id;
-       beacon_cmd.tx.life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE);
+       u.beacon_cmd_v6.tx.len = cpu_to_le16((u16)beacon_skb_len);
+       u.beacon_cmd_v6.tx.sta_id = mvmvif->bcast_sta.sta_id;
+       u.beacon_cmd_v6.tx.life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE);
        tx_flags = TX_CMD_FLG_SEQ_CTL | TX_CMD_FLG_TSF;
        tx_flags |=
                iwl_mvm_bt_coex_tx_prio(mvm, (void *)beacon->data, info, 0) <<
                                                TX_CMD_FLG_BT_PRIO_POS;
-       beacon_cmd.tx.tx_flags = cpu_to_le32(tx_flags);
+       u.beacon_cmd_v6.tx.tx_flags = cpu_to_le32(tx_flags);
 
        if (!fw_has_capa(&mvm->fw->ucode_capa,
                         IWL_UCODE_TLV_CAPA_BEACON_ANT_SELECTION)) {
                                             mvm->mgmt_last_antenna_idx);
        }
 
-       beacon_cmd.tx.rate_n_flags =
+       u.beacon_cmd_v6.tx.rate_n_flags =
                cpu_to_le32(BIT(mvm->mgmt_last_antenna_idx) <<
                            RATE_MCS_ANT_POS);
 
                rate = IWL_FIRST_OFDM_RATE;
        } else {
                rate = IWL_FIRST_CCK_RATE;
-               beacon_cmd.tx.rate_n_flags |= cpu_to_le32(RATE_MCS_CCK_MSK);
+               u.beacon_cmd_v6.tx.rate_n_flags |=
+                                       cpu_to_le32(RATE_MCS_CCK_MSK);
        }
-       beacon_cmd.tx.rate_n_flags |=
+       u.beacon_cmd_v6.tx.rate_n_flags |=
                cpu_to_le32(iwl_mvm_mac80211_idx_to_hwrate(rate));
 
        /* Set up TX beacon command fields */
        if (vif->type == NL80211_IFTYPE_AP)
-               iwl_mvm_mac_ctxt_set_tim(mvm, &beacon_cmd,
+               iwl_mvm_mac_ctxt_set_tim(mvm, &u.beacon_cmd_v6,
                                         beacon->data,
                                         beacon_skb_len);
 
        /* Submit command */
-       cmd.len[0] = sizeof(beacon_cmd);
-       cmd.data[0] = &beacon_cmd;
+
+       if (fw_has_capa(&mvm->fw->ucode_capa,
+                       IWL_UCODE_TLV_CAPA_CSA_AND_TBTT_OFFLOAD)) {
+               u.beacon_cmd.csa_offset =
+                       cpu_to_le32(iwl_mvm_find_ie_offset(beacon->data,
+                                                   WLAN_EID_CHANNEL_SWITCH,
+                                                   beacon_skb_len));
+               u.beacon_cmd.ecsa_offset =
+                       cpu_to_le32(iwl_mvm_find_ie_offset(beacon->data,
+                                                   WLAN_EID_EXT_CHANSWITCH_ANN,
+                                                   beacon_skb_len));
+
+               cmd.len[0] = sizeof(u.beacon_cmd);
+       } else {
+               cmd.len[0] = sizeof(u.beacon_cmd_v6);
+       }
+
+       cmd.data[0] = &u;
        cmd.dataflags[0] = 0;
        cmd.len[1] = beacon_skb_len;
        cmd.data[1] = beacon->data;
        /* pass it as regular rx to mac80211 */
        ieee80211_rx_napi(mvm->hw, NULL, skb, NULL);
 }
+
+void iwl_mvm_channel_switch_noa_notif(struct iwl_mvm *mvm,
+                                     struct iwl_rx_cmd_buffer *rxb)
+{
+       struct iwl_rx_packet *pkt = rxb_addr(rxb);
+       struct iwl_channel_switch_noa_notif *notif = (void *)pkt->data;
+       struct ieee80211_vif *csa_vif;
+       struct iwl_mvm_vif *mvmvif;
+       int len = iwl_rx_packet_payload_len(pkt);
+       u32 id_n_color;
+
+       if (WARN_ON_ONCE(len < sizeof(*notif)))
+               return;
+
+       rcu_read_lock();
+
+       csa_vif = rcu_dereference(mvm->csa_vif);
+       if (WARN_ON(!csa_vif || !csa_vif->csa_active))
+               goto out_unlock;
+
+       id_n_color = le32_to_cpu(notif->id_and_color);
+
+       mvmvif = iwl_mvm_vif_from_mac80211(csa_vif);
+       if (WARN(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color) != id_n_color,
+                "channel switch noa notification on unexpected vif (csa_vif=%d, notif=%d)",
+                FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color), id_n_color))
+               goto out_unlock;
+
+       IWL_DEBUG_INFO(mvm, "Channel Switch Started Notification\n");
+
+       queue_delayed_work(system_wq, &mvm->cs_tx_unblock_dwork,
+                          msecs_to_jiffies(IWL_MVM_CS_UNBLOCK_TX_TIMEOUT *
+                                           csa_vif->bss_conf.beacon_int));
+
+       ieee80211_csa_finish(csa_vif);
+
+       rcu_read_unlock();
+
+       RCU_INIT_POINTER(mvm->csa_vif, NULL);
+
+       return;
+
+out_unlock:
+       rcu_read_unlock();
+}
 
        flush_work(&mvm->async_handlers_wk);
        flush_work(&mvm->add_stream_wk);
        cancel_delayed_work_sync(&mvm->fw_dump_wk);
+       cancel_delayed_work_sync(&mvm->cs_tx_unblock_dwork);
        iwl_mvm_free_fw_dump_desc(mvm);
 
        mutex_lock(&mvm->mutex);
                        goto out_unlock;
                }
 
+               /* we still didn't unblock tx. prevent new CS meanwhile */
+               if (rcu_dereference_protected(mvm->csa_tx_blocked_vif,
+                                             lockdep_is_held(&mvm->mutex))) {
+                       ret = -EBUSY;
+                       goto out_unlock;
+               }
+
                rcu_assign_pointer(mvm->csa_vif, vif);
 
                if (WARN_ONCE(mvmvif->csa_countdown,
                        goto out_unlock;
                }
 
+               mvmvif->csa_target_freq = chsw->chandef.chan->center_freq;
+
                break;
        case NL80211_IFTYPE_STATION:
                if (mvmvif->lqm_active)
 
        /* Indicates that CSA countdown may be started */
        bool csa_countdown;
        bool csa_failed;
+       u16 csa_target_freq;
 
        /* TCP Checksum Offload */
        netdev_features_t features;
         * clients.
         */
        bool drop_bcn_ap_mode;
+
+       struct delayed_work cs_tx_unblock_dwork;
 };
 
 /* Extract MVM priv from op_mode and _hw */
                                    struct ieee80211_vif *vif);
 unsigned long iwl_mvm_get_used_hw_queues(struct iwl_mvm *mvm,
                                         struct ieee80211_vif *exclude_vif);
+void iwl_mvm_channel_switch_noa_notif(struct iwl_mvm *mvm,
+                                     struct iwl_rx_cmd_buffer *rxb);
 /* Bindings */
 int iwl_mvm_binding_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif);
 int iwl_mvm_binding_remove_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif);
 
 static const struct iwl_hcmd_names iwl_mvm_mac_conf_names[] = {
        HCMD_NAME(LINK_QUALITY_MEASUREMENT_CMD),
        HCMD_NAME(LINK_QUALITY_MEASUREMENT_COMPLETE_NOTIF),
+       HCMD_NAME(CHANNEL_SWITCH_NOA_NOTIF),
 };
 
 /* Please keep this array *SORTED* by hex value.
 
 static void iwl_mvm_fw_error_dump_wk(struct work_struct *work);
 
+static void iwl_mvm_tx_unblock_dwork(struct work_struct *work)
+{
+       struct iwl_mvm *mvm =
+               container_of(work, struct iwl_mvm, cs_tx_unblock_dwork.work);
+       struct ieee80211_vif *tx_blocked_vif;
+       struct iwl_mvm_vif *mvmvif;
+
+       mutex_lock(&mvm->mutex);
+
+       tx_blocked_vif =
+               rcu_dereference_protected(mvm->csa_tx_blocked_vif,
+                                         lockdep_is_held(&mvm->mutex));
+
+       if (!tx_blocked_vif)
+               goto unlock;
+
+       mvmvif = iwl_mvm_vif_from_mac80211(tx_blocked_vif);
+       iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, false);
+       RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL);
+unlock:
+       mutex_unlock(&mvm->mutex);
+}
+
 static struct iwl_op_mode *
 iwl_op_mode_mvm_start(struct iwl_trans *trans, const struct iwl_cfg *cfg,
                      const struct iwl_fw *fw, struct dentry *dbgfs_dir)
 
        SET_IEEE80211_DEV(mvm->hw, mvm->trans->dev);
 
+       INIT_DELAYED_WORK(&mvm->cs_tx_unblock_dwork, iwl_mvm_tx_unblock_dwork);
+
        /*
         * Populate the state variables that the transport layer needs
         * to know about.
 
 
        if (sta) {
                struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
+               struct ieee80211_vif *tx_blocked_vif =
+                       rcu_dereference(mvm->csa_tx_blocked_vif);
 
                /* We have tx blocked stations (with CS bit). If we heard
                 * frames from a blocked station on a new channel we can
                 * TX to it again.
                 */
-               if (unlikely(mvm->csa_tx_block_bcn_timeout))
-                       iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, false);
+               if (unlikely(tx_blocked_vif) &&
+                   mvmsta->vif == tx_blocked_vif) {
+                       struct iwl_mvm_vif *mvmvif =
+                               iwl_mvm_vif_from_mac80211(tx_blocked_vif);
+
+                       if (mvmvif->csa_target_freq == rx_status->freq)
+                               iwl_mvm_sta_modify_disable_tx_ap(mvm, sta,
+                                                                false);
+               }
 
                rs_update_last_rssi(mvm, &mvmsta->lq_sta, rx_status);
 
 
 
        if (sta) {
                struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
+               struct ieee80211_vif *tx_blocked_vif =
+                       rcu_dereference(mvm->csa_tx_blocked_vif);
                u8 baid = (u8)((le32_to_cpu(desc->reorder_data) &
                               IWL_RX_MPDU_REORDER_BAID_MASK) >>
                               IWL_RX_MPDU_REORDER_BAID_SHIFT);
                 * frames from a blocked station on a new channel we can
                 * TX to it again.
                 */
-               if (unlikely(mvm->csa_tx_block_bcn_timeout))
-                       iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, false);
+               if (unlikely(tx_blocked_vif) &&
+                   tx_blocked_vif == mvmsta->vif) {
+                       struct iwl_mvm_vif *mvmvif =
+                               iwl_mvm_vif_from_mac80211(tx_blocked_vif);
+
+                       if (mvmvif->csa_target_freq == rx_status->freq)
+                               iwl_mvm_sta_modify_disable_tx_ap(mvm, sta,
+                                                                false);
+               }
 
                rs_update_last_rssi(mvm, &mvmsta->lq_sta, rx_status);