18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: ISC
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2013-2017 Qualcomm Atheros, Inc.
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/relay.h>
78c2ecf20Sopenharmony_ci#include "core.h"
88c2ecf20Sopenharmony_ci#include "debug.h"
98c2ecf20Sopenharmony_ci#include "wmi-ops.h"
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_cistatic void send_fft_sample(struct ath10k *ar,
128c2ecf20Sopenharmony_ci			    const struct fft_sample_tlv *fft_sample_tlv)
138c2ecf20Sopenharmony_ci{
148c2ecf20Sopenharmony_ci	int length;
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci	if (!ar->spectral.rfs_chan_spec_scan)
178c2ecf20Sopenharmony_ci		return;
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci	length = __be16_to_cpu(fft_sample_tlv->length) +
208c2ecf20Sopenharmony_ci		 sizeof(*fft_sample_tlv);
218c2ecf20Sopenharmony_ci	relay_write(ar->spectral.rfs_chan_spec_scan, fft_sample_tlv, length);
228c2ecf20Sopenharmony_ci}
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistatic uint8_t get_max_exp(s8 max_index, u16 max_magnitude, size_t bin_len,
258c2ecf20Sopenharmony_ci			   u8 *data)
268c2ecf20Sopenharmony_ci{
278c2ecf20Sopenharmony_ci	int dc_pos;
288c2ecf20Sopenharmony_ci	u8 max_exp;
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	dc_pos = bin_len / 2;
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	/* peak index outside of bins */
338c2ecf20Sopenharmony_ci	if (dc_pos < max_index || -dc_pos >= max_index)
348c2ecf20Sopenharmony_ci		return 0;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	for (max_exp = 0; max_exp < 8; max_exp++) {
378c2ecf20Sopenharmony_ci		if (data[dc_pos + max_index] == (max_magnitude >> max_exp))
388c2ecf20Sopenharmony_ci			break;
398c2ecf20Sopenharmony_ci	}
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	/* max_exp not found */
428c2ecf20Sopenharmony_ci	if (data[dc_pos + max_index] != (max_magnitude >> max_exp))
438c2ecf20Sopenharmony_ci		return 0;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	return max_exp;
468c2ecf20Sopenharmony_ci}
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic inline size_t ath10k_spectral_fix_bin_size(struct ath10k *ar,
498c2ecf20Sopenharmony_ci						  size_t bin_len)
508c2ecf20Sopenharmony_ci{
518c2ecf20Sopenharmony_ci	/* some chipsets reports bin size as 2^n bytes + 'm' bytes in
528c2ecf20Sopenharmony_ci	 * report mode 2. First 2^n bytes carries inband tones and last
538c2ecf20Sopenharmony_ci	 * 'm' bytes carries band edge detection data mainly used in
548c2ecf20Sopenharmony_ci	 * radar detection purpose. Strip last 'm' bytes to make bin size
558c2ecf20Sopenharmony_ci	 * as a valid one. 'm' can take possible values of 4, 12.
568c2ecf20Sopenharmony_ci	 */
578c2ecf20Sopenharmony_ci	if (!is_power_of_2(bin_len))
588c2ecf20Sopenharmony_ci		bin_len -= ar->hw_params.spectral_bin_discard;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	return bin_len;
618c2ecf20Sopenharmony_ci}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ciint ath10k_spectral_process_fft(struct ath10k *ar,
648c2ecf20Sopenharmony_ci				struct wmi_phyerr_ev_arg *phyerr,
658c2ecf20Sopenharmony_ci				const struct phyerr_fft_report *fftr,
668c2ecf20Sopenharmony_ci				size_t bin_len, u64 tsf)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	struct fft_sample_ath10k *fft_sample;
698c2ecf20Sopenharmony_ci	u8 buf[sizeof(*fft_sample) + SPECTRAL_ATH10K_MAX_NUM_BINS];
708c2ecf20Sopenharmony_ci	u16 freq1, freq2, total_gain_db, base_pwr_db, length, peak_mag;
718c2ecf20Sopenharmony_ci	u32 reg0, reg1;
728c2ecf20Sopenharmony_ci	u8 chain_idx, *bins;
738c2ecf20Sopenharmony_ci	int dc_pos;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	fft_sample = (struct fft_sample_ath10k *)&buf;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	bin_len = ath10k_spectral_fix_bin_size(ar, bin_len);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	if (bin_len < 64 || bin_len > SPECTRAL_ATH10K_MAX_NUM_BINS)
808c2ecf20Sopenharmony_ci		return -EINVAL;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	reg0 = __le32_to_cpu(fftr->reg0);
838c2ecf20Sopenharmony_ci	reg1 = __le32_to_cpu(fftr->reg1);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	length = sizeof(*fft_sample) - sizeof(struct fft_sample_tlv) + bin_len;
868c2ecf20Sopenharmony_ci	fft_sample->tlv.type = ATH_FFT_SAMPLE_ATH10K;
878c2ecf20Sopenharmony_ci	fft_sample->tlv.length = __cpu_to_be16(length);
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	/* TODO: there might be a reason why the hardware reports 20/40/80 MHz,
908c2ecf20Sopenharmony_ci	 * but the results/plots suggest that its actually 22/44/88 MHz.
918c2ecf20Sopenharmony_ci	 */
928c2ecf20Sopenharmony_ci	switch (phyerr->chan_width_mhz) {
938c2ecf20Sopenharmony_ci	case 20:
948c2ecf20Sopenharmony_ci		fft_sample->chan_width_mhz = 22;
958c2ecf20Sopenharmony_ci		break;
968c2ecf20Sopenharmony_ci	case 40:
978c2ecf20Sopenharmony_ci		fft_sample->chan_width_mhz = 44;
988c2ecf20Sopenharmony_ci		break;
998c2ecf20Sopenharmony_ci	case 80:
1008c2ecf20Sopenharmony_ci		/* TODO: As experiments with an analogue sender and various
1018c2ecf20Sopenharmony_ci		 * configurations (fft-sizes of 64/128/256 and 20/40/80 Mhz)
1028c2ecf20Sopenharmony_ci		 * show, the particular configuration of 80 MHz/64 bins does
1038c2ecf20Sopenharmony_ci		 * not match with the other samples at all. Until the reason
1048c2ecf20Sopenharmony_ci		 * for that is found, don't report these samples.
1058c2ecf20Sopenharmony_ci		 */
1068c2ecf20Sopenharmony_ci		if (bin_len == 64)
1078c2ecf20Sopenharmony_ci			return -EINVAL;
1088c2ecf20Sopenharmony_ci		fft_sample->chan_width_mhz = 88;
1098c2ecf20Sopenharmony_ci		break;
1108c2ecf20Sopenharmony_ci	default:
1118c2ecf20Sopenharmony_ci		fft_sample->chan_width_mhz = phyerr->chan_width_mhz;
1128c2ecf20Sopenharmony_ci	}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	fft_sample->relpwr_db = MS(reg1, SEARCH_FFT_REPORT_REG1_RELPWR_DB);
1158c2ecf20Sopenharmony_ci	fft_sample->avgpwr_db = MS(reg1, SEARCH_FFT_REPORT_REG1_AVGPWR_DB);
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	peak_mag = MS(reg1, SEARCH_FFT_REPORT_REG1_PEAK_MAG);
1188c2ecf20Sopenharmony_ci	fft_sample->max_magnitude = __cpu_to_be16(peak_mag);
1198c2ecf20Sopenharmony_ci	fft_sample->max_index = MS(reg0, SEARCH_FFT_REPORT_REG0_PEAK_SIDX);
1208c2ecf20Sopenharmony_ci	fft_sample->rssi = phyerr->rssi_combined;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	total_gain_db = MS(reg0, SEARCH_FFT_REPORT_REG0_TOTAL_GAIN_DB);
1238c2ecf20Sopenharmony_ci	base_pwr_db = MS(reg0, SEARCH_FFT_REPORT_REG0_BASE_PWR_DB);
1248c2ecf20Sopenharmony_ci	fft_sample->total_gain_db = __cpu_to_be16(total_gain_db);
1258c2ecf20Sopenharmony_ci	fft_sample->base_pwr_db = __cpu_to_be16(base_pwr_db);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	freq1 = phyerr->freq1;
1288c2ecf20Sopenharmony_ci	freq2 = phyerr->freq2;
1298c2ecf20Sopenharmony_ci	fft_sample->freq1 = __cpu_to_be16(freq1);
1308c2ecf20Sopenharmony_ci	fft_sample->freq2 = __cpu_to_be16(freq2);
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	chain_idx = MS(reg0, SEARCH_FFT_REPORT_REG0_FFT_CHN_IDX);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	fft_sample->noise = __cpu_to_be16(phyerr->nf_chains[chain_idx]);
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	bins = (u8 *)fftr;
1378c2ecf20Sopenharmony_ci	bins += sizeof(*fftr) + ar->hw_params.spectral_bin_offset;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	fft_sample->tsf = __cpu_to_be64(tsf);
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	/* max_exp has been directly reported by previous hardware (ath9k),
1428c2ecf20Sopenharmony_ci	 * maybe its possible to get it by other means?
1438c2ecf20Sopenharmony_ci	 */
1448c2ecf20Sopenharmony_ci	fft_sample->max_exp = get_max_exp(fft_sample->max_index, peak_mag,
1458c2ecf20Sopenharmony_ci					  bin_len, bins);
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	memcpy(fft_sample->data, bins, bin_len);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	/* DC value (value in the middle) is the blind spot of the spectral
1508c2ecf20Sopenharmony_ci	 * sample and invalid, interpolate it.
1518c2ecf20Sopenharmony_ci	 */
1528c2ecf20Sopenharmony_ci	dc_pos = bin_len / 2;
1538c2ecf20Sopenharmony_ci	fft_sample->data[dc_pos] = (fft_sample->data[dc_pos + 1] +
1548c2ecf20Sopenharmony_ci				    fft_sample->data[dc_pos - 1]) / 2;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	send_fft_sample(ar, &fft_sample->tlv);
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	return 0;
1598c2ecf20Sopenharmony_ci}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_cistatic struct ath10k_vif *ath10k_get_spectral_vdev(struct ath10k *ar)
1628c2ecf20Sopenharmony_ci{
1638c2ecf20Sopenharmony_ci	struct ath10k_vif *arvif;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	lockdep_assert_held(&ar->conf_mutex);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	if (list_empty(&ar->arvifs))
1688c2ecf20Sopenharmony_ci		return NULL;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	/* if there already is a vif doing spectral, return that. */
1718c2ecf20Sopenharmony_ci	list_for_each_entry(arvif, &ar->arvifs, list)
1728c2ecf20Sopenharmony_ci		if (arvif->spectral_enabled)
1738c2ecf20Sopenharmony_ci			return arvif;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	/* otherwise, return the first vif. */
1768c2ecf20Sopenharmony_ci	return list_first_entry(&ar->arvifs, typeof(*arvif), list);
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_cistatic int ath10k_spectral_scan_trigger(struct ath10k *ar)
1808c2ecf20Sopenharmony_ci{
1818c2ecf20Sopenharmony_ci	struct ath10k_vif *arvif;
1828c2ecf20Sopenharmony_ci	int res;
1838c2ecf20Sopenharmony_ci	int vdev_id;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	lockdep_assert_held(&ar->conf_mutex);
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	arvif = ath10k_get_spectral_vdev(ar);
1888c2ecf20Sopenharmony_ci	if (!arvif)
1898c2ecf20Sopenharmony_ci		return -ENODEV;
1908c2ecf20Sopenharmony_ci	vdev_id = arvif->vdev_id;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	if (ar->spectral.mode == SPECTRAL_DISABLED)
1938c2ecf20Sopenharmony_ci		return 0;
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
1968c2ecf20Sopenharmony_ci					      WMI_SPECTRAL_TRIGGER_CMD_CLEAR,
1978c2ecf20Sopenharmony_ci					      WMI_SPECTRAL_ENABLE_CMD_ENABLE);
1988c2ecf20Sopenharmony_ci	if (res < 0)
1998c2ecf20Sopenharmony_ci		return res;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
2028c2ecf20Sopenharmony_ci					      WMI_SPECTRAL_TRIGGER_CMD_TRIGGER,
2038c2ecf20Sopenharmony_ci					      WMI_SPECTRAL_ENABLE_CMD_ENABLE);
2048c2ecf20Sopenharmony_ci	if (res < 0)
2058c2ecf20Sopenharmony_ci		return res;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	return 0;
2088c2ecf20Sopenharmony_ci}
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_cistatic int ath10k_spectral_scan_config(struct ath10k *ar,
2118c2ecf20Sopenharmony_ci				       enum ath10k_spectral_mode mode)
2128c2ecf20Sopenharmony_ci{
2138c2ecf20Sopenharmony_ci	struct wmi_vdev_spectral_conf_arg arg;
2148c2ecf20Sopenharmony_ci	struct ath10k_vif *arvif;
2158c2ecf20Sopenharmony_ci	int vdev_id, count, res = 0;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	lockdep_assert_held(&ar->conf_mutex);
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	arvif = ath10k_get_spectral_vdev(ar);
2208c2ecf20Sopenharmony_ci	if (!arvif)
2218c2ecf20Sopenharmony_ci		return -ENODEV;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	vdev_id = arvif->vdev_id;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	arvif->spectral_enabled = (mode != SPECTRAL_DISABLED);
2268c2ecf20Sopenharmony_ci	ar->spectral.mode = mode;
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
2298c2ecf20Sopenharmony_ci					      WMI_SPECTRAL_TRIGGER_CMD_CLEAR,
2308c2ecf20Sopenharmony_ci					      WMI_SPECTRAL_ENABLE_CMD_DISABLE);
2318c2ecf20Sopenharmony_ci	if (res < 0) {
2328c2ecf20Sopenharmony_ci		ath10k_warn(ar, "failed to enable spectral scan: %d\n", res);
2338c2ecf20Sopenharmony_ci		return res;
2348c2ecf20Sopenharmony_ci	}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	if (mode == SPECTRAL_DISABLED)
2378c2ecf20Sopenharmony_ci		return 0;
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	if (mode == SPECTRAL_BACKGROUND)
2408c2ecf20Sopenharmony_ci		count = WMI_SPECTRAL_COUNT_DEFAULT;
2418c2ecf20Sopenharmony_ci	else
2428c2ecf20Sopenharmony_ci		count = max_t(u8, 1, ar->spectral.config.count);
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	arg.vdev_id = vdev_id;
2458c2ecf20Sopenharmony_ci	arg.scan_count = count;
2468c2ecf20Sopenharmony_ci	arg.scan_period = WMI_SPECTRAL_PERIOD_DEFAULT;
2478c2ecf20Sopenharmony_ci	arg.scan_priority = WMI_SPECTRAL_PRIORITY_DEFAULT;
2488c2ecf20Sopenharmony_ci	arg.scan_fft_size = ar->spectral.config.fft_size;
2498c2ecf20Sopenharmony_ci	arg.scan_gc_ena = WMI_SPECTRAL_GC_ENA_DEFAULT;
2508c2ecf20Sopenharmony_ci	arg.scan_restart_ena = WMI_SPECTRAL_RESTART_ENA_DEFAULT;
2518c2ecf20Sopenharmony_ci	arg.scan_noise_floor_ref = WMI_SPECTRAL_NOISE_FLOOR_REF_DEFAULT;
2528c2ecf20Sopenharmony_ci	arg.scan_init_delay = WMI_SPECTRAL_INIT_DELAY_DEFAULT;
2538c2ecf20Sopenharmony_ci	arg.scan_nb_tone_thr = WMI_SPECTRAL_NB_TONE_THR_DEFAULT;
2548c2ecf20Sopenharmony_ci	arg.scan_str_bin_thr = WMI_SPECTRAL_STR_BIN_THR_DEFAULT;
2558c2ecf20Sopenharmony_ci	arg.scan_wb_rpt_mode = WMI_SPECTRAL_WB_RPT_MODE_DEFAULT;
2568c2ecf20Sopenharmony_ci	arg.scan_rssi_rpt_mode = WMI_SPECTRAL_RSSI_RPT_MODE_DEFAULT;
2578c2ecf20Sopenharmony_ci	arg.scan_rssi_thr = WMI_SPECTRAL_RSSI_THR_DEFAULT;
2588c2ecf20Sopenharmony_ci	arg.scan_pwr_format = WMI_SPECTRAL_PWR_FORMAT_DEFAULT;
2598c2ecf20Sopenharmony_ci	arg.scan_rpt_mode = WMI_SPECTRAL_RPT_MODE_DEFAULT;
2608c2ecf20Sopenharmony_ci	arg.scan_bin_scale = WMI_SPECTRAL_BIN_SCALE_DEFAULT;
2618c2ecf20Sopenharmony_ci	arg.scan_dbm_adj = WMI_SPECTRAL_DBM_ADJ_DEFAULT;
2628c2ecf20Sopenharmony_ci	arg.scan_chn_mask = WMI_SPECTRAL_CHN_MASK_DEFAULT;
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	res = ath10k_wmi_vdev_spectral_conf(ar, &arg);
2658c2ecf20Sopenharmony_ci	if (res < 0) {
2668c2ecf20Sopenharmony_ci		ath10k_warn(ar, "failed to configure spectral scan: %d\n", res);
2678c2ecf20Sopenharmony_ci		return res;
2688c2ecf20Sopenharmony_ci	}
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	return 0;
2718c2ecf20Sopenharmony_ci}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_cistatic ssize_t read_file_spec_scan_ctl(struct file *file, char __user *user_buf,
2748c2ecf20Sopenharmony_ci				       size_t count, loff_t *ppos)
2758c2ecf20Sopenharmony_ci{
2768c2ecf20Sopenharmony_ci	struct ath10k *ar = file->private_data;
2778c2ecf20Sopenharmony_ci	char *mode = "";
2788c2ecf20Sopenharmony_ci	size_t len;
2798c2ecf20Sopenharmony_ci	enum ath10k_spectral_mode spectral_mode;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	mutex_lock(&ar->conf_mutex);
2828c2ecf20Sopenharmony_ci	spectral_mode = ar->spectral.mode;
2838c2ecf20Sopenharmony_ci	mutex_unlock(&ar->conf_mutex);
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	switch (spectral_mode) {
2868c2ecf20Sopenharmony_ci	case SPECTRAL_DISABLED:
2878c2ecf20Sopenharmony_ci		mode = "disable";
2888c2ecf20Sopenharmony_ci		break;
2898c2ecf20Sopenharmony_ci	case SPECTRAL_BACKGROUND:
2908c2ecf20Sopenharmony_ci		mode = "background";
2918c2ecf20Sopenharmony_ci		break;
2928c2ecf20Sopenharmony_ci	case SPECTRAL_MANUAL:
2938c2ecf20Sopenharmony_ci		mode = "manual";
2948c2ecf20Sopenharmony_ci		break;
2958c2ecf20Sopenharmony_ci	}
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	len = strlen(mode);
2988c2ecf20Sopenharmony_ci	return simple_read_from_buffer(user_buf, count, ppos, mode, len);
2998c2ecf20Sopenharmony_ci}
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_cistatic ssize_t write_file_spec_scan_ctl(struct file *file,
3028c2ecf20Sopenharmony_ci					const char __user *user_buf,
3038c2ecf20Sopenharmony_ci					size_t count, loff_t *ppos)
3048c2ecf20Sopenharmony_ci{
3058c2ecf20Sopenharmony_ci	struct ath10k *ar = file->private_data;
3068c2ecf20Sopenharmony_ci	char buf[32];
3078c2ecf20Sopenharmony_ci	ssize_t len;
3088c2ecf20Sopenharmony_ci	int res;
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ci	len = min(count, sizeof(buf) - 1);
3118c2ecf20Sopenharmony_ci	if (copy_from_user(buf, user_buf, len))
3128c2ecf20Sopenharmony_ci		return -EFAULT;
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci	buf[len] = '\0';
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	mutex_lock(&ar->conf_mutex);
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci	if (strncmp("trigger", buf, 7) == 0) {
3198c2ecf20Sopenharmony_ci		if (ar->spectral.mode == SPECTRAL_MANUAL ||
3208c2ecf20Sopenharmony_ci		    ar->spectral.mode == SPECTRAL_BACKGROUND) {
3218c2ecf20Sopenharmony_ci			/* reset the configuration to adopt possibly changed
3228c2ecf20Sopenharmony_ci			 * debugfs parameters
3238c2ecf20Sopenharmony_ci			 */
3248c2ecf20Sopenharmony_ci			res = ath10k_spectral_scan_config(ar,
3258c2ecf20Sopenharmony_ci							  ar->spectral.mode);
3268c2ecf20Sopenharmony_ci			if (res < 0) {
3278c2ecf20Sopenharmony_ci				ath10k_warn(ar, "failed to reconfigure spectral scan: %d\n",
3288c2ecf20Sopenharmony_ci					    res);
3298c2ecf20Sopenharmony_ci			}
3308c2ecf20Sopenharmony_ci			res = ath10k_spectral_scan_trigger(ar);
3318c2ecf20Sopenharmony_ci			if (res < 0) {
3328c2ecf20Sopenharmony_ci				ath10k_warn(ar, "failed to trigger spectral scan: %d\n",
3338c2ecf20Sopenharmony_ci					    res);
3348c2ecf20Sopenharmony_ci			}
3358c2ecf20Sopenharmony_ci		} else {
3368c2ecf20Sopenharmony_ci			res = -EINVAL;
3378c2ecf20Sopenharmony_ci		}
3388c2ecf20Sopenharmony_ci	} else if (strncmp("background", buf, 10) == 0) {
3398c2ecf20Sopenharmony_ci		res = ath10k_spectral_scan_config(ar, SPECTRAL_BACKGROUND);
3408c2ecf20Sopenharmony_ci	} else if (strncmp("manual", buf, 6) == 0) {
3418c2ecf20Sopenharmony_ci		res = ath10k_spectral_scan_config(ar, SPECTRAL_MANUAL);
3428c2ecf20Sopenharmony_ci	} else if (strncmp("disable", buf, 7) == 0) {
3438c2ecf20Sopenharmony_ci		res = ath10k_spectral_scan_config(ar, SPECTRAL_DISABLED);
3448c2ecf20Sopenharmony_ci	} else {
3458c2ecf20Sopenharmony_ci		res = -EINVAL;
3468c2ecf20Sopenharmony_ci	}
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_ci	mutex_unlock(&ar->conf_mutex);
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	if (res < 0)
3518c2ecf20Sopenharmony_ci		return res;
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci	return count;
3548c2ecf20Sopenharmony_ci}
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_cistatic const struct file_operations fops_spec_scan_ctl = {
3578c2ecf20Sopenharmony_ci	.read = read_file_spec_scan_ctl,
3588c2ecf20Sopenharmony_ci	.write = write_file_spec_scan_ctl,
3598c2ecf20Sopenharmony_ci	.open = simple_open,
3608c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
3618c2ecf20Sopenharmony_ci	.llseek = default_llseek,
3628c2ecf20Sopenharmony_ci};
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_cistatic ssize_t read_file_spectral_count(struct file *file,
3658c2ecf20Sopenharmony_ci					char __user *user_buf,
3668c2ecf20Sopenharmony_ci					size_t count, loff_t *ppos)
3678c2ecf20Sopenharmony_ci{
3688c2ecf20Sopenharmony_ci	struct ath10k *ar = file->private_data;
3698c2ecf20Sopenharmony_ci	char buf[32];
3708c2ecf20Sopenharmony_ci	size_t len;
3718c2ecf20Sopenharmony_ci	u8 spectral_count;
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci	mutex_lock(&ar->conf_mutex);
3748c2ecf20Sopenharmony_ci	spectral_count = ar->spectral.config.count;
3758c2ecf20Sopenharmony_ci	mutex_unlock(&ar->conf_mutex);
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_ci	len = sprintf(buf, "%d\n", spectral_count);
3788c2ecf20Sopenharmony_ci	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
3798c2ecf20Sopenharmony_ci}
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_cistatic ssize_t write_file_spectral_count(struct file *file,
3828c2ecf20Sopenharmony_ci					 const char __user *user_buf,
3838c2ecf20Sopenharmony_ci					 size_t count, loff_t *ppos)
3848c2ecf20Sopenharmony_ci{
3858c2ecf20Sopenharmony_ci	struct ath10k *ar = file->private_data;
3868c2ecf20Sopenharmony_ci	unsigned long val;
3878c2ecf20Sopenharmony_ci	char buf[32];
3888c2ecf20Sopenharmony_ci	ssize_t len;
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_ci	len = min(count, sizeof(buf) - 1);
3918c2ecf20Sopenharmony_ci	if (copy_from_user(buf, user_buf, len))
3928c2ecf20Sopenharmony_ci		return -EFAULT;
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_ci	buf[len] = '\0';
3958c2ecf20Sopenharmony_ci	if (kstrtoul(buf, 0, &val))
3968c2ecf20Sopenharmony_ci		return -EINVAL;
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_ci	if (val > 255)
3998c2ecf20Sopenharmony_ci		return -EINVAL;
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci	mutex_lock(&ar->conf_mutex);
4028c2ecf20Sopenharmony_ci	ar->spectral.config.count = val;
4038c2ecf20Sopenharmony_ci	mutex_unlock(&ar->conf_mutex);
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_ci	return count;
4068c2ecf20Sopenharmony_ci}
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_cistatic const struct file_operations fops_spectral_count = {
4098c2ecf20Sopenharmony_ci	.read = read_file_spectral_count,
4108c2ecf20Sopenharmony_ci	.write = write_file_spectral_count,
4118c2ecf20Sopenharmony_ci	.open = simple_open,
4128c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
4138c2ecf20Sopenharmony_ci	.llseek = default_llseek,
4148c2ecf20Sopenharmony_ci};
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_cistatic ssize_t read_file_spectral_bins(struct file *file,
4178c2ecf20Sopenharmony_ci				       char __user *user_buf,
4188c2ecf20Sopenharmony_ci				       size_t count, loff_t *ppos)
4198c2ecf20Sopenharmony_ci{
4208c2ecf20Sopenharmony_ci	struct ath10k *ar = file->private_data;
4218c2ecf20Sopenharmony_ci	char buf[32];
4228c2ecf20Sopenharmony_ci	unsigned int bins, fft_size, bin_scale;
4238c2ecf20Sopenharmony_ci	size_t len;
4248c2ecf20Sopenharmony_ci
4258c2ecf20Sopenharmony_ci	mutex_lock(&ar->conf_mutex);
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ci	fft_size = ar->spectral.config.fft_size;
4288c2ecf20Sopenharmony_ci	bin_scale = WMI_SPECTRAL_BIN_SCALE_DEFAULT;
4298c2ecf20Sopenharmony_ci	bins = 1 << (fft_size - bin_scale);
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci	mutex_unlock(&ar->conf_mutex);
4328c2ecf20Sopenharmony_ci
4338c2ecf20Sopenharmony_ci	len = sprintf(buf, "%d\n", bins);
4348c2ecf20Sopenharmony_ci	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
4358c2ecf20Sopenharmony_ci}
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_cistatic ssize_t write_file_spectral_bins(struct file *file,
4388c2ecf20Sopenharmony_ci					const char __user *user_buf,
4398c2ecf20Sopenharmony_ci					size_t count, loff_t *ppos)
4408c2ecf20Sopenharmony_ci{
4418c2ecf20Sopenharmony_ci	struct ath10k *ar = file->private_data;
4428c2ecf20Sopenharmony_ci	unsigned long val;
4438c2ecf20Sopenharmony_ci	char buf[32];
4448c2ecf20Sopenharmony_ci	ssize_t len;
4458c2ecf20Sopenharmony_ci
4468c2ecf20Sopenharmony_ci	len = min(count, sizeof(buf) - 1);
4478c2ecf20Sopenharmony_ci	if (copy_from_user(buf, user_buf, len))
4488c2ecf20Sopenharmony_ci		return -EFAULT;
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	buf[len] = '\0';
4518c2ecf20Sopenharmony_ci	if (kstrtoul(buf, 0, &val))
4528c2ecf20Sopenharmony_ci		return -EINVAL;
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	if (val < 64 || val > SPECTRAL_ATH10K_MAX_NUM_BINS)
4558c2ecf20Sopenharmony_ci		return -EINVAL;
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci	if (!is_power_of_2(val))
4588c2ecf20Sopenharmony_ci		return -EINVAL;
4598c2ecf20Sopenharmony_ci
4608c2ecf20Sopenharmony_ci	mutex_lock(&ar->conf_mutex);
4618c2ecf20Sopenharmony_ci	ar->spectral.config.fft_size = ilog2(val);
4628c2ecf20Sopenharmony_ci	ar->spectral.config.fft_size += WMI_SPECTRAL_BIN_SCALE_DEFAULT;
4638c2ecf20Sopenharmony_ci	mutex_unlock(&ar->conf_mutex);
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_ci	return count;
4668c2ecf20Sopenharmony_ci}
4678c2ecf20Sopenharmony_ci
4688c2ecf20Sopenharmony_cistatic const struct file_operations fops_spectral_bins = {
4698c2ecf20Sopenharmony_ci	.read = read_file_spectral_bins,
4708c2ecf20Sopenharmony_ci	.write = write_file_spectral_bins,
4718c2ecf20Sopenharmony_ci	.open = simple_open,
4728c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
4738c2ecf20Sopenharmony_ci	.llseek = default_llseek,
4748c2ecf20Sopenharmony_ci};
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_cistatic struct dentry *create_buf_file_handler(const char *filename,
4778c2ecf20Sopenharmony_ci					      struct dentry *parent,
4788c2ecf20Sopenharmony_ci					      umode_t mode,
4798c2ecf20Sopenharmony_ci					      struct rchan_buf *buf,
4808c2ecf20Sopenharmony_ci					      int *is_global)
4818c2ecf20Sopenharmony_ci{
4828c2ecf20Sopenharmony_ci	struct dentry *buf_file;
4838c2ecf20Sopenharmony_ci
4848c2ecf20Sopenharmony_ci	buf_file = debugfs_create_file(filename, mode, parent, buf,
4858c2ecf20Sopenharmony_ci				       &relay_file_operations);
4868c2ecf20Sopenharmony_ci	if (IS_ERR(buf_file))
4878c2ecf20Sopenharmony_ci		return NULL;
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ci	*is_global = 1;
4908c2ecf20Sopenharmony_ci	return buf_file;
4918c2ecf20Sopenharmony_ci}
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_cistatic int remove_buf_file_handler(struct dentry *dentry)
4948c2ecf20Sopenharmony_ci{
4958c2ecf20Sopenharmony_ci	debugfs_remove(dentry);
4968c2ecf20Sopenharmony_ci
4978c2ecf20Sopenharmony_ci	return 0;
4988c2ecf20Sopenharmony_ci}
4998c2ecf20Sopenharmony_ci
5008c2ecf20Sopenharmony_cistatic struct rchan_callbacks rfs_spec_scan_cb = {
5018c2ecf20Sopenharmony_ci	.create_buf_file = create_buf_file_handler,
5028c2ecf20Sopenharmony_ci	.remove_buf_file = remove_buf_file_handler,
5038c2ecf20Sopenharmony_ci};
5048c2ecf20Sopenharmony_ci
5058c2ecf20Sopenharmony_ciint ath10k_spectral_start(struct ath10k *ar)
5068c2ecf20Sopenharmony_ci{
5078c2ecf20Sopenharmony_ci	struct ath10k_vif *arvif;
5088c2ecf20Sopenharmony_ci
5098c2ecf20Sopenharmony_ci	lockdep_assert_held(&ar->conf_mutex);
5108c2ecf20Sopenharmony_ci
5118c2ecf20Sopenharmony_ci	list_for_each_entry(arvif, &ar->arvifs, list)
5128c2ecf20Sopenharmony_ci		arvif->spectral_enabled = 0;
5138c2ecf20Sopenharmony_ci
5148c2ecf20Sopenharmony_ci	ar->spectral.mode = SPECTRAL_DISABLED;
5158c2ecf20Sopenharmony_ci	ar->spectral.config.count = WMI_SPECTRAL_COUNT_DEFAULT;
5168c2ecf20Sopenharmony_ci	ar->spectral.config.fft_size = WMI_SPECTRAL_FFT_SIZE_DEFAULT;
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_ci	return 0;
5198c2ecf20Sopenharmony_ci}
5208c2ecf20Sopenharmony_ci
5218c2ecf20Sopenharmony_ciint ath10k_spectral_vif_stop(struct ath10k_vif *arvif)
5228c2ecf20Sopenharmony_ci{
5238c2ecf20Sopenharmony_ci	if (!arvif->spectral_enabled)
5248c2ecf20Sopenharmony_ci		return 0;
5258c2ecf20Sopenharmony_ci
5268c2ecf20Sopenharmony_ci	return ath10k_spectral_scan_config(arvif->ar, SPECTRAL_DISABLED);
5278c2ecf20Sopenharmony_ci}
5288c2ecf20Sopenharmony_ci
5298c2ecf20Sopenharmony_ciint ath10k_spectral_create(struct ath10k *ar)
5308c2ecf20Sopenharmony_ci{
5318c2ecf20Sopenharmony_ci	/* The buffer size covers whole channels in dual bands up to 128 bins.
5328c2ecf20Sopenharmony_ci	 * Scan with bigger than 128 bins needs to be run on single band each.
5338c2ecf20Sopenharmony_ci	 */
5348c2ecf20Sopenharmony_ci	ar->spectral.rfs_chan_spec_scan = relay_open("spectral_scan",
5358c2ecf20Sopenharmony_ci						     ar->debug.debugfs_phy,
5368c2ecf20Sopenharmony_ci						     1140, 2500,
5378c2ecf20Sopenharmony_ci						     &rfs_spec_scan_cb, NULL);
5388c2ecf20Sopenharmony_ci	debugfs_create_file("spectral_scan_ctl",
5398c2ecf20Sopenharmony_ci			    0600,
5408c2ecf20Sopenharmony_ci			    ar->debug.debugfs_phy, ar,
5418c2ecf20Sopenharmony_ci			    &fops_spec_scan_ctl);
5428c2ecf20Sopenharmony_ci	debugfs_create_file("spectral_count",
5438c2ecf20Sopenharmony_ci			    0600,
5448c2ecf20Sopenharmony_ci			    ar->debug.debugfs_phy, ar,
5458c2ecf20Sopenharmony_ci			    &fops_spectral_count);
5468c2ecf20Sopenharmony_ci	debugfs_create_file("spectral_bins",
5478c2ecf20Sopenharmony_ci			    0600,
5488c2ecf20Sopenharmony_ci			    ar->debug.debugfs_phy, ar,
5498c2ecf20Sopenharmony_ci			    &fops_spectral_bins);
5508c2ecf20Sopenharmony_ci
5518c2ecf20Sopenharmony_ci	return 0;
5528c2ecf20Sopenharmony_ci}
5538c2ecf20Sopenharmony_ci
5548c2ecf20Sopenharmony_civoid ath10k_spectral_destroy(struct ath10k *ar)
5558c2ecf20Sopenharmony_ci{
5568c2ecf20Sopenharmony_ci	if (ar->spectral.rfs_chan_spec_scan) {
5578c2ecf20Sopenharmony_ci		relay_close(ar->spectral.rfs_chan_spec_scan);
5588c2ecf20Sopenharmony_ci		ar->spectral.rfs_chan_spec_scan = NULL;
5598c2ecf20Sopenharmony_ci	}
5608c2ecf20Sopenharmony_ci}
561