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