18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: BSD-3-Clause-Clear
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved.
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/relay.h>
78c2ecf20Sopenharmony_ci#include "core.h"
88c2ecf20Sopenharmony_ci#include "debug.h"
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_NUM_RESP_PER_EVENT	2
118c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_EVENT_TIMEOUT_MS	1
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_DWORD_SIZE		4
148c2ecf20Sopenharmony_ci/* HW bug, expected BIN size is 2 bytes but HW report as 4 bytes */
158c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_BIN_SIZE		4
168c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_ATH11K_MIN_BINS		64
178c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_ATH11K_MIN_IB_BINS	32
188c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_ATH11K_MAX_IB_BINS	256
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_SCAN_COUNT_MAX		4095
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci/* Max channel computed by sum of 2g and 5g band channels */
238c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_TOTAL_CHANNEL		41
248c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_SAMPLES_PER_CHANNEL	70
258c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_PER_SAMPLE_SIZE		(sizeof(struct fft_sample_ath11k) + \
268c2ecf20Sopenharmony_ci						 ATH11K_SPECTRAL_ATH11K_MAX_IB_BINS)
278c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_TOTAL_SAMPLE		(ATH11K_SPECTRAL_TOTAL_CHANNEL * \
288c2ecf20Sopenharmony_ci						 ATH11K_SPECTRAL_SAMPLES_PER_CHANNEL)
298c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_SUB_BUFF_SIZE		ATH11K_SPECTRAL_PER_SAMPLE_SIZE
308c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_NUM_SUB_BUF		ATH11K_SPECTRAL_TOTAL_SAMPLE
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_20MHZ			20
338c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_40MHZ			40
348c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_80MHZ			80
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_SIGNATURE		0xFA
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_TAG_RADAR_SUMMARY	0x0
398c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_TAG_RADAR_FFT		0x1
408c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_TAG_SCAN_SUMMARY	0x2
418c2ecf20Sopenharmony_ci#define ATH11K_SPECTRAL_TAG_SCAN_SEARCH		0x3
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci#define SPECTRAL_TLV_HDR_LEN				GENMASK(15, 0)
448c2ecf20Sopenharmony_ci#define SPECTRAL_TLV_HDR_TAG				GENMASK(23, 16)
458c2ecf20Sopenharmony_ci#define SPECTRAL_TLV_HDR_SIGN				GENMASK(31, 24)
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci#define SPECTRAL_SUMMARY_INFO0_AGC_TOTAL_GAIN		GENMASK(7, 0)
488c2ecf20Sopenharmony_ci#define SPECTRAL_SUMMARY_INFO0_OB_FLAG			BIT(8)
498c2ecf20Sopenharmony_ci#define SPECTRAL_SUMMARY_INFO0_GRP_IDX			GENMASK(16, 9)
508c2ecf20Sopenharmony_ci#define SPECTRAL_SUMMARY_INFO0_RECENT_RFSAT		BIT(17)
518c2ecf20Sopenharmony_ci#define SPECTRAL_SUMMARY_INFO0_INBAND_PWR_DB		GENMASK(27, 18)
528c2ecf20Sopenharmony_ci#define SPECTRAL_SUMMARY_INFO0_FALSE_SCAN		BIT(28)
538c2ecf20Sopenharmony_ci#define SPECTRAL_SUMMARY_INFO0_DETECTOR_ID		GENMASK(30, 29)
548c2ecf20Sopenharmony_ci#define SPECTRAL_SUMMARY_INFO0_PRI80			BIT(31)
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci#define SPECTRAL_SUMMARY_INFO2_PEAK_SIGNED_IDX		GENMASK(11, 0)
578c2ecf20Sopenharmony_ci#define SPECTRAL_SUMMARY_INFO2_PEAK_MAGNITUDE		GENMASK(21, 12)
588c2ecf20Sopenharmony_ci#define SPECTRAL_SUMMARY_INFO2_NARROWBAND_MASK		GENMASK(29, 22)
598c2ecf20Sopenharmony_ci#define SPECTRAL_SUMMARY_INFO2_GAIN_CHANGE		BIT(30)
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistruct spectral_tlv {
628c2ecf20Sopenharmony_ci	__le32 timestamp;
638c2ecf20Sopenharmony_ci	__le32 header;
648c2ecf20Sopenharmony_ci} __packed;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistruct spectral_summary_fft_report {
678c2ecf20Sopenharmony_ci	__le32 timestamp;
688c2ecf20Sopenharmony_ci	__le32 tlv_header;
698c2ecf20Sopenharmony_ci	__le32 info0;
708c2ecf20Sopenharmony_ci	__le32 reserve0;
718c2ecf20Sopenharmony_ci	__le32 info2;
728c2ecf20Sopenharmony_ci	__le32 reserve1;
738c2ecf20Sopenharmony_ci} __packed;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistruct ath11k_spectral_summary_report {
768c2ecf20Sopenharmony_ci	struct wmi_dma_buf_release_meta_data meta;
778c2ecf20Sopenharmony_ci	u32 timestamp;
788c2ecf20Sopenharmony_ci	u8 agc_total_gain;
798c2ecf20Sopenharmony_ci	u8 grp_idx;
808c2ecf20Sopenharmony_ci	u16 inb_pwr_db;
818c2ecf20Sopenharmony_ci	s16 peak_idx;
828c2ecf20Sopenharmony_ci	u16 peak_mag;
838c2ecf20Sopenharmony_ci	u8 detector_id;
848c2ecf20Sopenharmony_ci	bool out_of_band_flag;
858c2ecf20Sopenharmony_ci	bool rf_saturation;
868c2ecf20Sopenharmony_ci	bool primary80;
878c2ecf20Sopenharmony_ci	bool gain_change;
888c2ecf20Sopenharmony_ci	bool false_scan;
898c2ecf20Sopenharmony_ci};
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci#define SPECTRAL_FFT_REPORT_INFO0_DETECTOR_ID		GENMASK(1, 0)
928c2ecf20Sopenharmony_ci#define SPECTRAL_FFT_REPORT_INFO0_FFT_NUM		GENMASK(4, 2)
938c2ecf20Sopenharmony_ci#define SPECTRAL_FFT_REPORT_INFO0_RADAR_CHECK		GENMASK(16, 5)
948c2ecf20Sopenharmony_ci#define SPECTRAL_FFT_REPORT_INFO0_PEAK_SIGNED_IDX	GENMASK(27, 17)
958c2ecf20Sopenharmony_ci#define SPECTRAL_FFT_REPORT_INFO0_CHAIN_IDX		GENMASK(30, 28)
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci#define SPECTRAL_FFT_REPORT_INFO1_BASE_PWR_DB		GENMASK(8, 0)
988c2ecf20Sopenharmony_ci#define SPECTRAL_FFT_REPORT_INFO1_TOTAL_GAIN_DB		GENMASK(16, 9)
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci#define SPECTRAL_FFT_REPORT_INFO2_NUM_STRONG_BINS	GENMASK(7, 0)
1018c2ecf20Sopenharmony_ci#define SPECTRAL_FFT_REPORT_INFO2_PEAK_MAGNITUDE	GENMASK(17, 8)
1028c2ecf20Sopenharmony_ci#define SPECTRAL_FFT_REPORT_INFO2_AVG_PWR_DB		GENMASK(24, 18)
1038c2ecf20Sopenharmony_ci#define SPECTRAL_FFT_REPORT_INFO2_REL_PWR_DB		GENMASK(31, 25)
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_cistruct spectral_search_fft_report {
1068c2ecf20Sopenharmony_ci	__le32 timestamp;
1078c2ecf20Sopenharmony_ci	__le32 tlv_header;
1088c2ecf20Sopenharmony_ci	__le32 info0;
1098c2ecf20Sopenharmony_ci	__le32 info1;
1108c2ecf20Sopenharmony_ci	__le32 info2;
1118c2ecf20Sopenharmony_ci	__le32 reserve0;
1128c2ecf20Sopenharmony_ci	u8 bins[0];
1138c2ecf20Sopenharmony_ci} __packed;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistruct ath11k_spectral_search_report {
1168c2ecf20Sopenharmony_ci	u32 timestamp;
1178c2ecf20Sopenharmony_ci	u8 detector_id;
1188c2ecf20Sopenharmony_ci	u8 fft_count;
1198c2ecf20Sopenharmony_ci	u16 radar_check;
1208c2ecf20Sopenharmony_ci	s16 peak_idx;
1218c2ecf20Sopenharmony_ci	u8 chain_idx;
1228c2ecf20Sopenharmony_ci	u16 base_pwr_db;
1238c2ecf20Sopenharmony_ci	u8 total_gain_db;
1248c2ecf20Sopenharmony_ci	u8 strong_bin_count;
1258c2ecf20Sopenharmony_ci	u16 peak_mag;
1268c2ecf20Sopenharmony_ci	u8 avg_pwr_db;
1278c2ecf20Sopenharmony_ci	u8 rel_pwr_db;
1288c2ecf20Sopenharmony_ci};
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_cistatic struct dentry *create_buf_file_handler(const char *filename,
1318c2ecf20Sopenharmony_ci					      struct dentry *parent,
1328c2ecf20Sopenharmony_ci					      umode_t mode,
1338c2ecf20Sopenharmony_ci					      struct rchan_buf *buf,
1348c2ecf20Sopenharmony_ci					      int *is_global)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci	struct dentry *buf_file;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	buf_file = debugfs_create_file(filename, mode, parent, buf,
1398c2ecf20Sopenharmony_ci				       &relay_file_operations);
1408c2ecf20Sopenharmony_ci	*is_global = 1;
1418c2ecf20Sopenharmony_ci	return buf_file;
1428c2ecf20Sopenharmony_ci}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_cistatic int remove_buf_file_handler(struct dentry *dentry)
1458c2ecf20Sopenharmony_ci{
1468c2ecf20Sopenharmony_ci	debugfs_remove(dentry);
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	return 0;
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_cistatic struct rchan_callbacks rfs_scan_cb = {
1528c2ecf20Sopenharmony_ci	.create_buf_file = create_buf_file_handler,
1538c2ecf20Sopenharmony_ci	.remove_buf_file = remove_buf_file_handler,
1548c2ecf20Sopenharmony_ci};
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_cistatic struct ath11k_vif *ath11k_spectral_get_vdev(struct ath11k *ar)
1578c2ecf20Sopenharmony_ci{
1588c2ecf20Sopenharmony_ci	struct ath11k_vif *arvif;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	lockdep_assert_held(&ar->conf_mutex);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	if (list_empty(&ar->arvifs))
1638c2ecf20Sopenharmony_ci		return NULL;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	/* if there already is a vif doing spectral, return that. */
1668c2ecf20Sopenharmony_ci	list_for_each_entry(arvif, &ar->arvifs, list)
1678c2ecf20Sopenharmony_ci		if (arvif->spectral_enabled)
1688c2ecf20Sopenharmony_ci			return arvif;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	/* otherwise, return the first vif. */
1718c2ecf20Sopenharmony_ci	return list_first_entry(&ar->arvifs, typeof(*arvif), list);
1728c2ecf20Sopenharmony_ci}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_cistatic int ath11k_spectral_scan_trigger(struct ath11k *ar)
1758c2ecf20Sopenharmony_ci{
1768c2ecf20Sopenharmony_ci	struct ath11k_vif *arvif;
1778c2ecf20Sopenharmony_ci	int ret;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	lockdep_assert_held(&ar->conf_mutex);
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	arvif = ath11k_spectral_get_vdev(ar);
1828c2ecf20Sopenharmony_ci	if (!arvif)
1838c2ecf20Sopenharmony_ci		return -ENODEV;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	if (ar->spectral.mode == ATH11K_SPECTRAL_DISABLED)
1868c2ecf20Sopenharmony_ci		return 0;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	ret = ath11k_wmi_vdev_spectral_enable(ar, arvif->vdev_id,
1898c2ecf20Sopenharmony_ci					      ATH11K_WMI_SPECTRAL_TRIGGER_CMD_CLEAR,
1908c2ecf20Sopenharmony_ci					      ATH11K_WMI_SPECTRAL_ENABLE_CMD_ENABLE);
1918c2ecf20Sopenharmony_ci	if (ret)
1928c2ecf20Sopenharmony_ci		return ret;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	ret = ath11k_wmi_vdev_spectral_enable(ar, arvif->vdev_id,
1958c2ecf20Sopenharmony_ci					      ATH11K_WMI_SPECTRAL_TRIGGER_CMD_TRIGGER,
1968c2ecf20Sopenharmony_ci					      ATH11K_WMI_SPECTRAL_ENABLE_CMD_ENABLE);
1978c2ecf20Sopenharmony_ci	if (ret)
1988c2ecf20Sopenharmony_ci		return ret;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	return 0;
2018c2ecf20Sopenharmony_ci}
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_cistatic int ath11k_spectral_scan_config(struct ath11k *ar,
2048c2ecf20Sopenharmony_ci				       enum ath11k_spectral_mode mode)
2058c2ecf20Sopenharmony_ci{
2068c2ecf20Sopenharmony_ci	struct ath11k_wmi_vdev_spectral_conf_param param = { 0 };
2078c2ecf20Sopenharmony_ci	struct ath11k_vif *arvif;
2088c2ecf20Sopenharmony_ci	int ret, count;
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	lockdep_assert_held(&ar->conf_mutex);
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	arvif = ath11k_spectral_get_vdev(ar);
2138c2ecf20Sopenharmony_ci	if (!arvif)
2148c2ecf20Sopenharmony_ci		return -ENODEV;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	arvif->spectral_enabled = (mode != ATH11K_SPECTRAL_DISABLED);
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	spin_lock_bh(&ar->spectral.lock);
2198c2ecf20Sopenharmony_ci	ar->spectral.mode = mode;
2208c2ecf20Sopenharmony_ci	spin_unlock_bh(&ar->spectral.lock);
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	ret = ath11k_wmi_vdev_spectral_enable(ar, arvif->vdev_id,
2238c2ecf20Sopenharmony_ci					      ATH11K_WMI_SPECTRAL_TRIGGER_CMD_CLEAR,
2248c2ecf20Sopenharmony_ci					      ATH11K_WMI_SPECTRAL_ENABLE_CMD_DISABLE);
2258c2ecf20Sopenharmony_ci	if (ret) {
2268c2ecf20Sopenharmony_ci		ath11k_warn(ar->ab, "failed to enable spectral scan: %d\n", ret);
2278c2ecf20Sopenharmony_ci		return ret;
2288c2ecf20Sopenharmony_ci	}
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	if (mode == ATH11K_SPECTRAL_DISABLED)
2318c2ecf20Sopenharmony_ci		return 0;
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	if (mode == ATH11K_SPECTRAL_BACKGROUND)
2348c2ecf20Sopenharmony_ci		count = ATH11K_WMI_SPECTRAL_COUNT_DEFAULT;
2358c2ecf20Sopenharmony_ci	else
2368c2ecf20Sopenharmony_ci		count = max_t(u16, 1, ar->spectral.count);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	param.vdev_id = arvif->vdev_id;
2398c2ecf20Sopenharmony_ci	param.scan_count = count;
2408c2ecf20Sopenharmony_ci	param.scan_fft_size = ar->spectral.fft_size;
2418c2ecf20Sopenharmony_ci	param.scan_period = ATH11K_WMI_SPECTRAL_PERIOD_DEFAULT;
2428c2ecf20Sopenharmony_ci	param.scan_priority = ATH11K_WMI_SPECTRAL_PRIORITY_DEFAULT;
2438c2ecf20Sopenharmony_ci	param.scan_gc_ena = ATH11K_WMI_SPECTRAL_GC_ENA_DEFAULT;
2448c2ecf20Sopenharmony_ci	param.scan_restart_ena = ATH11K_WMI_SPECTRAL_RESTART_ENA_DEFAULT;
2458c2ecf20Sopenharmony_ci	param.scan_noise_floor_ref = ATH11K_WMI_SPECTRAL_NOISE_FLOOR_REF_DEFAULT;
2468c2ecf20Sopenharmony_ci	param.scan_init_delay = ATH11K_WMI_SPECTRAL_INIT_DELAY_DEFAULT;
2478c2ecf20Sopenharmony_ci	param.scan_nb_tone_thr = ATH11K_WMI_SPECTRAL_NB_TONE_THR_DEFAULT;
2488c2ecf20Sopenharmony_ci	param.scan_str_bin_thr = ATH11K_WMI_SPECTRAL_STR_BIN_THR_DEFAULT;
2498c2ecf20Sopenharmony_ci	param.scan_wb_rpt_mode = ATH11K_WMI_SPECTRAL_WB_RPT_MODE_DEFAULT;
2508c2ecf20Sopenharmony_ci	param.scan_rssi_rpt_mode = ATH11K_WMI_SPECTRAL_RSSI_RPT_MODE_DEFAULT;
2518c2ecf20Sopenharmony_ci	param.scan_rssi_thr = ATH11K_WMI_SPECTRAL_RSSI_THR_DEFAULT;
2528c2ecf20Sopenharmony_ci	param.scan_pwr_format = ATH11K_WMI_SPECTRAL_PWR_FORMAT_DEFAULT;
2538c2ecf20Sopenharmony_ci	param.scan_rpt_mode = ATH11K_WMI_SPECTRAL_RPT_MODE_DEFAULT;
2548c2ecf20Sopenharmony_ci	param.scan_bin_scale = ATH11K_WMI_SPECTRAL_BIN_SCALE_DEFAULT;
2558c2ecf20Sopenharmony_ci	param.scan_dbm_adj = ATH11K_WMI_SPECTRAL_DBM_ADJ_DEFAULT;
2568c2ecf20Sopenharmony_ci	param.scan_chn_mask = ATH11K_WMI_SPECTRAL_CHN_MASK_DEFAULT;
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	ret = ath11k_wmi_vdev_spectral_conf(ar, &param);
2598c2ecf20Sopenharmony_ci	if (ret) {
2608c2ecf20Sopenharmony_ci		ath11k_warn(ar->ab, "failed to configure spectral scan: %d\n", ret);
2618c2ecf20Sopenharmony_ci		return ret;
2628c2ecf20Sopenharmony_ci	}
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	return 0;
2658c2ecf20Sopenharmony_ci}
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_cistatic ssize_t ath11k_read_file_spec_scan_ctl(struct file *file,
2688c2ecf20Sopenharmony_ci					      char __user *user_buf,
2698c2ecf20Sopenharmony_ci					      size_t count, loff_t *ppos)
2708c2ecf20Sopenharmony_ci{
2718c2ecf20Sopenharmony_ci	struct ath11k *ar = file->private_data;
2728c2ecf20Sopenharmony_ci	char *mode = "";
2738c2ecf20Sopenharmony_ci	size_t len;
2748c2ecf20Sopenharmony_ci	enum ath11k_spectral_mode spectral_mode;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	mutex_lock(&ar->conf_mutex);
2778c2ecf20Sopenharmony_ci	spectral_mode = ar->spectral.mode;
2788c2ecf20Sopenharmony_ci	mutex_unlock(&ar->conf_mutex);
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	switch (spectral_mode) {
2818c2ecf20Sopenharmony_ci	case ATH11K_SPECTRAL_DISABLED:
2828c2ecf20Sopenharmony_ci		mode = "disable";
2838c2ecf20Sopenharmony_ci		break;
2848c2ecf20Sopenharmony_ci	case ATH11K_SPECTRAL_BACKGROUND:
2858c2ecf20Sopenharmony_ci		mode = "background";
2868c2ecf20Sopenharmony_ci		break;
2878c2ecf20Sopenharmony_ci	case ATH11K_SPECTRAL_MANUAL:
2888c2ecf20Sopenharmony_ci		mode = "manual";
2898c2ecf20Sopenharmony_ci		break;
2908c2ecf20Sopenharmony_ci	}
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	len = strlen(mode);
2938c2ecf20Sopenharmony_ci	return simple_read_from_buffer(user_buf, count, ppos, mode, len);
2948c2ecf20Sopenharmony_ci}
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_cistatic ssize_t ath11k_write_file_spec_scan_ctl(struct file *file,
2978c2ecf20Sopenharmony_ci					       const char __user *user_buf,
2988c2ecf20Sopenharmony_ci					       size_t count, loff_t *ppos)
2998c2ecf20Sopenharmony_ci{
3008c2ecf20Sopenharmony_ci	struct ath11k *ar = file->private_data;
3018c2ecf20Sopenharmony_ci	char buf[32];
3028c2ecf20Sopenharmony_ci	ssize_t len;
3038c2ecf20Sopenharmony_ci	int ret;
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci	len = min(count, sizeof(buf) - 1);
3068c2ecf20Sopenharmony_ci	if (copy_from_user(buf, user_buf, len))
3078c2ecf20Sopenharmony_ci		return -EFAULT;
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	buf[len] = '\0';
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci	mutex_lock(&ar->conf_mutex);
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	if (strncmp("trigger", buf, 7) == 0) {
3148c2ecf20Sopenharmony_ci		if (ar->spectral.mode == ATH11K_SPECTRAL_MANUAL ||
3158c2ecf20Sopenharmony_ci		    ar->spectral.mode == ATH11K_SPECTRAL_BACKGROUND) {
3168c2ecf20Sopenharmony_ci			/* reset the configuration to adopt possibly changed
3178c2ecf20Sopenharmony_ci			 * debugfs parameters
3188c2ecf20Sopenharmony_ci			 */
3198c2ecf20Sopenharmony_ci			ret = ath11k_spectral_scan_config(ar, ar->spectral.mode);
3208c2ecf20Sopenharmony_ci			if (ret) {
3218c2ecf20Sopenharmony_ci				ath11k_warn(ar->ab, "failed to reconfigure spectral scan: %d\n",
3228c2ecf20Sopenharmony_ci					    ret);
3238c2ecf20Sopenharmony_ci				goto unlock;
3248c2ecf20Sopenharmony_ci			}
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci			ret = ath11k_spectral_scan_trigger(ar);
3278c2ecf20Sopenharmony_ci			if (ret) {
3288c2ecf20Sopenharmony_ci				ath11k_warn(ar->ab, "failed to trigger spectral scan: %d\n",
3298c2ecf20Sopenharmony_ci					    ret);
3308c2ecf20Sopenharmony_ci			}
3318c2ecf20Sopenharmony_ci		} else {
3328c2ecf20Sopenharmony_ci			ret = -EINVAL;
3338c2ecf20Sopenharmony_ci		}
3348c2ecf20Sopenharmony_ci	} else if (strncmp("background", buf, 10) == 0) {
3358c2ecf20Sopenharmony_ci		ret = ath11k_spectral_scan_config(ar, ATH11K_SPECTRAL_BACKGROUND);
3368c2ecf20Sopenharmony_ci	} else if (strncmp("manual", buf, 6) == 0) {
3378c2ecf20Sopenharmony_ci		ret = ath11k_spectral_scan_config(ar, ATH11K_SPECTRAL_MANUAL);
3388c2ecf20Sopenharmony_ci	} else if (strncmp("disable", buf, 7) == 0) {
3398c2ecf20Sopenharmony_ci		ret = ath11k_spectral_scan_config(ar, ATH11K_SPECTRAL_DISABLED);
3408c2ecf20Sopenharmony_ci	} else {
3418c2ecf20Sopenharmony_ci		ret = -EINVAL;
3428c2ecf20Sopenharmony_ci	}
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_ciunlock:
3458c2ecf20Sopenharmony_ci	mutex_unlock(&ar->conf_mutex);
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	if (ret)
3488c2ecf20Sopenharmony_ci		return ret;
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	return count;
3518c2ecf20Sopenharmony_ci}
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_cistatic const struct file_operations fops_scan_ctl = {
3548c2ecf20Sopenharmony_ci	.read = ath11k_read_file_spec_scan_ctl,
3558c2ecf20Sopenharmony_ci	.write = ath11k_write_file_spec_scan_ctl,
3568c2ecf20Sopenharmony_ci	.open = simple_open,
3578c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
3588c2ecf20Sopenharmony_ci	.llseek = default_llseek,
3598c2ecf20Sopenharmony_ci};
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_cistatic ssize_t ath11k_read_file_spectral_count(struct file *file,
3628c2ecf20Sopenharmony_ci					       char __user *user_buf,
3638c2ecf20Sopenharmony_ci					       size_t count, loff_t *ppos)
3648c2ecf20Sopenharmony_ci{
3658c2ecf20Sopenharmony_ci	struct ath11k *ar = file->private_data;
3668c2ecf20Sopenharmony_ci	char buf[32];
3678c2ecf20Sopenharmony_ci	size_t len;
3688c2ecf20Sopenharmony_ci	u16 spectral_count;
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_ci	mutex_lock(&ar->conf_mutex);
3718c2ecf20Sopenharmony_ci	spectral_count = ar->spectral.count;
3728c2ecf20Sopenharmony_ci	mutex_unlock(&ar->conf_mutex);
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_ci	len = sprintf(buf, "%d\n", spectral_count);
3758c2ecf20Sopenharmony_ci	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
3768c2ecf20Sopenharmony_ci}
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_cistatic ssize_t ath11k_write_file_spectral_count(struct file *file,
3798c2ecf20Sopenharmony_ci						const char __user *user_buf,
3808c2ecf20Sopenharmony_ci						size_t count, loff_t *ppos)
3818c2ecf20Sopenharmony_ci{
3828c2ecf20Sopenharmony_ci	struct ath11k *ar = file->private_data;
3838c2ecf20Sopenharmony_ci	unsigned long val;
3848c2ecf20Sopenharmony_ci	char buf[32];
3858c2ecf20Sopenharmony_ci	ssize_t len;
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	len = min(count, sizeof(buf) - 1);
3888c2ecf20Sopenharmony_ci	if (copy_from_user(buf, user_buf, len))
3898c2ecf20Sopenharmony_ci		return -EFAULT;
3908c2ecf20Sopenharmony_ci
3918c2ecf20Sopenharmony_ci	buf[len] = '\0';
3928c2ecf20Sopenharmony_ci	if (kstrtoul(buf, 0, &val))
3938c2ecf20Sopenharmony_ci		return -EINVAL;
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_ci	if (val > ATH11K_SPECTRAL_SCAN_COUNT_MAX)
3968c2ecf20Sopenharmony_ci		return -EINVAL;
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_ci	mutex_lock(&ar->conf_mutex);
3998c2ecf20Sopenharmony_ci	ar->spectral.count = val;
4008c2ecf20Sopenharmony_ci	mutex_unlock(&ar->conf_mutex);
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ci	return count;
4038c2ecf20Sopenharmony_ci}
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_cistatic const struct file_operations fops_scan_count = {
4068c2ecf20Sopenharmony_ci	.read = ath11k_read_file_spectral_count,
4078c2ecf20Sopenharmony_ci	.write = ath11k_write_file_spectral_count,
4088c2ecf20Sopenharmony_ci	.open = simple_open,
4098c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
4108c2ecf20Sopenharmony_ci	.llseek = default_llseek,
4118c2ecf20Sopenharmony_ci};
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_cistatic ssize_t ath11k_read_file_spectral_bins(struct file *file,
4148c2ecf20Sopenharmony_ci					      char __user *user_buf,
4158c2ecf20Sopenharmony_ci					      size_t count, loff_t *ppos)
4168c2ecf20Sopenharmony_ci{
4178c2ecf20Sopenharmony_ci	struct ath11k *ar = file->private_data;
4188c2ecf20Sopenharmony_ci	char buf[32];
4198c2ecf20Sopenharmony_ci	unsigned int bins, fft_size;
4208c2ecf20Sopenharmony_ci	size_t len;
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_ci	mutex_lock(&ar->conf_mutex);
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci	fft_size = ar->spectral.fft_size;
4258c2ecf20Sopenharmony_ci	bins = 1 << fft_size;
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ci	mutex_unlock(&ar->conf_mutex);
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	len = sprintf(buf, "%d\n", bins);
4308c2ecf20Sopenharmony_ci	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
4318c2ecf20Sopenharmony_ci}
4328c2ecf20Sopenharmony_ci
4338c2ecf20Sopenharmony_cistatic ssize_t ath11k_write_file_spectral_bins(struct file *file,
4348c2ecf20Sopenharmony_ci					       const char __user *user_buf,
4358c2ecf20Sopenharmony_ci					       size_t count, loff_t *ppos)
4368c2ecf20Sopenharmony_ci{
4378c2ecf20Sopenharmony_ci	struct ath11k *ar = file->private_data;
4388c2ecf20Sopenharmony_ci	unsigned long val;
4398c2ecf20Sopenharmony_ci	char buf[32];
4408c2ecf20Sopenharmony_ci	ssize_t len;
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci	len = min(count, sizeof(buf) - 1);
4438c2ecf20Sopenharmony_ci	if (copy_from_user(buf, user_buf, len))
4448c2ecf20Sopenharmony_ci		return -EFAULT;
4458c2ecf20Sopenharmony_ci
4468c2ecf20Sopenharmony_ci	buf[len] = '\0';
4478c2ecf20Sopenharmony_ci	if (kstrtoul(buf, 0, &val))
4488c2ecf20Sopenharmony_ci		return -EINVAL;
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	if (val < ATH11K_SPECTRAL_ATH11K_MIN_BINS ||
4518c2ecf20Sopenharmony_ci	    val > SPECTRAL_ATH11K_MAX_NUM_BINS)
4528c2ecf20Sopenharmony_ci		return -EINVAL;
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	if (!is_power_of_2(val))
4558c2ecf20Sopenharmony_ci		return -EINVAL;
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci	mutex_lock(&ar->conf_mutex);
4588c2ecf20Sopenharmony_ci	ar->spectral.fft_size = ilog2(val);
4598c2ecf20Sopenharmony_ci	mutex_unlock(&ar->conf_mutex);
4608c2ecf20Sopenharmony_ci
4618c2ecf20Sopenharmony_ci	return count;
4628c2ecf20Sopenharmony_ci}
4638c2ecf20Sopenharmony_ci
4648c2ecf20Sopenharmony_cistatic const struct file_operations fops_scan_bins = {
4658c2ecf20Sopenharmony_ci	.read = ath11k_read_file_spectral_bins,
4668c2ecf20Sopenharmony_ci	.write = ath11k_write_file_spectral_bins,
4678c2ecf20Sopenharmony_ci	.open = simple_open,
4688c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
4698c2ecf20Sopenharmony_ci	.llseek = default_llseek,
4708c2ecf20Sopenharmony_ci};
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_cistatic int ath11k_spectral_pull_summary(struct ath11k *ar,
4738c2ecf20Sopenharmony_ci					struct wmi_dma_buf_release_meta_data *meta,
4748c2ecf20Sopenharmony_ci					struct spectral_summary_fft_report *summary,
4758c2ecf20Sopenharmony_ci					struct ath11k_spectral_summary_report *report)
4768c2ecf20Sopenharmony_ci{
4778c2ecf20Sopenharmony_ci	report->timestamp = __le32_to_cpu(summary->timestamp);
4788c2ecf20Sopenharmony_ci	report->agc_total_gain = FIELD_GET(SPECTRAL_SUMMARY_INFO0_AGC_TOTAL_GAIN,
4798c2ecf20Sopenharmony_ci					   __le32_to_cpu(summary->info0));
4808c2ecf20Sopenharmony_ci	report->out_of_band_flag = FIELD_GET(SPECTRAL_SUMMARY_INFO0_OB_FLAG,
4818c2ecf20Sopenharmony_ci					     __le32_to_cpu(summary->info0));
4828c2ecf20Sopenharmony_ci	report->grp_idx = FIELD_GET(SPECTRAL_SUMMARY_INFO0_GRP_IDX,
4838c2ecf20Sopenharmony_ci				    __le32_to_cpu(summary->info0));
4848c2ecf20Sopenharmony_ci	report->rf_saturation = FIELD_GET(SPECTRAL_SUMMARY_INFO0_RECENT_RFSAT,
4858c2ecf20Sopenharmony_ci					  __le32_to_cpu(summary->info0));
4868c2ecf20Sopenharmony_ci	report->inb_pwr_db = FIELD_GET(SPECTRAL_SUMMARY_INFO0_INBAND_PWR_DB,
4878c2ecf20Sopenharmony_ci				       __le32_to_cpu(summary->info0));
4888c2ecf20Sopenharmony_ci	report->false_scan = FIELD_GET(SPECTRAL_SUMMARY_INFO0_FALSE_SCAN,
4898c2ecf20Sopenharmony_ci				       __le32_to_cpu(summary->info0));
4908c2ecf20Sopenharmony_ci	report->detector_id = FIELD_GET(SPECTRAL_SUMMARY_INFO0_DETECTOR_ID,
4918c2ecf20Sopenharmony_ci					__le32_to_cpu(summary->info0));
4928c2ecf20Sopenharmony_ci	report->primary80 = FIELD_GET(SPECTRAL_SUMMARY_INFO0_PRI80,
4938c2ecf20Sopenharmony_ci				      __le32_to_cpu(summary->info0));
4948c2ecf20Sopenharmony_ci	report->peak_idx = FIELD_GET(SPECTRAL_SUMMARY_INFO2_PEAK_SIGNED_IDX,
4958c2ecf20Sopenharmony_ci				     __le32_to_cpu(summary->info2));
4968c2ecf20Sopenharmony_ci	report->peak_mag = FIELD_GET(SPECTRAL_SUMMARY_INFO2_PEAK_MAGNITUDE,
4978c2ecf20Sopenharmony_ci				     __le32_to_cpu(summary->info2));
4988c2ecf20Sopenharmony_ci	report->gain_change = FIELD_GET(SPECTRAL_SUMMARY_INFO2_GAIN_CHANGE,
4998c2ecf20Sopenharmony_ci					__le32_to_cpu(summary->info2));
5008c2ecf20Sopenharmony_ci
5018c2ecf20Sopenharmony_ci	memcpy(&report->meta, meta, sizeof(*meta));
5028c2ecf20Sopenharmony_ci
5038c2ecf20Sopenharmony_ci	return 0;
5048c2ecf20Sopenharmony_ci}
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_cistatic int ath11k_spectral_pull_search(struct ath11k *ar,
5078c2ecf20Sopenharmony_ci				       struct spectral_search_fft_report *search,
5088c2ecf20Sopenharmony_ci				       struct ath11k_spectral_search_report *report)
5098c2ecf20Sopenharmony_ci{
5108c2ecf20Sopenharmony_ci	report->timestamp = __le32_to_cpu(search->timestamp);
5118c2ecf20Sopenharmony_ci	report->detector_id = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_DETECTOR_ID,
5128c2ecf20Sopenharmony_ci					__le32_to_cpu(search->info0));
5138c2ecf20Sopenharmony_ci	report->fft_count = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_FFT_NUM,
5148c2ecf20Sopenharmony_ci				      __le32_to_cpu(search->info0));
5158c2ecf20Sopenharmony_ci	report->radar_check = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_RADAR_CHECK,
5168c2ecf20Sopenharmony_ci					__le32_to_cpu(search->info0));
5178c2ecf20Sopenharmony_ci	report->peak_idx = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_PEAK_SIGNED_IDX,
5188c2ecf20Sopenharmony_ci				     __le32_to_cpu(search->info0));
5198c2ecf20Sopenharmony_ci	report->chain_idx = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_CHAIN_IDX,
5208c2ecf20Sopenharmony_ci				      __le32_to_cpu(search->info0));
5218c2ecf20Sopenharmony_ci	report->base_pwr_db = FIELD_GET(SPECTRAL_FFT_REPORT_INFO1_BASE_PWR_DB,
5228c2ecf20Sopenharmony_ci					__le32_to_cpu(search->info1));
5238c2ecf20Sopenharmony_ci	report->total_gain_db = FIELD_GET(SPECTRAL_FFT_REPORT_INFO1_TOTAL_GAIN_DB,
5248c2ecf20Sopenharmony_ci					  __le32_to_cpu(search->info1));
5258c2ecf20Sopenharmony_ci	report->strong_bin_count = FIELD_GET(SPECTRAL_FFT_REPORT_INFO2_NUM_STRONG_BINS,
5268c2ecf20Sopenharmony_ci					     __le32_to_cpu(search->info2));
5278c2ecf20Sopenharmony_ci	report->peak_mag = FIELD_GET(SPECTRAL_FFT_REPORT_INFO2_PEAK_MAGNITUDE,
5288c2ecf20Sopenharmony_ci				     __le32_to_cpu(search->info2));
5298c2ecf20Sopenharmony_ci	report->avg_pwr_db = FIELD_GET(SPECTRAL_FFT_REPORT_INFO2_AVG_PWR_DB,
5308c2ecf20Sopenharmony_ci				       __le32_to_cpu(search->info2));
5318c2ecf20Sopenharmony_ci	report->rel_pwr_db = FIELD_GET(SPECTRAL_FFT_REPORT_INFO2_REL_PWR_DB,
5328c2ecf20Sopenharmony_ci				       __le32_to_cpu(search->info2));
5338c2ecf20Sopenharmony_ci
5348c2ecf20Sopenharmony_ci	return 0;
5358c2ecf20Sopenharmony_ci}
5368c2ecf20Sopenharmony_ci
5378c2ecf20Sopenharmony_cistatic u8 ath11k_spectral_get_max_exp(s8 max_index, u8 max_magnitude,
5388c2ecf20Sopenharmony_ci				      int bin_len, u8 *bins)
5398c2ecf20Sopenharmony_ci{
5408c2ecf20Sopenharmony_ci	int dc_pos;
5418c2ecf20Sopenharmony_ci	u8 max_exp;
5428c2ecf20Sopenharmony_ci
5438c2ecf20Sopenharmony_ci	dc_pos = bin_len / 2;
5448c2ecf20Sopenharmony_ci
5458c2ecf20Sopenharmony_ci	/* peak index outside of bins */
5468c2ecf20Sopenharmony_ci	if (dc_pos <= max_index || -dc_pos >= max_index)
5478c2ecf20Sopenharmony_ci		return 0;
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	for (max_exp = 0; max_exp < 8; max_exp++) {
5508c2ecf20Sopenharmony_ci		if (bins[dc_pos + max_index] == (max_magnitude >> max_exp))
5518c2ecf20Sopenharmony_ci			break;
5528c2ecf20Sopenharmony_ci	}
5538c2ecf20Sopenharmony_ci
5548c2ecf20Sopenharmony_ci	/* max_exp not found */
5558c2ecf20Sopenharmony_ci	if (bins[dc_pos + max_index] != (max_magnitude >> max_exp))
5568c2ecf20Sopenharmony_ci		return 0;
5578c2ecf20Sopenharmony_ci
5588c2ecf20Sopenharmony_ci	return max_exp;
5598c2ecf20Sopenharmony_ci}
5608c2ecf20Sopenharmony_ci
5618c2ecf20Sopenharmony_cistatic void ath11k_spectral_parse_fft(u8 *outbins, u8 *inbins, int num_bins, u8 fft_sz)
5628c2ecf20Sopenharmony_ci{
5638c2ecf20Sopenharmony_ci	int i, j;
5648c2ecf20Sopenharmony_ci
5658c2ecf20Sopenharmony_ci	i = 0;
5668c2ecf20Sopenharmony_ci	j = 0;
5678c2ecf20Sopenharmony_ci	while (i < num_bins) {
5688c2ecf20Sopenharmony_ci		outbins[i] = inbins[j];
5698c2ecf20Sopenharmony_ci		i++;
5708c2ecf20Sopenharmony_ci		j += fft_sz;
5718c2ecf20Sopenharmony_ci	}
5728c2ecf20Sopenharmony_ci}
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_cistatic
5758c2ecf20Sopenharmony_ciint ath11k_spectral_process_fft(struct ath11k *ar,
5768c2ecf20Sopenharmony_ci				struct ath11k_spectral_summary_report *summary,
5778c2ecf20Sopenharmony_ci				void *data,
5788c2ecf20Sopenharmony_ci				struct fft_sample_ath11k *fft_sample,
5798c2ecf20Sopenharmony_ci				u32 data_len)
5808c2ecf20Sopenharmony_ci{
5818c2ecf20Sopenharmony_ci	struct ath11k_base *ab = ar->ab;
5828c2ecf20Sopenharmony_ci	struct spectral_search_fft_report *fft_report = data;
5838c2ecf20Sopenharmony_ci	struct ath11k_spectral_search_report search;
5848c2ecf20Sopenharmony_ci	struct spectral_tlv *tlv;
5858c2ecf20Sopenharmony_ci	int tlv_len, bin_len, num_bins;
5868c2ecf20Sopenharmony_ci	u16 length, freq;
5878c2ecf20Sopenharmony_ci	u8 chan_width_mhz;
5888c2ecf20Sopenharmony_ci	int ret;
5898c2ecf20Sopenharmony_ci
5908c2ecf20Sopenharmony_ci	lockdep_assert_held(&ar->spectral.lock);
5918c2ecf20Sopenharmony_ci
5928c2ecf20Sopenharmony_ci	if (!ab->hw_params.spectral_fft_sz) {
5938c2ecf20Sopenharmony_ci		ath11k_warn(ab, "invalid bin size type for hw rev %d\n",
5948c2ecf20Sopenharmony_ci			    ab->hw_rev);
5958c2ecf20Sopenharmony_ci		return -EINVAL;
5968c2ecf20Sopenharmony_ci	}
5978c2ecf20Sopenharmony_ci
5988c2ecf20Sopenharmony_ci	tlv = (struct spectral_tlv *)data;
5998c2ecf20Sopenharmony_ci	tlv_len = FIELD_GET(SPECTRAL_TLV_HDR_LEN, __le32_to_cpu(tlv->header));
6008c2ecf20Sopenharmony_ci	/* convert Dword into bytes */
6018c2ecf20Sopenharmony_ci	tlv_len *= ATH11K_SPECTRAL_DWORD_SIZE;
6028c2ecf20Sopenharmony_ci	bin_len = tlv_len - (sizeof(*fft_report) - sizeof(*tlv));
6038c2ecf20Sopenharmony_ci
6048c2ecf20Sopenharmony_ci	if (data_len < (bin_len + sizeof(*fft_report))) {
6058c2ecf20Sopenharmony_ci		ath11k_warn(ab, "mismatch in expected bin len %d and data len %d\n",
6068c2ecf20Sopenharmony_ci			    bin_len, data_len);
6078c2ecf20Sopenharmony_ci		return -EINVAL;
6088c2ecf20Sopenharmony_ci	}
6098c2ecf20Sopenharmony_ci
6108c2ecf20Sopenharmony_ci	num_bins = bin_len / ATH11K_SPECTRAL_BIN_SIZE;
6118c2ecf20Sopenharmony_ci	/* Only In-band bins are useful to user for visualize */
6128c2ecf20Sopenharmony_ci	num_bins >>= 1;
6138c2ecf20Sopenharmony_ci
6148c2ecf20Sopenharmony_ci	if (num_bins < ATH11K_SPECTRAL_ATH11K_MIN_IB_BINS ||
6158c2ecf20Sopenharmony_ci	    num_bins > ATH11K_SPECTRAL_ATH11K_MAX_IB_BINS ||
6168c2ecf20Sopenharmony_ci	    !is_power_of_2(num_bins)) {
6178c2ecf20Sopenharmony_ci		ath11k_warn(ab, "Invalid num of bins %d\n", num_bins);
6188c2ecf20Sopenharmony_ci		return -EINVAL;
6198c2ecf20Sopenharmony_ci	}
6208c2ecf20Sopenharmony_ci
6218c2ecf20Sopenharmony_ci	ret = ath11k_spectral_pull_search(ar, data, &search);
6228c2ecf20Sopenharmony_ci	if (ret) {
6238c2ecf20Sopenharmony_ci		ath11k_warn(ab, "failed to pull search report %d\n", ret);
6248c2ecf20Sopenharmony_ci		return ret;
6258c2ecf20Sopenharmony_ci	}
6268c2ecf20Sopenharmony_ci
6278c2ecf20Sopenharmony_ci	chan_width_mhz = summary->meta.ch_width;
6288c2ecf20Sopenharmony_ci
6298c2ecf20Sopenharmony_ci	switch (chan_width_mhz) {
6308c2ecf20Sopenharmony_ci	case ATH11K_SPECTRAL_20MHZ:
6318c2ecf20Sopenharmony_ci	case ATH11K_SPECTRAL_40MHZ:
6328c2ecf20Sopenharmony_ci	case ATH11K_SPECTRAL_80MHZ:
6338c2ecf20Sopenharmony_ci		fft_sample->chan_width_mhz = chan_width_mhz;
6348c2ecf20Sopenharmony_ci		break;
6358c2ecf20Sopenharmony_ci	default:
6368c2ecf20Sopenharmony_ci		ath11k_warn(ab, "invalid channel width %d\n", chan_width_mhz);
6378c2ecf20Sopenharmony_ci		return -EINVAL;
6388c2ecf20Sopenharmony_ci	}
6398c2ecf20Sopenharmony_ci
6408c2ecf20Sopenharmony_ci	length = sizeof(*fft_sample) - sizeof(struct fft_sample_tlv) + num_bins;
6418c2ecf20Sopenharmony_ci	fft_sample->tlv.type = ATH_FFT_SAMPLE_ATH11K;
6428c2ecf20Sopenharmony_ci	fft_sample->tlv.length = __cpu_to_be16(length);
6438c2ecf20Sopenharmony_ci
6448c2ecf20Sopenharmony_ci	fft_sample->tsf = __cpu_to_be32(search.timestamp);
6458c2ecf20Sopenharmony_ci	fft_sample->max_magnitude = __cpu_to_be16(search.peak_mag);
6468c2ecf20Sopenharmony_ci	fft_sample->max_index = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_PEAK_SIGNED_IDX,
6478c2ecf20Sopenharmony_ci					  __le32_to_cpu(fft_report->info0));
6488c2ecf20Sopenharmony_ci
6498c2ecf20Sopenharmony_ci	summary->inb_pwr_db >>= 1;
6508c2ecf20Sopenharmony_ci	fft_sample->rssi = __cpu_to_be16(summary->inb_pwr_db);
6518c2ecf20Sopenharmony_ci	fft_sample->noise = __cpu_to_be32(summary->meta.noise_floor[search.chain_idx]);
6528c2ecf20Sopenharmony_ci
6538c2ecf20Sopenharmony_ci	freq = summary->meta.freq1;
6548c2ecf20Sopenharmony_ci	fft_sample->freq1 = __cpu_to_be16(freq);
6558c2ecf20Sopenharmony_ci
6568c2ecf20Sopenharmony_ci	freq = summary->meta.freq2;
6578c2ecf20Sopenharmony_ci	fft_sample->freq2 = __cpu_to_be16(freq);
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_ci	ath11k_spectral_parse_fft(fft_sample->data, fft_report->bins, num_bins,
6608c2ecf20Sopenharmony_ci				  ab->hw_params.spectral_fft_sz);
6618c2ecf20Sopenharmony_ci
6628c2ecf20Sopenharmony_ci	fft_sample->max_exp = ath11k_spectral_get_max_exp(fft_sample->max_index,
6638c2ecf20Sopenharmony_ci							  search.peak_mag,
6648c2ecf20Sopenharmony_ci							  num_bins,
6658c2ecf20Sopenharmony_ci							  fft_sample->data);
6668c2ecf20Sopenharmony_ci
6678c2ecf20Sopenharmony_ci	if (ar->spectral.rfs_scan)
6688c2ecf20Sopenharmony_ci		relay_write(ar->spectral.rfs_scan, fft_sample,
6698c2ecf20Sopenharmony_ci			    length + sizeof(struct fft_sample_tlv));
6708c2ecf20Sopenharmony_ci
6718c2ecf20Sopenharmony_ci	return 0;
6728c2ecf20Sopenharmony_ci}
6738c2ecf20Sopenharmony_ci
6748c2ecf20Sopenharmony_cistatic int ath11k_spectral_process_data(struct ath11k *ar,
6758c2ecf20Sopenharmony_ci					struct ath11k_dbring_data *param)
6768c2ecf20Sopenharmony_ci{
6778c2ecf20Sopenharmony_ci	struct ath11k_base *ab = ar->ab;
6788c2ecf20Sopenharmony_ci	struct spectral_tlv *tlv;
6798c2ecf20Sopenharmony_ci	struct spectral_summary_fft_report *summary = NULL;
6808c2ecf20Sopenharmony_ci	struct ath11k_spectral_summary_report summ_rpt;
6818c2ecf20Sopenharmony_ci	struct fft_sample_ath11k *fft_sample = NULL;
6828c2ecf20Sopenharmony_ci	u8 *data;
6838c2ecf20Sopenharmony_ci	u32 data_len, i;
6848c2ecf20Sopenharmony_ci	u8 sign, tag;
6858c2ecf20Sopenharmony_ci	int tlv_len, sample_sz;
6868c2ecf20Sopenharmony_ci	int ret;
6878c2ecf20Sopenharmony_ci	bool quit = false;
6888c2ecf20Sopenharmony_ci
6898c2ecf20Sopenharmony_ci	spin_lock_bh(&ar->spectral.lock);
6908c2ecf20Sopenharmony_ci
6918c2ecf20Sopenharmony_ci	if (!ar->spectral.enabled) {
6928c2ecf20Sopenharmony_ci		ret = -EINVAL;
6938c2ecf20Sopenharmony_ci		goto unlock;
6948c2ecf20Sopenharmony_ci	}
6958c2ecf20Sopenharmony_ci
6968c2ecf20Sopenharmony_ci	sample_sz = sizeof(*fft_sample) + ATH11K_SPECTRAL_ATH11K_MAX_IB_BINS;
6978c2ecf20Sopenharmony_ci	fft_sample = kmalloc(sample_sz, GFP_ATOMIC);
6988c2ecf20Sopenharmony_ci	if (!fft_sample) {
6998c2ecf20Sopenharmony_ci		ret = -ENOBUFS;
7008c2ecf20Sopenharmony_ci		goto unlock;
7018c2ecf20Sopenharmony_ci	}
7028c2ecf20Sopenharmony_ci
7038c2ecf20Sopenharmony_ci	data = param->data;
7048c2ecf20Sopenharmony_ci	data_len = param->data_sz;
7058c2ecf20Sopenharmony_ci	i = 0;
7068c2ecf20Sopenharmony_ci	while (!quit && (i < data_len)) {
7078c2ecf20Sopenharmony_ci		if ((i + sizeof(*tlv)) > data_len) {
7088c2ecf20Sopenharmony_ci			ath11k_warn(ab, "failed to parse spectral tlv hdr at bytes %d\n",
7098c2ecf20Sopenharmony_ci				    i);
7108c2ecf20Sopenharmony_ci			ret = -EINVAL;
7118c2ecf20Sopenharmony_ci			goto err;
7128c2ecf20Sopenharmony_ci		}
7138c2ecf20Sopenharmony_ci
7148c2ecf20Sopenharmony_ci		tlv = (struct spectral_tlv *)&data[i];
7158c2ecf20Sopenharmony_ci		sign = FIELD_GET(SPECTRAL_TLV_HDR_SIGN,
7168c2ecf20Sopenharmony_ci				 __le32_to_cpu(tlv->header));
7178c2ecf20Sopenharmony_ci		if (sign != ATH11K_SPECTRAL_SIGNATURE) {
7188c2ecf20Sopenharmony_ci			ath11k_warn(ab, "Invalid sign 0x%x at bytes %d\n",
7198c2ecf20Sopenharmony_ci				    sign, i);
7208c2ecf20Sopenharmony_ci			ret = -EINVAL;
7218c2ecf20Sopenharmony_ci			goto err;
7228c2ecf20Sopenharmony_ci		}
7238c2ecf20Sopenharmony_ci
7248c2ecf20Sopenharmony_ci		tlv_len = FIELD_GET(SPECTRAL_TLV_HDR_LEN,
7258c2ecf20Sopenharmony_ci				    __le32_to_cpu(tlv->header));
7268c2ecf20Sopenharmony_ci		/* convert Dword into bytes */
7278c2ecf20Sopenharmony_ci		tlv_len *= ATH11K_SPECTRAL_DWORD_SIZE;
7288c2ecf20Sopenharmony_ci		if ((i + sizeof(*tlv) + tlv_len) > data_len) {
7298c2ecf20Sopenharmony_ci			ath11k_warn(ab, "failed to parse spectral tlv payload at bytes %d tlv_len:%d data_len:%d\n",
7308c2ecf20Sopenharmony_ci				    i, tlv_len, data_len);
7318c2ecf20Sopenharmony_ci			ret = -EINVAL;
7328c2ecf20Sopenharmony_ci			goto err;
7338c2ecf20Sopenharmony_ci		}
7348c2ecf20Sopenharmony_ci
7358c2ecf20Sopenharmony_ci		tag = FIELD_GET(SPECTRAL_TLV_HDR_TAG,
7368c2ecf20Sopenharmony_ci				__le32_to_cpu(tlv->header));
7378c2ecf20Sopenharmony_ci		switch (tag) {
7388c2ecf20Sopenharmony_ci		case ATH11K_SPECTRAL_TAG_SCAN_SUMMARY:
7398c2ecf20Sopenharmony_ci			/* HW bug in tlv length of summary report,
7408c2ecf20Sopenharmony_ci			 * HW report 3 DWORD size but the data payload
7418c2ecf20Sopenharmony_ci			 * is 4 DWORD size (16 bytes).
7428c2ecf20Sopenharmony_ci			 * Need to remove this workaround once HW bug fixed
7438c2ecf20Sopenharmony_ci			 */
7448c2ecf20Sopenharmony_ci			tlv_len = sizeof(*summary) - sizeof(*tlv);
7458c2ecf20Sopenharmony_ci
7468c2ecf20Sopenharmony_ci			if (tlv_len < (sizeof(*summary) - sizeof(*tlv))) {
7478c2ecf20Sopenharmony_ci				ath11k_warn(ab, "failed to parse spectral summary at bytes %d tlv_len:%d\n",
7488c2ecf20Sopenharmony_ci					    i, tlv_len);
7498c2ecf20Sopenharmony_ci				ret = -EINVAL;
7508c2ecf20Sopenharmony_ci				goto err;
7518c2ecf20Sopenharmony_ci			}
7528c2ecf20Sopenharmony_ci
7538c2ecf20Sopenharmony_ci			summary = (struct spectral_summary_fft_report *)tlv;
7548c2ecf20Sopenharmony_ci			ath11k_spectral_pull_summary(ar, &param->meta,
7558c2ecf20Sopenharmony_ci						     summary, &summ_rpt);
7568c2ecf20Sopenharmony_ci			break;
7578c2ecf20Sopenharmony_ci		case ATH11K_SPECTRAL_TAG_SCAN_SEARCH:
7588c2ecf20Sopenharmony_ci			if (tlv_len < (sizeof(struct spectral_search_fft_report) -
7598c2ecf20Sopenharmony_ci				       sizeof(*tlv))) {
7608c2ecf20Sopenharmony_ci				ath11k_warn(ab, "failed to parse spectral search fft at bytes %d\n",
7618c2ecf20Sopenharmony_ci					    i);
7628c2ecf20Sopenharmony_ci				ret = -EINVAL;
7638c2ecf20Sopenharmony_ci				goto err;
7648c2ecf20Sopenharmony_ci			}
7658c2ecf20Sopenharmony_ci
7668c2ecf20Sopenharmony_ci			memset(fft_sample, 0, sample_sz);
7678c2ecf20Sopenharmony_ci			ret = ath11k_spectral_process_fft(ar, &summ_rpt, tlv,
7688c2ecf20Sopenharmony_ci							  fft_sample,
7698c2ecf20Sopenharmony_ci							  data_len - i);
7708c2ecf20Sopenharmony_ci			if (ret) {
7718c2ecf20Sopenharmony_ci				ath11k_warn(ab, "failed to process spectral fft at bytes %d\n",
7728c2ecf20Sopenharmony_ci					    i);
7738c2ecf20Sopenharmony_ci				goto err;
7748c2ecf20Sopenharmony_ci			}
7758c2ecf20Sopenharmony_ci			quit = true;
7768c2ecf20Sopenharmony_ci			break;
7778c2ecf20Sopenharmony_ci		}
7788c2ecf20Sopenharmony_ci
7798c2ecf20Sopenharmony_ci		i += sizeof(*tlv) + tlv_len;
7808c2ecf20Sopenharmony_ci	}
7818c2ecf20Sopenharmony_ci
7828c2ecf20Sopenharmony_ci	ret = 0;
7838c2ecf20Sopenharmony_ci
7848c2ecf20Sopenharmony_cierr:
7858c2ecf20Sopenharmony_ci	kfree(fft_sample);
7868c2ecf20Sopenharmony_ciunlock:
7878c2ecf20Sopenharmony_ci	spin_unlock_bh(&ar->spectral.lock);
7888c2ecf20Sopenharmony_ci	return ret;
7898c2ecf20Sopenharmony_ci}
7908c2ecf20Sopenharmony_ci
7918c2ecf20Sopenharmony_cistatic int ath11k_spectral_ring_alloc(struct ath11k *ar,
7928c2ecf20Sopenharmony_ci				      struct ath11k_dbring_cap *db_cap)
7938c2ecf20Sopenharmony_ci{
7948c2ecf20Sopenharmony_ci	struct ath11k_spectral *sp = &ar->spectral;
7958c2ecf20Sopenharmony_ci	int ret;
7968c2ecf20Sopenharmony_ci
7978c2ecf20Sopenharmony_ci	ret = ath11k_dbring_srng_setup(ar, &sp->rx_ring,
7988c2ecf20Sopenharmony_ci				       0, db_cap->min_elem);
7998c2ecf20Sopenharmony_ci	if (ret) {
8008c2ecf20Sopenharmony_ci		ath11k_warn(ar->ab, "failed to setup db ring\n");
8018c2ecf20Sopenharmony_ci		return ret;
8028c2ecf20Sopenharmony_ci	}
8038c2ecf20Sopenharmony_ci
8048c2ecf20Sopenharmony_ci	ath11k_dbring_set_cfg(ar, &sp->rx_ring,
8058c2ecf20Sopenharmony_ci			      ATH11K_SPECTRAL_NUM_RESP_PER_EVENT,
8068c2ecf20Sopenharmony_ci			      ATH11K_SPECTRAL_EVENT_TIMEOUT_MS,
8078c2ecf20Sopenharmony_ci			      ath11k_spectral_process_data);
8088c2ecf20Sopenharmony_ci
8098c2ecf20Sopenharmony_ci	ret = ath11k_dbring_buf_setup(ar, &sp->rx_ring, db_cap);
8108c2ecf20Sopenharmony_ci	if (ret) {
8118c2ecf20Sopenharmony_ci		ath11k_warn(ar->ab, "failed to setup db ring buffer\n");
8128c2ecf20Sopenharmony_ci		goto srng_cleanup;
8138c2ecf20Sopenharmony_ci	}
8148c2ecf20Sopenharmony_ci
8158c2ecf20Sopenharmony_ci	ret = ath11k_dbring_wmi_cfg_setup(ar, &sp->rx_ring,
8168c2ecf20Sopenharmony_ci					  WMI_DIRECT_BUF_SPECTRAL);
8178c2ecf20Sopenharmony_ci	if (ret) {
8188c2ecf20Sopenharmony_ci		ath11k_warn(ar->ab, "failed to setup db ring cfg\n");
8198c2ecf20Sopenharmony_ci		goto buffer_cleanup;
8208c2ecf20Sopenharmony_ci	}
8218c2ecf20Sopenharmony_ci
8228c2ecf20Sopenharmony_ci	return 0;
8238c2ecf20Sopenharmony_ci
8248c2ecf20Sopenharmony_cibuffer_cleanup:
8258c2ecf20Sopenharmony_ci	ath11k_dbring_buf_cleanup(ar, &sp->rx_ring);
8268c2ecf20Sopenharmony_cisrng_cleanup:
8278c2ecf20Sopenharmony_ci	ath11k_dbring_srng_cleanup(ar, &sp->rx_ring);
8288c2ecf20Sopenharmony_ci	return ret;
8298c2ecf20Sopenharmony_ci}
8308c2ecf20Sopenharmony_ci
8318c2ecf20Sopenharmony_cistatic inline void ath11k_spectral_ring_free(struct ath11k *ar)
8328c2ecf20Sopenharmony_ci{
8338c2ecf20Sopenharmony_ci	struct ath11k_spectral *sp = &ar->spectral;
8348c2ecf20Sopenharmony_ci
8358c2ecf20Sopenharmony_ci	ath11k_dbring_srng_cleanup(ar, &sp->rx_ring);
8368c2ecf20Sopenharmony_ci	ath11k_dbring_buf_cleanup(ar, &sp->rx_ring);
8378c2ecf20Sopenharmony_ci}
8388c2ecf20Sopenharmony_ci
8398c2ecf20Sopenharmony_cistatic inline void ath11k_spectral_debug_unregister(struct ath11k *ar)
8408c2ecf20Sopenharmony_ci{
8418c2ecf20Sopenharmony_ci	debugfs_remove(ar->spectral.scan_bins);
8428c2ecf20Sopenharmony_ci	ar->spectral.scan_bins = NULL;
8438c2ecf20Sopenharmony_ci
8448c2ecf20Sopenharmony_ci	debugfs_remove(ar->spectral.scan_count);
8458c2ecf20Sopenharmony_ci	ar->spectral.scan_count = NULL;
8468c2ecf20Sopenharmony_ci
8478c2ecf20Sopenharmony_ci	debugfs_remove(ar->spectral.scan_ctl);
8488c2ecf20Sopenharmony_ci	ar->spectral.scan_ctl = NULL;
8498c2ecf20Sopenharmony_ci
8508c2ecf20Sopenharmony_ci	if (ar->spectral.rfs_scan) {
8518c2ecf20Sopenharmony_ci		relay_close(ar->spectral.rfs_scan);
8528c2ecf20Sopenharmony_ci		ar->spectral.rfs_scan = NULL;
8538c2ecf20Sopenharmony_ci	}
8548c2ecf20Sopenharmony_ci}
8558c2ecf20Sopenharmony_ci
8568c2ecf20Sopenharmony_ciint ath11k_spectral_vif_stop(struct ath11k_vif *arvif)
8578c2ecf20Sopenharmony_ci{
8588c2ecf20Sopenharmony_ci	if (!arvif->spectral_enabled)
8598c2ecf20Sopenharmony_ci		return 0;
8608c2ecf20Sopenharmony_ci
8618c2ecf20Sopenharmony_ci	return ath11k_spectral_scan_config(arvif->ar, ATH11K_SPECTRAL_DISABLED);
8628c2ecf20Sopenharmony_ci}
8638c2ecf20Sopenharmony_ci
8648c2ecf20Sopenharmony_civoid ath11k_spectral_reset_buffer(struct ath11k *ar)
8658c2ecf20Sopenharmony_ci{
8668c2ecf20Sopenharmony_ci	if (!ar->spectral.enabled)
8678c2ecf20Sopenharmony_ci		return;
8688c2ecf20Sopenharmony_ci
8698c2ecf20Sopenharmony_ci	if (ar->spectral.rfs_scan)
8708c2ecf20Sopenharmony_ci		relay_reset(ar->spectral.rfs_scan);
8718c2ecf20Sopenharmony_ci}
8728c2ecf20Sopenharmony_ci
8738c2ecf20Sopenharmony_civoid ath11k_spectral_deinit(struct ath11k_base *ab)
8748c2ecf20Sopenharmony_ci{
8758c2ecf20Sopenharmony_ci	struct ath11k *ar;
8768c2ecf20Sopenharmony_ci	struct ath11k_spectral *sp;
8778c2ecf20Sopenharmony_ci	int i;
8788c2ecf20Sopenharmony_ci
8798c2ecf20Sopenharmony_ci	for (i = 0; i <  ab->num_radios; i++) {
8808c2ecf20Sopenharmony_ci		ar = ab->pdevs[i].ar;
8818c2ecf20Sopenharmony_ci		sp = &ar->spectral;
8828c2ecf20Sopenharmony_ci
8838c2ecf20Sopenharmony_ci		if (!sp->enabled)
8848c2ecf20Sopenharmony_ci			continue;
8858c2ecf20Sopenharmony_ci
8868c2ecf20Sopenharmony_ci		mutex_lock(&ar->conf_mutex);
8878c2ecf20Sopenharmony_ci		ath11k_spectral_scan_config(ar, ATH11K_SPECTRAL_DISABLED);
8888c2ecf20Sopenharmony_ci		mutex_unlock(&ar->conf_mutex);
8898c2ecf20Sopenharmony_ci
8908c2ecf20Sopenharmony_ci		spin_lock_bh(&sp->lock);
8918c2ecf20Sopenharmony_ci		sp->enabled = false;
8928c2ecf20Sopenharmony_ci		spin_unlock_bh(&sp->lock);
8938c2ecf20Sopenharmony_ci
8948c2ecf20Sopenharmony_ci		ath11k_spectral_debug_unregister(ar);
8958c2ecf20Sopenharmony_ci		ath11k_spectral_ring_free(ar);
8968c2ecf20Sopenharmony_ci	}
8978c2ecf20Sopenharmony_ci}
8988c2ecf20Sopenharmony_ci
8998c2ecf20Sopenharmony_cistatic inline int ath11k_spectral_debug_register(struct ath11k *ar)
9008c2ecf20Sopenharmony_ci{
9018c2ecf20Sopenharmony_ci	int ret;
9028c2ecf20Sopenharmony_ci
9038c2ecf20Sopenharmony_ci	ar->spectral.rfs_scan = relay_open("spectral_scan",
9048c2ecf20Sopenharmony_ci					   ar->debug.debugfs_pdev,
9058c2ecf20Sopenharmony_ci					   ATH11K_SPECTRAL_SUB_BUFF_SIZE,
9068c2ecf20Sopenharmony_ci					   ATH11K_SPECTRAL_NUM_SUB_BUF,
9078c2ecf20Sopenharmony_ci					   &rfs_scan_cb, NULL);
9088c2ecf20Sopenharmony_ci	if (!ar->spectral.rfs_scan) {
9098c2ecf20Sopenharmony_ci		ath11k_warn(ar->ab, "failed to open relay in pdev %d\n",
9108c2ecf20Sopenharmony_ci			    ar->pdev_idx);
9118c2ecf20Sopenharmony_ci		return -EINVAL;
9128c2ecf20Sopenharmony_ci	}
9138c2ecf20Sopenharmony_ci
9148c2ecf20Sopenharmony_ci	ar->spectral.scan_ctl = debugfs_create_file("spectral_scan_ctl",
9158c2ecf20Sopenharmony_ci						    0600,
9168c2ecf20Sopenharmony_ci						    ar->debug.debugfs_pdev, ar,
9178c2ecf20Sopenharmony_ci						    &fops_scan_ctl);
9188c2ecf20Sopenharmony_ci	if (!ar->spectral.scan_ctl) {
9198c2ecf20Sopenharmony_ci		ath11k_warn(ar->ab, "failed to open debugfs in pdev %d\n",
9208c2ecf20Sopenharmony_ci			    ar->pdev_idx);
9218c2ecf20Sopenharmony_ci		ret = -EINVAL;
9228c2ecf20Sopenharmony_ci		goto debug_unregister;
9238c2ecf20Sopenharmony_ci	}
9248c2ecf20Sopenharmony_ci
9258c2ecf20Sopenharmony_ci	ar->spectral.scan_count = debugfs_create_file("spectral_count",
9268c2ecf20Sopenharmony_ci						      0600,
9278c2ecf20Sopenharmony_ci						      ar->debug.debugfs_pdev, ar,
9288c2ecf20Sopenharmony_ci						      &fops_scan_count);
9298c2ecf20Sopenharmony_ci	if (!ar->spectral.scan_count) {
9308c2ecf20Sopenharmony_ci		ath11k_warn(ar->ab, "failed to open debugfs in pdev %d\n",
9318c2ecf20Sopenharmony_ci			    ar->pdev_idx);
9328c2ecf20Sopenharmony_ci		ret = -EINVAL;
9338c2ecf20Sopenharmony_ci		goto debug_unregister;
9348c2ecf20Sopenharmony_ci	}
9358c2ecf20Sopenharmony_ci
9368c2ecf20Sopenharmony_ci	ar->spectral.scan_bins = debugfs_create_file("spectral_bins",
9378c2ecf20Sopenharmony_ci						     0600,
9388c2ecf20Sopenharmony_ci						     ar->debug.debugfs_pdev, ar,
9398c2ecf20Sopenharmony_ci						     &fops_scan_bins);
9408c2ecf20Sopenharmony_ci	if (!ar->spectral.scan_bins) {
9418c2ecf20Sopenharmony_ci		ath11k_warn(ar->ab, "failed to open debugfs in pdev %d\n",
9428c2ecf20Sopenharmony_ci			    ar->pdev_idx);
9438c2ecf20Sopenharmony_ci		ret = -EINVAL;
9448c2ecf20Sopenharmony_ci		goto debug_unregister;
9458c2ecf20Sopenharmony_ci	}
9468c2ecf20Sopenharmony_ci
9478c2ecf20Sopenharmony_ci	return 0;
9488c2ecf20Sopenharmony_ci
9498c2ecf20Sopenharmony_cidebug_unregister:
9508c2ecf20Sopenharmony_ci	ath11k_spectral_debug_unregister(ar);
9518c2ecf20Sopenharmony_ci	return ret;
9528c2ecf20Sopenharmony_ci}
9538c2ecf20Sopenharmony_ci
9548c2ecf20Sopenharmony_ciint ath11k_spectral_init(struct ath11k_base *ab)
9558c2ecf20Sopenharmony_ci{
9568c2ecf20Sopenharmony_ci	struct ath11k *ar;
9578c2ecf20Sopenharmony_ci	struct ath11k_spectral *sp;
9588c2ecf20Sopenharmony_ci	struct ath11k_dbring_cap db_cap;
9598c2ecf20Sopenharmony_ci	int ret;
9608c2ecf20Sopenharmony_ci	int i;
9618c2ecf20Sopenharmony_ci
9628c2ecf20Sopenharmony_ci	if (!test_bit(WMI_TLV_SERVICE_FREQINFO_IN_METADATA,
9638c2ecf20Sopenharmony_ci		      ab->wmi_ab.svc_map))
9648c2ecf20Sopenharmony_ci		return 0;
9658c2ecf20Sopenharmony_ci
9668c2ecf20Sopenharmony_ci	if (!ab->hw_params.spectral_fft_sz)
9678c2ecf20Sopenharmony_ci		return 0;
9688c2ecf20Sopenharmony_ci
9698c2ecf20Sopenharmony_ci	for (i = 0; i < ab->num_radios; i++) {
9708c2ecf20Sopenharmony_ci		ar = ab->pdevs[i].ar;
9718c2ecf20Sopenharmony_ci		sp = &ar->spectral;
9728c2ecf20Sopenharmony_ci
9738c2ecf20Sopenharmony_ci		ret = ath11k_dbring_get_cap(ar->ab, ar->pdev_idx,
9748c2ecf20Sopenharmony_ci					    WMI_DIRECT_BUF_SPECTRAL,
9758c2ecf20Sopenharmony_ci					    &db_cap);
9768c2ecf20Sopenharmony_ci		if (ret)
9778c2ecf20Sopenharmony_ci			continue;
9788c2ecf20Sopenharmony_ci
9798c2ecf20Sopenharmony_ci		idr_init(&sp->rx_ring.bufs_idr);
9808c2ecf20Sopenharmony_ci		spin_lock_init(&sp->rx_ring.idr_lock);
9818c2ecf20Sopenharmony_ci		spin_lock_init(&sp->lock);
9828c2ecf20Sopenharmony_ci
9838c2ecf20Sopenharmony_ci		ret = ath11k_spectral_ring_alloc(ar, &db_cap);
9848c2ecf20Sopenharmony_ci		if (ret) {
9858c2ecf20Sopenharmony_ci			ath11k_warn(ab, "failed to init spectral ring for pdev %d\n",
9868c2ecf20Sopenharmony_ci				    i);
9878c2ecf20Sopenharmony_ci			goto deinit;
9888c2ecf20Sopenharmony_ci		}
9898c2ecf20Sopenharmony_ci
9908c2ecf20Sopenharmony_ci		spin_lock_bh(&sp->lock);
9918c2ecf20Sopenharmony_ci
9928c2ecf20Sopenharmony_ci		sp->mode = ATH11K_SPECTRAL_DISABLED;
9938c2ecf20Sopenharmony_ci		sp->count = ATH11K_WMI_SPECTRAL_COUNT_DEFAULT;
9948c2ecf20Sopenharmony_ci		sp->fft_size = ATH11K_WMI_SPECTRAL_FFT_SIZE_DEFAULT;
9958c2ecf20Sopenharmony_ci		sp->enabled = true;
9968c2ecf20Sopenharmony_ci
9978c2ecf20Sopenharmony_ci		spin_unlock_bh(&sp->lock);
9988c2ecf20Sopenharmony_ci
9998c2ecf20Sopenharmony_ci		ret = ath11k_spectral_debug_register(ar);
10008c2ecf20Sopenharmony_ci		if (ret) {
10018c2ecf20Sopenharmony_ci			ath11k_warn(ab, "failed to register spectral for pdev %d\n",
10028c2ecf20Sopenharmony_ci				    i);
10038c2ecf20Sopenharmony_ci			goto deinit;
10048c2ecf20Sopenharmony_ci		}
10058c2ecf20Sopenharmony_ci	}
10068c2ecf20Sopenharmony_ci
10078c2ecf20Sopenharmony_ci	return 0;
10088c2ecf20Sopenharmony_ci
10098c2ecf20Sopenharmony_cideinit:
10108c2ecf20Sopenharmony_ci	ath11k_spectral_deinit(ab);
10118c2ecf20Sopenharmony_ci	return ret;
10128c2ecf20Sopenharmony_ci}
10138c2ecf20Sopenharmony_ci
10148c2ecf20Sopenharmony_cienum ath11k_spectral_mode ath11k_spectral_get_mode(struct ath11k *ar)
10158c2ecf20Sopenharmony_ci{
10168c2ecf20Sopenharmony_ci	if (ar->spectral.enabled)
10178c2ecf20Sopenharmony_ci		return ar->spectral.mode;
10188c2ecf20Sopenharmony_ci	else
10198c2ecf20Sopenharmony_ci		return ATH11K_SPECTRAL_DISABLED;
10208c2ecf20Sopenharmony_ci}
10218c2ecf20Sopenharmony_ci
10228c2ecf20Sopenharmony_cistruct ath11k_dbring *ath11k_spectral_get_dbring(struct ath11k *ar)
10238c2ecf20Sopenharmony_ci{
10248c2ecf20Sopenharmony_ci	if (ar->spectral.enabled)
10258c2ecf20Sopenharmony_ci		return &ar->spectral.rx_ring;
10268c2ecf20Sopenharmony_ci	else
10278c2ecf20Sopenharmony_ci		return NULL;
10288c2ecf20Sopenharmony_ci}
1029