Prechádzať zdrojové kódy

qcacmn: Add support to configure spectral scan bandwidth

Add support to configure spectral scan bandwidth as provided by the user.
Validate the user configured value before applying it. As the spectral scan
bandwidth is an optional parameter, when the user doesn't configure it, use
configure the spectral scan bandwidth to the maximum value supported by the
target for the current operating bandwidth.
Also, populate the spectral scan bandwidths supported for different
operating bandwidths on different targets.

CRs-Fixed: 3042424
Change-Id: I266aa5489b0ce5c149968067a06e1cc65bb4e3b7
Shiva Krishna Pittala 3 rokov pred
rodič
commit
aead8c4d39

+ 11 - 1
os_if/linux/spectral/src/wlan_cfg80211_spectral.c

@@ -91,6 +91,8 @@ const struct nla_policy spectral_scan_policy[
 							.type = NLA_U8},
 	[QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_CONFIG_DMA_BUFFER_DEBUG] = {
 							.type = NLA_U8},
+	[QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_CONFIG_BANDWIDTH] = {
+							.type = NLA_U8},
 };
 
 const struct nla_policy spectral_scan_get_status_policy[
@@ -126,6 +128,7 @@ static void wlan_spectral_intit_config(struct spectral_config *config_req)
 	config_req->ss_chn_mask =        SPECTRAL_PHYERR_PARAM_NOVAL;
 	config_req->ss_frequency.cfreq1 = SPECTRAL_PHYERR_PARAM_NOVAL;
 	config_req->ss_frequency.cfreq2 = SPECTRAL_PHYERR_PARAM_NOVAL;
+	config_req->ss_bandwidth = SPECTRAL_PHYERR_PARAM_NOVAL;
 }
 
 /**
@@ -369,6 +372,10 @@ int wlan_cfg80211_spectral_scan_config_and_start(struct wiphy *wiphy,
 		config_req.ss_frequency.cfreq2 = nla_get_u32(tb
 		   [QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_CONFIG_FREQUENCY_2]);
 
+	if (tb[QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_CONFIG_BANDWIDTH])
+		config_req.ss_bandwidth = nla_get_u8(tb
+		   [QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_CONFIG_BANDWIDTH]);
+
 	if (tb[QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_MODE]) {
 		status = convert_spectral_mode_nl_to_internal(nla_get_u32(tb
 		   [QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_MODE]), &sscan_mode);
@@ -676,7 +683,10 @@ int wlan_cfg80211_spectral_scan_get_config(struct wiphy *wiphy,
 			sconfig->ss_frequency.cfreq1) ||
 	    nla_put_u32(skb,
 			QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_CONFIG_FREQUENCY_2,
-			sconfig->ss_frequency.cfreq2))
+			sconfig->ss_frequency.cfreq2) ||
+	    nla_put_u8(skb,
+		       QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_CONFIG_BANDWIDTH,
+		       sconfig->ss_bandwidth))
 
 		goto fail;
 

+ 11 - 1
spectral/core/spectral_common.c

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011,2017-2020 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2011,2017-2021 The Linux Foundation. All rights reserved.
  *
  *
  * Permission to use, copy, modify, and/or distribute this software for
@@ -343,6 +343,15 @@ spectral_control_cmn(struct wlan_objmgr_pdev *pdev,
 				goto bad;
 		}
 
+		if (sp_in->ss_bandwidth != SPECTRAL_PHYERR_PARAM_NOVAL) {
+			param.id = SPECTRAL_PARAM_CHAN_WIDTH;
+			param.value = sp_in->ss_bandwidth;
+			ret = sc->sptrlc_set_spectral_config
+						(pdev, &param, smode, err);
+			if (QDF_IS_STATUS_ERROR(ret))
+				goto bad;
+		}
+
 		break;
 
 	case SPECTRAL_GET_CONFIG:
@@ -373,6 +382,7 @@ spectral_control_cmn(struct wlan_objmgr_pdev *pdev,
 		spectralparams->ss_dbm_adj = sp_out.ss_dbm_adj;
 		spectralparams->ss_chn_mask = sp_out.ss_chn_mask;
 		spectralparams->ss_frequency = sp_out.ss_frequency;
+		spectralparams->ss_bandwidth = sp_out.ss_bandwidth;
 		break;
 
 	case SPECTRAL_IS_ACTIVE:

+ 2 - 0
spectral/dispatcher/inc/spectral_ioctl.h

@@ -309,6 +309,7 @@ struct spectral_config_frequency {
 *                           span of interest or center frequency (in MHz) of
  *                          any WLAN channel in the secondary 80 MHz span of
  *                          interest.
+ * @ss_bandwidth: Spectral scan bandwidth
  */
 struct spectral_config {
 	uint16_t ss_fft_period;
@@ -336,6 +337,7 @@ struct spectral_config {
 	int8_t ss_nf_pwr[AH_MAX_CHAINS * 2];
 	int32_t ss_nf_temp_data;
 	struct spectral_config_frequency ss_frequency;
+	uint16_t ss_bandwidth;
 };
 
 /**

+ 487 - 106
target_if/spectral/target_if_spectral.c

@@ -525,7 +525,7 @@ target_if_send_vdev_spectral_configure_cmd(struct target_if_spectral *spectral,
 	sparam.mode = smode;
 	sparam.center_freq1 = param->ss_frequency.cfreq1;
 	sparam.center_freq2 = param->ss_frequency.cfreq2;
-	sparam.chan_width = spectral->ch_width[smode];
+	sparam.chan_width = param->ss_bandwidth;
 
 	return psoc_spectral->wmi_ops.wmi_spectral_configure_cmd_send(
 				GET_WMI_HDL_FROM_PDEV(pdev), &sparam);
@@ -595,6 +595,240 @@ target_if_send_vdev_spectral_enable_cmd(struct target_if_spectral *spectral,
 				GET_WMI_HDL_FROM_PDEV(pdev), &param);
 }
 
+/**
+ * is_spectral_arch_beryllium() - Check whether the given target Spectral
+ * architecture is Beryllium
+ * @target_tpe: Target type
+ *
+ * Return: true if the spectral architecture is Beryllium, else false
+ */
+static inline bool is_spectral_arch_beryllium(uint32_t target_tpe)
+{
+	if (target_tpe == TARGET_TYPE_QCN9224)
+		return true;
+
+	return false;
+}
+
+/**
+ * List of supported sscan BWs. Make sure to maintain the array elements in the
+ * same order of BWs as that of struct spectral_supported_bws bitmap.
+ */
+static const enum phy_ch_width supported_sscan_bw_list[] = {
+	CH_WIDTH_5MHZ,
+	CH_WIDTH_10MHZ,
+	CH_WIDTH_20MHZ,
+	CH_WIDTH_40MHZ,
+	CH_WIDTH_80MHZ,
+	CH_WIDTH_160MHZ,
+	CH_WIDTH_80P80MHZ,
+#ifdef WLAN_FEATURE_11BE
+	CH_WIDTH_320MHZ,
+#endif
+};
+
+#define INVALID_SSCAN_BW_POS (-1)
+int get_supported_sscan_bw_pos(enum phy_ch_width sscan_bw)
+{
+	int max_pos, pos;
+
+	max_pos =  QDF_ARRAY_SIZE(supported_sscan_bw_list);
+	for (pos = 0; pos < max_pos; pos++) {
+		if (supported_sscan_bw_list[pos] == sscan_bw)
+			return pos;
+	}
+
+	return INVALID_SSCAN_BW_POS;
+}
+
+/**
+ * target_if_is_sscan_bw_supported() - Check whether the given sscan_bw is
+ * supported
+ * @spectral: Spectral LMAC object
+ * @smode: Spectral scan mode
+ * @sscan_bw: Spectral scan bandwidth
+ * @op_bw: operating bandwidth
+ * @is_bw_supported: Pointer to the caller variable where this function
+ * populates whether @sscan_bw is supported
+ * @is_80_80_agile: Indicates an 80+80 agile Scan request
+ *
+ * Return: QDF_STATUS of operation
+ */
+static QDF_STATUS
+target_if_is_sscan_bw_supported(struct target_if_spectral *spectral,
+				enum spectral_scan_mode smode,
+				enum phy_ch_width sscan_bw,
+				enum phy_ch_width op_bw,
+				bool *is_bw_supported,
+				bool is_80_80_agile)
+{
+	struct spectral_supported_bws *supported_bws;
+
+	*is_bw_supported = false;
+
+	if (op_bw >= CH_WIDTH_INVALID) {
+		spectral_err("Invalid channel width %d", op_bw);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	if ((is_80_80_agile && sscan_bw != CH_WIDTH_80P80MHZ) ||
+	    (!is_80_80_agile && sscan_bw == CH_WIDTH_80P80MHZ)) {
+		*is_bw_supported = false;
+		return QDF_STATUS_SUCCESS;
+	}
+
+	/* Get the supported sscan bandwidths for this operating bandwidth */
+	supported_bws = &spectral->supported_bws[smode][op_bw];
+	*is_bw_supported = supported_bws->bandwidths &
+				(1 << get_supported_sscan_bw_pos(sscan_bw));
+
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * get_max_sscan_bw() - Get the maximum sscan bandwidth for a given operating
+ * bandwidth
+ * @spectral: Spectral LMAC object
+ * @smode: Spectral scan mode
+ * @op_bw: operating bandwidth
+ *
+ * Return: Maximum sscan bandwidth for @op_bw on success, else CH_WIDTH_INVALID
+ */
+static enum phy_ch_width
+get_max_sscan_bw(struct target_if_spectral *spectral,
+		 enum spectral_scan_mode smode,
+		 enum phy_ch_width op_bw)
+{
+	int op_bw_pos, pos;
+	struct spectral_supported_bws *supported_bws;
+
+	supported_bws = &spectral->supported_bws[smode][op_bw];
+	op_bw_pos = get_supported_sscan_bw_pos(op_bw);
+
+	/**
+	 * Start with operating bandwidth, and keep reducing the bandwidth until
+	 * a supported sscan BW is found.
+	 */
+	for (pos = op_bw_pos; pos >= 0; pos--) {
+		if (supported_bws->bandwidths & (1 << pos))
+			return supported_sscan_bw_list[pos];
+	}
+
+	return CH_WIDTH_INVALID;
+}
+
+/* target_if_spectral_find_agile_width() - Given a channel width enum, find the
+ * corresponding translation for Agile channel width.
+ * @spectral: pointer to Spectral object
+ * @op_width: operating channel width
+ * @is_80_80_agile: Indicates an 80+80 agile Scan request
+ *
+ * Return: The translated channel width enum.
+ */
+static enum phy_ch_width
+target_if_spectral_find_agile_width(struct target_if_spectral *spectral,
+				    enum phy_ch_width op_bw,
+				    bool is_80_80_agile)
+{
+	enum phy_ch_width agile_width;
+	struct wlan_objmgr_pdev *pdev;
+	struct wlan_objmgr_psoc *psoc;
+
+	if (!spectral) {
+		spectral_err("Spectral object is null");
+		return CH_WIDTH_INVALID;
+	}
+
+	pdev =  spectral->pdev_obj;
+	if (!pdev) {
+		spectral_err("pdev is null");
+		return CH_WIDTH_INVALID;
+	}
+
+	psoc = wlan_pdev_get_psoc(pdev);
+	if (!psoc) {
+		spectral_err("psoc is null");
+		return CH_WIDTH_INVALID;
+	}
+
+	agile_width = get_max_sscan_bw(spectral, SPECTRAL_SCAN_MODE_AGILE,
+				       op_bw);
+
+	if (wlan_psoc_nif_fw_ext_cap_get(psoc,
+					 WLAN_SOC_RESTRICTED_80P80_SUPPORT)) {
+		switch (op_bw) {
+		case CH_WIDTH_80P80MHZ:
+			if (!is_80_80_agile)
+				agile_width = CH_WIDTH_160MHZ;
+			else
+				agile_width = CH_WIDTH_80P80MHZ;
+
+			break;
+
+		case CH_WIDTH_160MHZ:
+			if (is_80_80_agile)
+				agile_width = CH_WIDTH_80P80MHZ;
+			else
+				agile_width = CH_WIDTH_160MHZ;
+
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	return agile_width;
+}
+
+/**
+ * get_default_sscan_bw() - Get the default sscan bandwidth for a given
+ * operating bandwidth
+ * @spectral: Spectral LMAC object
+ * @smode: Spectral scan mode
+ * @is_80_80_agile: Indicates an 80+80 agile Scan request
+ *
+ * Return: Default sscan bandwidth for @op_bw on success, else CH_WIDTH_INVALID
+ */
+static enum phy_ch_width
+get_default_sscan_bw(struct target_if_spectral *spectral,
+		     enum spectral_scan_mode smode,
+		     bool is_80_80_agile)
+{
+	struct wlan_objmgr_vdev *vdev;
+	enum phy_ch_width vdev_ch_width, sscan_width;
+
+	vdev = target_if_spectral_get_vdev(spectral, smode);
+	if (!vdev) {
+		spectral_err("vdev is null");
+		return CH_WIDTH_INVALID;
+	}
+
+	vdev_ch_width = target_if_vdev_get_ch_width(vdev);
+	wlan_objmgr_vdev_release_ref(vdev, WLAN_SPECTRAL_ID);
+	if (vdev_ch_width >= CH_WIDTH_INVALID) {
+		spectral_err("Invalid vdev channel width %d", vdev_ch_width);
+		return CH_WIDTH_INVALID;
+	}
+
+	switch (smode) {
+	case SPECTRAL_SCAN_MODE_NORMAL:
+		sscan_width = get_max_sscan_bw(spectral, smode, vdev_ch_width);
+		break;
+
+	case SPECTRAL_SCAN_MODE_AGILE:
+		sscan_width = target_if_spectral_find_agile_width(
+				spectral, vdev_ch_width, is_80_80_agile);
+		break;
+
+	default:
+		sscan_width = CH_WIDTH_INVALID;
+		break;
+	}
+
+	return sscan_width;
+}
+
 /**
  * target_if_spectral_info_init_defaults() - Helper function to load defaults
  * for Spectral information (parameters and state) into cache.
@@ -613,6 +847,7 @@ target_if_spectral_info_init_defaults(struct target_if_spectral *spectral,
 {
 	struct target_if_spectral_param_state_info *info;
 	struct wlan_objmgr_vdev *vdev = NULL;
+	enum phy_ch_width sscan_bw;
 
 	if (smode >= SPECTRAL_SCAN_MODE_MAX) {
 		spectral_err("Invalid Spectral mode %u", smode);
@@ -698,6 +933,13 @@ target_if_spectral_info_init_defaults(struct target_if_spectral *spectral,
 	info->osps_cache.osc_params.ss_frequency.cfreq2 =
 		SPECTRAL_SCAN_FREQUENCY_DEFAULT;
 
+	sscan_bw = get_default_sscan_bw(spectral, smode, false);
+	if (sscan_bw >= CH_WIDTH_INVALID) {
+		spectral_err("Invalid sscan BW %u", sscan_bw);
+		return QDF_STATUS_E_FAILURE;
+	}
+	info->osps_cache.osc_params.ss_bandwidth = sscan_bw;
+
 	/* The cache is now valid */
 	info->osps_cache.osc_is_valid = 1;
 
@@ -1876,6 +2118,182 @@ target_if_init_spectral_param_properties(struct target_if_spectral *spectral)
 	return QDF_STATUS_SUCCESS;
 }
 
+/* Bandwidth to half bandwidth mapping */
+static const enum phy_ch_width half_bw_map[] = {
+#ifdef WLAN_FEATURE_11BE
+	[CH_WIDTH_320MHZ] = CH_WIDTH_160MHZ,
+#endif
+	[CH_WIDTH_80P80MHZ] = CH_WIDTH_80MHZ,
+	[CH_WIDTH_160MHZ] = CH_WIDTH_80MHZ,
+	[CH_WIDTH_80MHZ] = CH_WIDTH_40MHZ,
+	[CH_WIDTH_40MHZ] = CH_WIDTH_20MHZ,
+	[CH_WIDTH_20MHZ] = CH_WIDTH_10MHZ,
+	[CH_WIDTH_10MHZ] = CH_WIDTH_5MHZ,
+	[CH_WIDTH_5MHZ] = CH_WIDTH_INVALID
+};
+
+/**
+ * target_if_get_half_bandwidth() - Get half bandwidth for a given bandwidth
+ * @bw: bandwidth
+ *
+ * Return: Half bandwidth of @bw
+ */
+static enum phy_ch_width target_if_get_half_bandwidth(enum phy_ch_width bw)
+{
+	if (bw >= CH_WIDTH_INVALID)
+		return CH_WIDTH_INVALID;
+
+	return half_bw_map[bw];
+}
+
+/**
+ * target_if_populate_supported_sscan_bws_be() - Populate supported spectral
+ * scan bandwidths for beryllium chipsets
+ * @spectral: Spectral LMAC object
+ *
+ * Return: QDF_STATUS of operation
+ */
+static QDF_STATUS
+target_if_populate_supported_sscan_bws_be(struct target_if_spectral *spectral)
+{
+	enum phy_ch_width op_bw;
+	struct spectral_supported_bws *supported_bws;
+	QDF_STATUS status;
+
+	qdf_assert_always(spectral);
+
+	/* 20MHz */
+	op_bw = CH_WIDTH_20MHZ;
+	supported_bws = &spectral->supported_bws
+			[SPECTRAL_SCAN_MODE_NORMAL][op_bw];
+	supported_bws->bandwidths |= 1 << get_supported_sscan_bw_pos(op_bw);
+	supported_bws = &spectral->supported_bws
+			[SPECTRAL_SCAN_MODE_AGILE][op_bw];
+	supported_bws->bandwidths |= 1 << get_supported_sscan_bw_pos(op_bw);
+
+	for (op_bw = CH_WIDTH_40MHZ; op_bw < CH_WIDTH_MAX; op_bw++) {
+		bool is_supported;
+		enum phy_ch_width half_op_bw;
+
+		status = wlan_reg_is_chwidth_supported(spectral->pdev_obj,
+						       op_bw, &is_supported);
+		if (QDF_IS_STATUS_ERROR(status)) {
+			spectral_err("Unable to check if ch_width(%d) is supported",
+				     op_bw);
+			return QDF_STATUS_E_FAILURE;
+		}
+
+		if (!is_supported)
+			continue;
+
+		spectral_debug("Updating supported bw for op_bw: %d", op_bw);
+		/* Normal mode */
+		supported_bws = &spectral->supported_bws
+				[SPECTRAL_SCAN_MODE_NORMAL][op_bw];
+		supported_bws->bandwidths |=
+				1 << get_supported_sscan_bw_pos(op_bw);
+
+		/* Agile mode */
+		supported_bws = &spectral->supported_bws
+				[SPECTRAL_SCAN_MODE_AGILE][op_bw];
+		supported_bws->bandwidths |=
+				1 << get_supported_sscan_bw_pos(op_bw);
+
+		half_op_bw = target_if_get_half_bandwidth(op_bw);
+		if (half_op_bw != CH_WIDTH_INVALID)
+			supported_bws->bandwidths |=
+				1 << get_supported_sscan_bw_pos(half_op_bw);
+	}
+
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * target_if_populate_supported_sscan_bws() - Populate supported spectral
+ * scan bandwidths
+ * @spectral: Spectral LMAC object
+ * @target_type: Target type
+ *
+ * Return: QDF_STATUS of operation
+ */
+static QDF_STATUS
+target_if_populate_supported_sscan_bws(struct target_if_spectral *spectral,
+				       uint32_t target_type)
+{
+	enum spectral_scan_mode smode;
+	enum phy_ch_width op_bw;
+	struct spectral_supported_bws *supported_bws;
+	struct wlan_objmgr_psoc *psoc;
+	QDF_STATUS status;
+
+	qdf_assert_always(spectral);
+
+	if (is_spectral_arch_beryllium(target_type))
+		return target_if_populate_supported_sscan_bws_be(spectral);
+
+	psoc = wlan_pdev_get_psoc(spectral->pdev_obj);
+	if (!psoc) {
+		spectral_err("psoc is null");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	for (op_bw = CH_WIDTH_20MHZ; op_bw < CH_WIDTH_MAX; op_bw++) {
+		bool is_supported;
+
+		status = wlan_reg_is_chwidth_supported(spectral->pdev_obj,
+						       op_bw, &is_supported);
+		if (QDF_IS_STATUS_ERROR(status)) {
+			spectral_err("Unable to check if ch_width(%d) is supported",
+				     op_bw);
+			return QDF_STATUS_E_FAILURE;
+		}
+
+		if (!is_supported)
+			continue;
+
+		spectral_debug("Updating supported bw for op_bw: %d", op_bw);
+		smode = SPECTRAL_SCAN_MODE_NORMAL;
+		for (; smode < SPECTRAL_SCAN_MODE_MAX; smode++) {
+			supported_bws = &spectral->supported_bws[smode][op_bw];
+
+			if (is_ch_width_160_or_80p80(op_bw) &&
+			    smode == SPECTRAL_SCAN_MODE_AGILE) {
+				/**
+				 * If fragmentation is supported, then only 80Hz
+				 * agile width is supported
+				 */
+				if (spectral->rparams.fragmentation_160[smode])
+					supported_bws->bandwidths |=
+					 1 << get_supported_sscan_bw_pos(
+						CH_WIDTH_80MHZ);
+
+				/**
+				 * If restricted 80p80 is supported, then both
+				 * 160 and 80p80 agile widths are supported for
+				 * 160MHz, and only 160MHz agile width is
+				 * supported for 80p80
+				 */
+				if (wlan_psoc_nif_fw_ext_cap_get(
+				     psoc, WLAN_SOC_RESTRICTED_80P80_SUPPORT)) {
+					supported_bws->bandwidths |=
+						1 << get_supported_sscan_bw_pos(
+							CH_WIDTH_160MHZ);
+					if (op_bw == CH_WIDTH_160MHZ)
+						supported_bws->bandwidths |=
+						1 << get_supported_sscan_bw_pos(
+							CH_WIDTH_80P80MHZ);
+				}
+			} else {
+				supported_bws->bandwidths |=
+					1 << get_supported_sscan_bw_pos(
+						op_bw);
+			}
+		}
+	}
+
+	return QDF_STATUS_SUCCESS;
+}
+
 QDF_STATUS
 target_if_init_spectral_capability(struct target_if_spectral *spectral,
 				   uint32_t target_type)
@@ -1887,6 +2305,7 @@ target_if_init_spectral_capability(struct target_if_spectral *spectral,
 	struct target_psoc_info *tgt_psoc_info;
 	struct wlan_psoc_host_service_ext_param *ext_svc_param;
 	struct spectral_caps *pcap = &spectral->capability;
+	QDF_STATUS status;
 
 	pdev = spectral->pdev_obj;
 	psoc = wlan_pdev_get_psoc(pdev);
@@ -1955,6 +2374,12 @@ target_if_init_spectral_capability(struct target_if_spectral *spectral,
 		pcap->num_detectors_80p80mhz = 2;
 	}
 
+	status = target_if_populate_supported_sscan_bws(spectral, target_type);
+	if (QDF_IS_STATUS_ERROR(status)) {
+		spectral_err("Unable to populate supported sscan BWs");
+		return QDF_STATUS_E_FAILURE;
+	}
+
 	return QDF_STATUS_SUCCESS;
 }
 
@@ -3248,84 +3673,6 @@ fail:
 	return psoc_spectral;
 }
 
-/* target_if_spectral_find_agile_width() - Given a channel width enum, find the
- * corresponding translation for Agile channel width.
- * @spectral: pointer to Spectral object
- * @chwidth: operating channel width
- * @is_80_80_agile: Indicates an 80+80 agile Scan request
- *
- * Return: The translated channel width enum.
- */
-static enum phy_ch_width
-target_if_spectral_find_agile_width(struct target_if_spectral *spectral,
-				    enum phy_ch_width chwidth,
-				    bool is_80_80_agile)
-{
-	enum phy_ch_width agile_width;
-	struct wlan_objmgr_pdev *pdev;
-	struct wlan_objmgr_psoc *psoc;
-
-	if (!spectral) {
-		spectral_err("Spectral object is null");
-		return CH_WIDTH_INVALID;
-	}
-
-	pdev =  spectral->pdev_obj;
-	if (!pdev) {
-		spectral_err("pdev is null");
-		return CH_WIDTH_INVALID;
-	}
-
-	psoc = wlan_pdev_get_psoc(pdev);
-	if (!psoc) {
-		spectral_err("psoc is null");
-		return CH_WIDTH_INVALID;
-	}
-
-	switch (chwidth) {
-	case CH_WIDTH_20MHZ:
-		agile_width = CH_WIDTH_20MHZ;
-		break;
-
-	case CH_WIDTH_40MHZ:
-		agile_width = CH_WIDTH_40MHZ;
-		break;
-
-	case CH_WIDTH_80MHZ:
-		agile_width = CH_WIDTH_80MHZ;
-		break;
-
-	case CH_WIDTH_80P80MHZ:
-		if (wlan_psoc_nif_fw_ext_cap_get(psoc,
-		    WLAN_SOC_RESTRICTED_80P80_SUPPORT) && !is_80_80_agile)
-			agile_width = CH_WIDTH_160MHZ;
-		else
-			agile_width = CH_WIDTH_80P80MHZ;
-
-		if (agile_width > spectral->rparams.max_agile_ch_width)
-			agile_width = spectral->rparams.max_agile_ch_width;
-		break;
-
-	case CH_WIDTH_160MHZ:
-		if (wlan_psoc_nif_fw_ext_cap_get(psoc,
-		    WLAN_SOC_RESTRICTED_80P80_SUPPORT) && is_80_80_agile)
-			agile_width = CH_WIDTH_80P80MHZ;
-		else
-			agile_width = CH_WIDTH_160MHZ;
-
-		if (agile_width > spectral->rparams.max_agile_ch_width)
-			agile_width = spectral->rparams.max_agile_ch_width;
-		break;
-
-	default:
-		spectral_err("Invalid channel width %d", chwidth);
-		agile_width = CH_WIDTH_INVALID;
-		break;
-	}
-
-	return agile_width;
-}
-
 /**
  * target_if_calculate_center_freq() - Helper routine to
  * check whether given frequency is center frequency of a
@@ -3708,38 +4055,30 @@ target_if_is_agile_span_overlap_with_operating_span
 static QDF_STATUS
 target_if_spectral_populate_chwidth(struct target_if_spectral *spectral,
 				    enum phy_ch_width *ch_width,
-				    bool is_80_80_agile) {
-	struct wlan_objmgr_vdev *vdev;
+				    bool is_80_80_agile)
+{
 	enum spectral_scan_mode smode;
-	enum phy_ch_width vdev_ch_width;
-
-	smode = SPECTRAL_SCAN_MODE_NORMAL;
-	for (; smode < SPECTRAL_SCAN_MODE_MAX; smode++)
-		ch_width[smode] = CH_WIDTH_INVALID;
-
-	if (!spectral) {
-		spectral_err("Spectral object is null");
-		return QDF_STATUS_E_INVAL;
-	}
 
-	vdev = target_if_spectral_get_vdev(spectral, SPECTRAL_SCAN_MODE_NORMAL);
-	if (!vdev) {
-		spectral_err("vdev is null");
-		return QDF_STATUS_E_FAILURE;
-	}
+	qdf_assert_always(spectral);
 
-	vdev_ch_width = target_if_vdev_get_ch_width(vdev);
-	wlan_objmgr_vdev_release_ref(vdev, WLAN_SPECTRAL_ID);
-	if (vdev_ch_width == CH_WIDTH_INVALID) {
-		spectral_err("Invalid channel width %d", vdev_ch_width);
-		return QDF_STATUS_E_FAILURE;
+	smode = SPECTRAL_SCAN_MODE_NORMAL;
+	for (; smode < SPECTRAL_SCAN_MODE_MAX; ++smode) {
+		/* If user has configured sscan bandwidth, use it */
+		if (spectral->sscan_width_configured[smode]) {
+			ch_width[smode] = spectral->params[smode].ss_bandwidth;
+		} else {
+			/* Otherwise, derive the default sscan bandwidth */
+			ch_width[smode] = get_default_sscan_bw(spectral, smode,
+							       is_80_80_agile);
+			if (ch_width[smode] >= CH_WIDTH_INVALID) {
+				spectral_err("Invalid sscan BW %u",
+					     ch_width[smode]);
+				return QDF_STATUS_E_FAILURE;
+			}
+			spectral->params[smode].ss_bandwidth = ch_width[smode];
+		}
 	}
 
-	ch_width[SPECTRAL_SCAN_MODE_NORMAL] = vdev_ch_width;
-	ch_width[SPECTRAL_SCAN_MODE_AGILE] =
-		target_if_spectral_find_agile_width(spectral, vdev_ch_width,
-						    is_80_80_agile);
-
 	return QDF_STATUS_SUCCESS;
 }
 
@@ -3839,6 +4178,9 @@ _target_if_set_spectral_config(struct target_if_spectral *spectral,
 	enum phy_ch_width ch_width[SPECTRAL_SCAN_MODE_MAX];
 	enum spectral_scan_mode m;
 	struct spectral_config_frequency center_freq = {0};
+	bool is_bw_supported;
+	struct wlan_objmgr_vdev *vdev;
+	enum phy_ch_width op_bw;
 
 	if (!err) {
 		spectral_err("Error code argument is null");
@@ -4081,6 +4423,44 @@ _target_if_set_spectral_config(struct target_if_spectral *spectral,
 		sparams->ss_frequency.cfreq1 = center_freq.cfreq1;
 		sparams->ss_frequency.cfreq2 = center_freq.cfreq2;
 
+		break;
+
+	case SPECTRAL_PARAM_CHAN_WIDTH:
+		if (param->value >= CH_WIDTH_INVALID) {
+			spectral_err("invalid sscan width: %u", param->value);
+			*err = SPECTRAL_SCAN_ERR_PARAM_INVALID_VALUE;
+			return QDF_STATUS_E_FAILURE;
+		}
+
+		vdev = target_if_spectral_get_vdev(spectral, smode);
+		if (!vdev) {
+			spectral_err("vdev is null");
+			return QDF_STATUS_E_NULL_VALUE;
+		}
+		op_bw = target_if_vdev_get_ch_width(vdev);
+		wlan_objmgr_vdev_release_ref(vdev, WLAN_SPECTRAL_ID);
+
+		/* Validate the bandwidth */
+		status = target_if_is_sscan_bw_supported(
+				spectral, smode,
+				param->value, op_bw, &is_bw_supported,
+				spectral->params[SPECTRAL_SCAN_MODE_AGILE].
+				ss_frequency.cfreq2 > 0);
+		if (QDF_IS_STATUS_ERROR(status)) {
+			spectral_err("Unable to check if given sscan_bw is supported");
+			return QDF_STATUS_E_FAILURE;
+		}
+
+		if (!is_bw_supported) {
+			spectral_err("sscan bw(%u) is not supported for the current operating width(%u) and sscan mode(%u)",
+				     param->value, op_bw, smode);
+			*err = SPECTRAL_SCAN_ERR_PARAM_INVALID_VALUE;
+			return QDF_STATUS_E_FAILURE;
+		}
+
+		sparams->ss_bandwidth = param->value;
+		spectral->sscan_width_configured[smode] = true;
+
 		break;
 	}
 
@@ -5399,6 +5779,7 @@ target_if_start_spectral_scan(struct wlan_objmgr_pdev *pdev,
 					      &spectral->params[smode], smode,
 					      err);
 
+	spectral->sscan_width_configured[smode] = false;
 	qdf_spin_unlock(&spectral->spectral_lock);
 
 	return QDF_STATUS_SUCCESS;

+ 37 - 0
target_if/spectral/target_if_spectral.h

@@ -1083,6 +1083,37 @@ struct sscan_detector_list {
 	uint8_t num_detectors;
 };
 
+/**
+ * struct spectral_supported_bws - Supported sscan bandwidths
+ * @bandwidths: bitmap of supported sscan bandwidths. Make sure to maintain this
+ * bitmap in the increasing order of bandwidths.
+ */
+struct spectral_supported_bws {
+	union {
+		struct {
+			uint32_t supports_sscan_bw_5:1,
+				 supports_sscan_bw_10:1,
+				 supports_sscan_bw_20:1,
+				 supports_sscan_bw_40:1,
+				 supports_sscan_bw_80:1,
+				 supports_sscan_bw_160:1,
+				 supports_sscan_bw_80_80:1,
+				 supports_sscan_bw_320:1,
+				 reserved:24;
+		};
+		uint32_t bandwidths;
+	};
+};
+
+/**
+ * get_supported_sscan_bw_pos() - Get the position of a given sscan_bw inside
+ * the supported sscan bandwidths bitmap
+ * @sscan_bw: Spectral scan bandwidth
+ *
+ * Return: bit position for a valid sscan bandwidth, else -1
+ */
+int get_supported_sscan_bw_pos(enum phy_ch_width sscan_bw);
+
 /**
  * struct target_if_spectral - main spectral structure
  * @pdev: Pointer to pdev
@@ -1146,6 +1177,7 @@ struct sscan_detector_list {
  * @tsf64: Latest TSF Value
  * @param_info: Offload architecture Spectral parameter cache information
  * @ch_width: Indicates Channel Width 20/40/80/160 MHz for each Spectral mode
+ * @sscan_width_configured: Whether user has configured sscan bandwidth
  * @diag_stats: Diagnostic statistics
  * @is_160_format:  Indicates whether information provided by HW is in altered
  * format for 802.11ac 160/80+80 MHz support (QCA9984 onwards)
@@ -1186,6 +1218,8 @@ struct sscan_detector_list {
  * @session_det_map_lock: Lock to synchronize accesses to session detector map
  * @report_info: Per session info to be filled at report level in SAMP message
  * @session_report_info_lock: Lock to synchronize access to session report info
+ * @supported_bws: Supported sscan bandwidths for all sscan modes and
+ * operating widths
  */
 struct target_if_spectral {
 	struct wlan_objmgr_pdev *pdev_obj;
@@ -1274,6 +1308,7 @@ struct target_if_spectral {
 					param_info[SPECTRAL_SCAN_MODE_MAX];
 #endif
 	enum phy_ch_width ch_width[SPECTRAL_SCAN_MODE_MAX];
+	bool sscan_width_configured[SPECTRAL_SCAN_MODE_MAX];
 	struct spectral_diag_stats              diag_stats;
 	bool                                    is_160_format;
 	bool                                    is_lb_edge_extrabins_format;
@@ -1314,6 +1349,8 @@ struct target_if_spectral {
 	qdf_spinlock_t session_det_map_lock;
 	struct per_session_report_info report_info[SPECTRAL_SCAN_MODE_MAX];
 	qdf_spinlock_t session_report_info_lock;
+	struct spectral_supported_bws
+		supported_bws[SPECTRAL_SCAN_MODE_MAX][CH_WIDTH_MAX];
 };
 
 /**