diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 11d5610d2ad5..38b2e99cf153 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1642,6 +1642,10 @@ enum ieee80211_vif_flags {
  * @txq: the multicast data TX queue (if driver uses the TXQ abstraction)
  * @txqs_stopped: per AC flag to indicate that intermediate TXQs are stopped,
  *	protected by fq->lock.
+ * @cca_active: marks whether a color change is going on. Internally it is
+ *	write-protected by sdata_lock and local->mtx so holding either is fine
+ *	for read access.
+ * @cca_color: the color that we will have after the change.
  */
 struct ieee80211_vif {
 	enum nl80211_iftype type;
@@ -1669,6 +1673,9 @@ struct ieee80211_vif {
 
 	bool txqs_stopped[IEEE80211_NUM_ACS];
 
+	bool cca_active;
+	u8 cca_color;
+
 	/* must be last */
 	u8 drv_priv[] __aligned(sizeof(void *));
 };
@@ -4737,6 +4744,11 @@ void ieee80211_report_low_ack(struct ieee80211_sta *sta, u32 num_packets);
 
 #define IEEE80211_MAX_CSA_COUNTERS_NUM 2
 
+enum mac80211_counter {
+	MAC80211_COUNTER_CSA = 0,
+	MAC80211_COUNTER_CCA,
+};
+
 /**
  * struct ieee80211_mutable_offsets - mutable beacon offsets
  * @tim_offset: position of TIM element
@@ -4744,12 +4756,15 @@ void ieee80211_report_low_ack(struct ieee80211_sta *sta, u32 num_packets);
  * @csa_counter_offs: array of IEEE80211_MAX_CSA_COUNTERS_NUM offsets
  *	to CSA counters.  This array can contain zero values which
  *	should be ignored.
+ * @cca_counter_off: offset to the cca counter. zero values should be
+ *	ignored.
  */
 struct ieee80211_mutable_offsets {
 	u16 tim_offset;
 	u16 tim_length;
 
 	u16 csa_counter_offs[IEEE80211_MAX_CSA_COUNTERS_NUM];
+	u16 cca_counter_off;
 };
 
 /**
@@ -4862,6 +4877,38 @@ void ieee80211_csa_finish(struct ieee80211_vif *vif);
  */
 bool ieee80211_csa_is_complete(struct ieee80211_vif *vif);
 
+/**
+ * ieee80211_cca_update_counter - request mac80211 to decrement the cca counter
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ *
+ * The cca counter should be updated after each beacon transmission.
+ * This function is called implicitly when
+ * ieee80211_beacon_get/ieee80211_beacon_get_tim are called, however if the
+ * beacon frames are generated by the device, the driver should call this
+ * function after each beacon transmission to sync mac80211's cca counters.
+ *
+ * Return: new csa counter value
+ */
+
+u8 ieee80211_cca_update_counter(struct ieee80211_vif *vif);
+
+/**
+ * ieee80211_cca_finish - notify mac80211 about color change
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ *
+ * After a color change announcement was scheduled and the counter in this
+ * announcement hits 1, this function must be called by the driver to
+ * notify mac80211 that the color can be changed
+ */
+void ieee80211_cca_finish(struct ieee80211_vif *vif);
+
+/**
+ * ieee80211_cca_is_complete - find out if counters reached 1
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ *
+ * This function returns whether the color change counters reached zero.
+ */
+bool ieee80211_cca_is_complete(struct ieee80211_vif *vif);
 
 /**
  * ieee80211_proberesp_get - retrieve a Probe Response template
@@ -6558,4 +6605,15 @@ u32 ieee80211_calc_tx_airtime(struct ieee80211_hw *hw,
  */
 bool ieee80211_set_hw_80211_encap(struct ieee80211_vif *vif, bool enable);
 
+/**
+ * ieeee80211_obss_color_collision_notify notify userland about a BSS color
+ * collision.
+ *
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ * @color_bitmap: a 64 bit bitmap representing the colors that the local BSS is
+ *	aware of.
+ */
+void
+ieeee80211_obss_color_collision_notify(struct ieee80211_vif *vif,
+				       u64 color_bitmap);
 #endif /* MAC80211_H */
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 9b360544ad6f..4ec6bc7473e8 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -809,7 +809,8 @@ static int ieee80211_set_monitor_channel(struct wiphy *wiphy,
 
 static int ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata,
 				    const u8 *resp, size_t resp_len,
-				    const struct ieee80211_csa_settings *csa)
+				    const struct ieee80211_csa_settings *csa,
+				    const struct ieee80211_cca_settings *cca)
 {
 	struct probe_resp *new, *old;
 
@@ -829,6 +830,8 @@ static int ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata,
 		memcpy(new->csa_counter_offsets, csa->counter_offsets_presp,
 		       csa->n_counter_offsets_presp *
 		       sizeof(new->csa_counter_offsets[0]));
+	if (cca)
+		new->cca_counter_offset = cca->counter_offset_presp;
 
 	rcu_assign_pointer(sdata->u.ap.probe_resp, new);
 	if (old)
@@ -881,7 +884,8 @@ static int ieee80211_set_ftm_responder_params(
 
 static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
 				   struct cfg80211_beacon_data *params,
-				   const struct ieee80211_csa_settings *csa)
+				   const struct ieee80211_csa_settings *csa,
+				   const struct ieee80211_cca_settings *cca)
 {
 	struct beacon_data *new, *old;
 	int new_head_len, new_tail_len;
@@ -932,6 +936,11 @@ static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
 		       sizeof(new->csa_counter_offsets[0]));
 	}
 
+	if (cca) {
+		new->cca_current_counter = cca->count;
+		new->cca_counter_offset = cca->counter_offset_beacon;
+	}
+
 	/* copy in head */
 	if (params->head)
 		memcpy(new->head, params->head, new_head_len);
@@ -946,7 +955,7 @@ static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
 			memcpy(new->tail, old->tail, new_tail_len);
 
 	err = ieee80211_set_probe_resp(sdata, params->probe_resp,
-				       params->probe_resp_len, csa);
+				       params->probe_resp_len, csa, cca);
 	if (err < 0) {
 		kfree(new);
 		return err;
@@ -1096,7 +1105,7 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
 		}
 	}
 
-	err = ieee80211_assign_beacon(sdata, &params->beacon, NULL);
+	err = ieee80211_assign_beacon(sdata, &params->beacon, NULL, NULL);
 	if (err < 0) {
 		ieee80211_vif_release_channel(sdata);
 		return err;
@@ -1137,14 +1146,14 @@ static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev,
 	/* don't allow changing the beacon while CSA is in place - offset
 	 * of channel switch counter may change
 	 */
-	if (sdata->vif.csa_active)
+	if (sdata->vif.csa_active || sdata->vif.cca_active)
 		return -EBUSY;
 
 	old = sdata_dereference(sdata->u.ap.beacon, sdata);
 	if (!old)
 		return -ENOENT;
 
-	err = ieee80211_assign_beacon(sdata, params, NULL);
+	err = ieee80211_assign_beacon(sdata, params, NULL, NULL);
 	if (err < 0)
 		return err;
 	ieee80211_bss_info_change_notify(sdata, err);
@@ -3023,7 +3032,7 @@ static int ieee80211_set_after_csa_beacon(struct ieee80211_sub_if_data *sdata,
 	switch (sdata->vif.type) {
 	case NL80211_IFTYPE_AP:
 		err = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon,
-					      NULL);
+					      NULL, NULL);
 		kfree(sdata->u.ap.next_beacon);
 		sdata->u.ap.next_beacon = NULL;
 
@@ -3189,7 +3198,7 @@ static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata,
 		csa.n_counter_offsets_presp = params->n_counter_offsets_presp;
 		csa.count = params->count;
 
-		err = ieee80211_assign_beacon(sdata, &params->beacon_csa, &csa);
+		err = ieee80211_assign_beacon(sdata, &params->beacon_csa, &csa, NULL);
 		if (err < 0) {
 			kfree(sdata->u.ap.next_beacon);
 			return err;
@@ -3277,6 +3286,14 @@ static int ieee80211_set_csa_beacon(struct ieee80211_sub_if_data *sdata,
 	return 0;
 }
 
+static void ieee80211_cca_abort(struct ieee80211_sub_if_data  *sdata)
+{
+	sdata->vif.cca_active = false;
+	kfree(sdata->u.ap.next_beacon);
+	sdata->u.ap.next_beacon = NULL;
+	cfg80211_color_change_aborted_notify(sdata->dev);
+}
+
 static int
 __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 			   struct cfg80211_csa_settings *params)
@@ -3345,6 +3362,10 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 		goto out;
 	}
 
+	/* if there is a CCA in progress, abort it */
+	if (sdata->vif.cca_active)
+		ieee80211_cca_abort(sdata);
+
 	err = ieee80211_set_csa_beacon(sdata, params, &changed);
 	if (err) {
 		ieee80211_vif_unreserve_chanctx(sdata);
@@ -3985,6 +4006,193 @@ static int ieee80211_reset_tid_config(struct wiphy *wiphy,
 	return ret;
 }
 
+static int ieee80211_set_after_cca_beacon(struct ieee80211_sub_if_data *sdata,
+					  u32 *changed)
+{
+	int err;
+
+	switch (sdata->vif.type) {
+	case NL80211_IFTYPE_AP:
+		err = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon,
+					      NULL, NULL);
+		kfree(sdata->u.ap.next_beacon);
+		sdata->u.ap.next_beacon = NULL;
+
+		if (err < 0)
+			return err;
+		*changed |= err;
+		break;
+	default:
+		WARN_ON(1);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int ieee80211_set_cca_beacon(struct ieee80211_sub_if_data *sdata,
+				    struct cfg80211_cca_settings *params,
+				    u32 *changed)
+{
+	struct ieee80211_cca_settings cca = {};
+	int err;
+
+	switch (sdata->vif.type) {
+	case NL80211_IFTYPE_AP:
+		sdata->u.ap.next_beacon =
+			cfg80211_beacon_dup(&params->beacon_after);
+		if (!sdata->u.ap.next_beacon)
+			return -ENOMEM;
+
+		if (params->count <= 1)
+			break;
+
+		cca.counter_offset_beacon = params->counter_offset_beacon;
+		cca.counter_offset_presp = params->counter_offset_presp;
+		cca.count = params->count;
+
+		err = ieee80211_assign_beacon(sdata, &params->beacon_cca, NULL, &cca);
+		if (err < 0) {
+			kfree(sdata->u.ap.next_beacon);
+			return err;
+		}
+		*changed |= err;
+
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int ieee80211_cca_finalize(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_local *local = sdata->local;
+	u32 changed = 0;
+	int err;
+
+	sdata_assert_lock(sdata);
+	lockdep_assert_held(&local->mtx);
+
+	sdata->vif.cca_active = false;
+
+	err = ieee80211_set_after_cca_beacon(sdata, &changed);
+	if (err) {
+		cfg80211_color_change_aborted_notify(sdata->dev);
+		return err;
+	}
+
+	sdata->vif.bss_conf.he_bss_color.color = sdata->vif.cca_color;
+	sdata->vif.bss_conf.he_bss_color.disabled = 0;
+	changed |= BSS_CHANGED_HE_BSS_COLOR;
+
+	ieee80211_bss_info_change_notify(sdata, changed);
+
+	cfg80211_color_change_notify(sdata->dev);
+
+	return 0;
+}
+
+void ieee80211_cca_finalize_work(struct work_struct *work)
+{
+	struct ieee80211_sub_if_data *sdata =
+		container_of(work, struct ieee80211_sub_if_data,
+			     cca_finalize_work);
+	struct ieee80211_local *local = sdata->local;
+
+	sdata_lock(sdata);
+	mutex_lock(&local->mtx);
+
+	/* AP might have been stopped while waiting for the lock. */
+	if (!sdata->vif.cca_active)
+		goto unlock;
+
+	if (!ieee80211_sdata_running(sdata))
+		goto unlock;
+
+	ieee80211_cca_finalize(sdata);
+
+unlock:
+	mutex_unlock(&local->mtx);
+	sdata_unlock(sdata);
+}
+
+void ieee80211_cca_finish(struct ieee80211_vif *vif)
+{
+	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+
+	ieee80211_queue_work(&sdata->local->hw,
+			     &sdata->cca_finalize_work);
+}
+EXPORT_SYMBOL_GPL(ieee80211_cca_finish);
+
+void
+ieeee80211_obss_color_collision_notify(struct ieee80211_vif *vif,
+				       u64 color_bitmap)
+{
+	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+
+	if (sdata->vif.cca_active || sdata->vif.csa_active)
+		return;
+
+	cfg80211_obss_color_collision_notify(sdata->dev, color_bitmap);
+}
+EXPORT_SYMBOL_GPL(ieeee80211_obss_color_collision_notify);
+
+static int
+__ieee80211_color_change(struct wiphy *wiphy, struct net_device *dev,
+			 struct cfg80211_cca_settings *params)
+{
+	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+	struct ieee80211_local *local = sdata->local;
+	u32 changed = 0;
+	int err;
+
+	sdata_assert_lock(sdata);
+	lockdep_assert_held(&local->mtx);
+
+	/* don't allow another color change if one is already active or if csa
+	 * is active
+	 */
+	if (sdata->vif.cca_active || sdata->vif.csa_active)
+		return -EBUSY;
+
+	err = ieee80211_set_cca_beacon(sdata, params, &changed);
+	if (err)
+		return err;
+
+	sdata->vif.cca_active = true;
+	sdata->vif.cca_color = params->color;
+
+	cfg80211_color_change_started_notify(sdata->dev, params->count);
+
+	if (changed) {
+		sdata->vif.bss_conf.he_bss_color.disabled = 1;
+		changed |= BSS_CHANGED_HE_BSS_COLOR;
+		ieee80211_bss_info_change_notify(sdata, changed);
+	} else {
+		/* if the beacon didn't change, we can finalize immediately */
+		ieee80211_cca_finalize(sdata);
+	}
+
+	return 0;
+}
+
+static int ieee80211_color_change(struct wiphy *wiphy, struct net_device *dev,
+				  struct cfg80211_cca_settings *params)
+{
+	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+	struct ieee80211_local *local = sdata->local;
+	int err;
+
+	mutex_lock(&local->mtx);
+	err = __ieee80211_color_change(wiphy, dev, params);
+	mutex_unlock(&local->mtx);
+
+	return err;
+}
+
 const struct cfg80211_ops mac80211_config_ops = {
 	.add_virtual_intf = ieee80211_add_iface,
 	.del_virtual_intf = ieee80211_del_iface,
@@ -4088,4 +4296,5 @@ const struct cfg80211_ops mac80211_config_ops = {
 	.probe_mesh_link = ieee80211_probe_mesh_link,
 	.set_tid_config = ieee80211_set_tid_config,
 	.reset_tid_config = ieee80211_reset_tid_config,
+	.color_change = ieee80211_color_change,
 };
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index ec1a71ac65f2..2059f9fc9a25 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -256,12 +256,21 @@ struct ieee80211_csa_settings {
 	u8 count;
 };
 
+struct ieee80211_cca_settings {
+	u16 counter_offset_beacon;
+	u16 counter_offset_presp;
+
+	u8 count;
+};
+
 struct beacon_data {
 	u8 *head, *tail;
 	int head_len, tail_len;
 	struct ieee80211_meshconf_ie *meshconf;
 	u16 csa_counter_offsets[IEEE80211_MAX_CSA_COUNTERS_NUM];
 	u8 csa_current_counter;
+	u16 cca_counter_offset;
+	u8 cca_current_counter;
 	struct rcu_head rcu_head;
 };
 
@@ -269,6 +278,7 @@ struct probe_resp {
 	struct rcu_head rcu_head;
 	int len;
 	u16 csa_counter_offsets[IEEE80211_MAX_CSA_COUNTERS_NUM];
+	u16 cca_counter_offset;
 	u8 data[];
 };
 
@@ -924,6 +934,8 @@ struct ieee80211_sub_if_data {
 	bool csa_block_tx; /* write-protected by sdata_lock and local->mtx */
 	struct cfg80211_chan_def csa_chandef;
 
+	struct work_struct cca_finalize_work;
+
 	struct list_head assigned_chanctx_list; /* protected by chanctx_mtx */
 	struct list_head reserved_chanctx_list; /* protected by chanctx_mtx */
 
@@ -1738,6 +1750,9 @@ void ieee80211_csa_finalize_work(struct work_struct *work);
 int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 			     struct cfg80211_csa_settings *params);
 
+/* color change handling */
+void ieee80211_cca_finalize_work(struct work_struct *work);
+
 /* interface handling */
 #define MAC80211_SUPPORTED_FEATURES_TX	(NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | \
 					 NETIF_F_HW_CSUM | NETIF_F_SG | \
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index f900c84fb40f..e0bd423360a7 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -912,6 +912,8 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
 				   GFP_KERNEL);
 	}
 
+	cancel_work_sync(&sdata->cca_finalize_work);
+
 	/* APs need special treatment */
 	if (sdata->vif.type == NL80211_IFTYPE_AP) {
 		struct ieee80211_sub_if_data *vlan, *tmpsdata;
@@ -1489,6 +1491,7 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
 	INIT_WORK(&sdata->work, ieee80211_iface_work);
 	INIT_WORK(&sdata->recalc_smps, ieee80211_recalc_smps_work);
 	INIT_WORK(&sdata->csa_finalize_work, ieee80211_csa_finalize_work);
+	INIT_WORK(&sdata->cca_finalize_work, ieee80211_cca_finalize_work);
 	INIT_LIST_HEAD(&sdata->assigned_chanctx_list);
 	INIT_LIST_HEAD(&sdata->reserved_chanctx_list);
 
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index e9ce658141f5..2668fa8c0e62 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -4537,14 +4537,16 @@ static int ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata,
 	return 0;
 }
 
-static void ieee80211_set_csa(struct ieee80211_sub_if_data *sdata,
-			      struct beacon_data *beacon)
+static void ieee80211_set_beacon_counters(struct ieee80211_sub_if_data *sdata,
+					  struct beacon_data *beacon,
+					  enum mac80211_counter type)
 {
+	u16 *bcn_offsets, *resp_offsets;
 	struct probe_resp *resp;
 	u8 *beacon_data;
 	size_t beacon_data_len;
 	int i;
-	u8 count = beacon->csa_current_counter;
+	u8 count, max_count = 1;
 
 	switch (sdata->vif.type) {
 	case NL80211_IFTYPE_AP:
@@ -4564,25 +4566,50 @@ static void ieee80211_set_csa(struct ieee80211_sub_if_data *sdata,
 	}
 
 	rcu_read_lock();
-	for (i = 0; i < IEEE80211_MAX_CSA_COUNTERS_NUM; ++i) {
-		resp = rcu_dereference(sdata->u.ap.probe_resp);
+	resp = rcu_dereference(sdata->u.ap.probe_resp);
+
+	switch (type) {
+	case MAC80211_COUNTER_CSA:
+		bcn_offsets = beacon->csa_counter_offsets;
+		resp_offsets = resp->csa_counter_offsets;
+		count = beacon->csa_current_counter;
+		max_count = IEEE80211_MAX_CSA_COUNTERS_NUM;
+		break;
+	case MAC80211_COUNTER_CCA:
+		bcn_offsets = &beacon->cca_counter_offset;
+		resp_offsets = &resp->cca_counter_offset;
+		count = beacon->cca_current_counter;
+		break;
+	}
 
-		if (beacon->csa_counter_offsets[i]) {
-			if (WARN_ON_ONCE(beacon->csa_counter_offsets[i] >=
-					 beacon_data_len)) {
+	for (i = 0; i < max_count; ++i) {
+		if (bcn_offsets[i]) {
+			if (WARN_ON_ONCE(bcn_offsets[i] >= beacon_data_len)) {
 				rcu_read_unlock();
 				return;
 			}
 
-			beacon_data[beacon->csa_counter_offsets[i]] = count;
+			beacon_data[bcn_offsets[i]] = count;
 		}
 
 		if (sdata->vif.type == NL80211_IFTYPE_AP && resp)
-			resp->data[resp->csa_counter_offsets[i]] = count;
+			resp->data[resp_offsets[i]] = count;
 	}
 	rcu_read_unlock();
 }
 
+static void ieee80211_set_csa(struct ieee80211_sub_if_data *sdata,
+			      struct beacon_data *beacon)
+{
+	ieee80211_set_beacon_counters(sdata, beacon, MAC80211_COUNTER_CSA);
+}
+
+static void ieee80211_set_cca(struct ieee80211_sub_if_data *sdata,
+			      struct beacon_data *beacon)
+{
+	ieee80211_set_beacon_counters(sdata, beacon, MAC80211_COUNTER_CCA);
+}
+
 static u8 __ieee80211_csa_update_counter(struct beacon_data *beacon)
 {
 	beacon->csa_current_counter--;
@@ -4593,7 +4620,18 @@ static u8 __ieee80211_csa_update_counter(struct beacon_data *beacon)
 	return beacon->csa_current_counter;
 }
 
-u8 ieee80211_csa_update_counter(struct ieee80211_vif *vif)
+static u8 __ieee80211_cca_update_counter(struct beacon_data *beacon)
+{
+	beacon->cca_current_counter--;
+
+	/* the counter should never reach 0 */
+	WARN_ON_ONCE(!beacon->cca_current_counter);
+
+	return beacon->cca_current_counter;
+}
+
+static u8 ieee80211_beacon_update_counter(struct ieee80211_vif *vif,
+					  enum mac80211_counter type)
 {
 	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
 	struct beacon_data *beacon = NULL;
@@ -4611,14 +4649,32 @@ u8 ieee80211_csa_update_counter(struct ieee80211_vif *vif)
 	if (!beacon)
 		goto unlock;
 
-	count = __ieee80211_csa_update_counter(beacon);
+	switch (type) {
+	case MAC80211_COUNTER_CSA:
+		count = __ieee80211_csa_update_counter(beacon);
+		break;
+	case MAC80211_COUNTER_CCA:
+		count = __ieee80211_cca_update_counter(beacon);
+		break;
+	}
 
 unlock:
 	rcu_read_unlock();
 	return count;
 }
+
+u8 ieee80211_csa_update_counter(struct ieee80211_vif *vif)
+{
+	return ieee80211_beacon_update_counter(vif, MAC80211_COUNTER_CSA);
+}
 EXPORT_SYMBOL(ieee80211_csa_update_counter);
 
+u8 ieee80211_cca_update_counter(struct ieee80211_vif *vif)
+{
+	return ieee80211_beacon_update_counter(vif, MAC80211_COUNTER_CCA);
+}
+EXPORT_SYMBOL(ieee80211_cca_update_counter);
+
 void ieee80211_csa_set_counter(struct ieee80211_vif *vif, u8 counter)
 {
 	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
@@ -4644,7 +4700,8 @@ void ieee80211_csa_set_counter(struct ieee80211_vif *vif, u8 counter)
 }
 EXPORT_SYMBOL(ieee80211_csa_set_counter);
 
-bool ieee80211_csa_is_complete(struct ieee80211_vif *vif)
+static bool ieee80211_beacon_counter_is_complete(struct ieee80211_vif *vif,
+						 enum mac80211_counter type)
 {
 	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
 	struct beacon_data *beacon = NULL;
@@ -4687,21 +4744,46 @@ bool ieee80211_csa_is_complete(struct ieee80211_vif *vif)
 		goto out;
 	}
 
-	if (!beacon->csa_counter_offsets[0])
-		goto out;
+	switch (type) {
+	case MAC80211_COUNTER_CSA:
+		if (!beacon->csa_counter_offsets[0])
+			break;
 
-	if (WARN_ON_ONCE(beacon->csa_counter_offsets[0] > beacon_data_len))
-		goto out;
+		if (WARN_ON_ONCE(beacon->csa_counter_offsets[0] > beacon_data_len))
+			break;
+
+		if (beacon_data[beacon->csa_counter_offsets[0]] == 1)
+			ret = true;
+		break;
+	case MAC80211_COUNTER_CCA:
+		if (!beacon->cca_counter_offset)
+			break;
+
+		if (WARN_ON_ONCE(beacon->cca_counter_offset > beacon_data_len))
+			break;
 
-	if (beacon_data[beacon->csa_counter_offsets[0]] == 1)
-		ret = true;
+		if (beacon_data[beacon->cca_counter_offset] == 1)
+			ret = true;
+		break;
+	}
  out:
 	rcu_read_unlock();
 
 	return ret;
 }
+
+bool ieee80211_csa_is_complete(struct ieee80211_vif *vif)
+{
+	return ieee80211_beacon_counter_is_complete(vif, MAC80211_COUNTER_CSA);
+}
 EXPORT_SYMBOL(ieee80211_csa_is_complete);
 
+bool ieee80211_cca_is_complete(struct ieee80211_vif *vif)
+{
+	return ieee80211_beacon_counter_is_complete(vif, MAC80211_COUNTER_CCA);
+}
+EXPORT_SYMBOL(ieee80211_cca_is_complete);
+
 static int ieee80211_beacon_protect(struct sk_buff *skb,
 				    struct ieee80211_local *local,
 				    struct ieee80211_sub_if_data *sdata)
@@ -4767,6 +4849,13 @@ __ieee80211_beacon_get(struct ieee80211_hw *hw,
 				ieee80211_set_csa(sdata, beacon);
 			}
 
+			if (beacon->cca_counter_offset) {
+				if (!is_template)
+					__ieee80211_cca_update_counter(beacon);
+
+				ieee80211_set_cca(sdata, beacon);
+			}
+
 			/*
 			 * headroom, head length,
 			 * tail length and maximum TIM length
@@ -4787,6 +4876,7 @@ __ieee80211_beacon_get(struct ieee80211_hw *hw,
 			if (offs) {
 				offs->tim_offset = beacon->head_len;
 				offs->tim_length = skb->len - beacon->head_len;
+				offs->cca_counter_off = beacon->cca_counter_offset;
 
 				/* for AP the csa offsets are from tail */
 				csa_off_base = skb->len;
