1 | // SPDX-License-Identifier: BSD-3-Clause-Clear |
2 | /* |
3 | * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved. |
4 | * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved. |
5 | */ |
6 | |
7 | #include <linux/relay.h> |
8 | #include "core.h" |
9 | #include "debug.h" |
10 | |
11 | #define ATH11K_SPECTRAL_NUM_RESP_PER_EVENT 2 |
12 | #define ATH11K_SPECTRAL_EVENT_TIMEOUT_MS 1 |
13 | |
14 | #define ATH11K_SPECTRAL_DWORD_SIZE 4 |
15 | #define ATH11K_SPECTRAL_MIN_BINS 32 |
16 | #define ATH11K_SPECTRAL_MIN_IB_BINS (ATH11K_SPECTRAL_MIN_BINS >> 1) |
17 | #define ATH11K_SPECTRAL_MAX_IB_BINS(x) ((x)->hw_params.spectral.max_fft_bins >> 1) |
18 | |
19 | #define ATH11K_SPECTRAL_SCAN_COUNT_MAX 4095 |
20 | |
21 | /* Max channel computed by sum of 2g and 5g band channels */ |
22 | #define ATH11K_SPECTRAL_TOTAL_CHANNEL 41 |
23 | #define ATH11K_SPECTRAL_SAMPLES_PER_CHANNEL 70 |
24 | #define ATH11K_SPECTRAL_PER_SAMPLE_SIZE(x) (sizeof(struct fft_sample_ath11k) + \ |
25 | ATH11K_SPECTRAL_MAX_IB_BINS(x)) |
26 | #define ATH11K_SPECTRAL_TOTAL_SAMPLE (ATH11K_SPECTRAL_TOTAL_CHANNEL * \ |
27 | ATH11K_SPECTRAL_SAMPLES_PER_CHANNEL) |
28 | #define ATH11K_SPECTRAL_SUB_BUFF_SIZE(x) ATH11K_SPECTRAL_PER_SAMPLE_SIZE(x) |
29 | #define ATH11K_SPECTRAL_NUM_SUB_BUF ATH11K_SPECTRAL_TOTAL_SAMPLE |
30 | |
31 | #define ATH11K_SPECTRAL_20MHZ 20 |
32 | #define ATH11K_SPECTRAL_40MHZ 40 |
33 | #define ATH11K_SPECTRAL_80MHZ 80 |
34 | #define ATH11K_SPECTRAL_160MHZ 160 |
35 | |
36 | #define ATH11K_SPECTRAL_SIGNATURE 0xFA |
37 | |
38 | #define ATH11K_SPECTRAL_TAG_RADAR_SUMMARY 0x0 |
39 | #define ATH11K_SPECTRAL_TAG_RADAR_FFT 0x1 |
40 | #define ATH11K_SPECTRAL_TAG_SCAN_SUMMARY 0x2 |
41 | #define ATH11K_SPECTRAL_TAG_SCAN_SEARCH 0x3 |
42 | |
43 | #define SPECTRAL_TLV_HDR_LEN GENMASK(15, 0) |
44 | #define SPECTRAL_TLV_HDR_TAG GENMASK(23, 16) |
45 | #define SPECTRAL_TLV_HDR_SIGN GENMASK(31, 24) |
46 | |
47 | #define SPECTRAL_SUMMARY_INFO0_AGC_TOTAL_GAIN GENMASK(7, 0) |
48 | #define SPECTRAL_SUMMARY_INFO0_OB_FLAG BIT(8) |
49 | #define SPECTRAL_SUMMARY_INFO0_GRP_IDX GENMASK(16, 9) |
50 | #define SPECTRAL_SUMMARY_INFO0_RECENT_RFSAT BIT(17) |
51 | #define SPECTRAL_SUMMARY_INFO0_INBAND_PWR_DB GENMASK(27, 18) |
52 | #define SPECTRAL_SUMMARY_INFO0_FALSE_SCAN BIT(28) |
53 | #define SPECTRAL_SUMMARY_INFO0_DETECTOR_ID GENMASK(30, 29) |
54 | #define SPECTRAL_SUMMARY_INFO0_PRI80 BIT(31) |
55 | |
56 | #define SPECTRAL_SUMMARY_INFO2_PEAK_SIGNED_IDX GENMASK(11, 0) |
57 | #define SPECTRAL_SUMMARY_INFO2_PEAK_MAGNITUDE GENMASK(21, 12) |
58 | #define SPECTRAL_SUMMARY_INFO2_NARROWBAND_MASK GENMASK(29, 22) |
59 | #define SPECTRAL_SUMMARY_INFO2_GAIN_CHANGE BIT(30) |
60 | |
61 | struct spectral_tlv { |
62 | __le32 timestamp; |
63 | __le32 ; |
64 | } __packed; |
65 | |
66 | struct spectral_summary_fft_report { |
67 | __le32 timestamp; |
68 | __le32 ; |
69 | __le32 info0; |
70 | __le32 reserve0; |
71 | __le32 info2; |
72 | __le32 reserve1; |
73 | } __packed; |
74 | |
75 | struct ath11k_spectral_summary_report { |
76 | struct wmi_dma_buf_release_meta_data meta; |
77 | u32 timestamp; |
78 | u8 agc_total_gain; |
79 | u8 grp_idx; |
80 | u16 inb_pwr_db; |
81 | s16 peak_idx; |
82 | u16 peak_mag; |
83 | u8 detector_id; |
84 | bool out_of_band_flag; |
85 | bool rf_saturation; |
86 | bool primary80; |
87 | bool gain_change; |
88 | bool false_scan; |
89 | }; |
90 | |
91 | #define SPECTRAL_FFT_REPORT_INFO0_DETECTOR_ID GENMASK(1, 0) |
92 | #define SPECTRAL_FFT_REPORT_INFO0_FFT_NUM GENMASK(4, 2) |
93 | #define SPECTRAL_FFT_REPORT_INFO0_RADAR_CHECK GENMASK(16, 5) |
94 | #define SPECTRAL_FFT_REPORT_INFO0_PEAK_SIGNED_IDX GENMASK(27, 17) |
95 | #define SPECTRAL_FFT_REPORT_INFO0_CHAIN_IDX GENMASK(30, 28) |
96 | |
97 | #define SPECTRAL_FFT_REPORT_INFO1_BASE_PWR_DB GENMASK(8, 0) |
98 | #define SPECTRAL_FFT_REPORT_INFO1_TOTAL_GAIN_DB GENMASK(16, 9) |
99 | |
100 | #define SPECTRAL_FFT_REPORT_INFO2_NUM_STRONG_BINS GENMASK(7, 0) |
101 | #define SPECTRAL_FFT_REPORT_INFO2_PEAK_MAGNITUDE GENMASK(17, 8) |
102 | #define SPECTRAL_FFT_REPORT_INFO2_AVG_PWR_DB GENMASK(24, 18) |
103 | #define SPECTRAL_FFT_REPORT_INFO2_REL_PWR_DB GENMASK(31, 25) |
104 | |
105 | struct spectral_search_fft_report { |
106 | __le32 timestamp; |
107 | __le32 ; |
108 | __le32 info0; |
109 | __le32 info1; |
110 | __le32 info2; |
111 | __le32 reserve0; |
112 | u8 bins[]; |
113 | } __packed; |
114 | |
115 | struct ath11k_spectral_search_report { |
116 | u32 timestamp; |
117 | u8 detector_id; |
118 | u8 fft_count; |
119 | u16 radar_check; |
120 | s16 peak_idx; |
121 | u8 chain_idx; |
122 | u16 base_pwr_db; |
123 | u8 total_gain_db; |
124 | u8 strong_bin_count; |
125 | u16 peak_mag; |
126 | u8 avg_pwr_db; |
127 | u8 rel_pwr_db; |
128 | }; |
129 | |
130 | static struct dentry *create_buf_file_handler(const char *filename, |
131 | struct dentry *parent, |
132 | umode_t mode, |
133 | struct rchan_buf *buf, |
134 | int *is_global) |
135 | { |
136 | struct dentry *buf_file; |
137 | |
138 | buf_file = debugfs_create_file(name: filename, mode, parent, data: buf, |
139 | fops: &relay_file_operations); |
140 | *is_global = 1; |
141 | return buf_file; |
142 | } |
143 | |
144 | static int remove_buf_file_handler(struct dentry *dentry) |
145 | { |
146 | debugfs_remove(dentry); |
147 | |
148 | return 0; |
149 | } |
150 | |
151 | static const struct rchan_callbacks rfs_scan_cb = { |
152 | .create_buf_file = create_buf_file_handler, |
153 | .remove_buf_file = remove_buf_file_handler, |
154 | }; |
155 | |
156 | static struct ath11k_vif *ath11k_spectral_get_vdev(struct ath11k *ar) |
157 | { |
158 | struct ath11k_vif *arvif; |
159 | |
160 | lockdep_assert_held(&ar->conf_mutex); |
161 | |
162 | if (list_empty(head: &ar->arvifs)) |
163 | return NULL; |
164 | |
165 | /* if there already is a vif doing spectral, return that. */ |
166 | list_for_each_entry(arvif, &ar->arvifs, list) |
167 | if (arvif->spectral_enabled) |
168 | return arvif; |
169 | |
170 | /* otherwise, return the first vif. */ |
171 | return list_first_entry(&ar->arvifs, typeof(*arvif), list); |
172 | } |
173 | |
174 | static int ath11k_spectral_scan_trigger(struct ath11k *ar) |
175 | { |
176 | struct ath11k_vif *arvif; |
177 | int ret; |
178 | |
179 | lockdep_assert_held(&ar->conf_mutex); |
180 | |
181 | arvif = ath11k_spectral_get_vdev(ar); |
182 | if (!arvif) |
183 | return -ENODEV; |
184 | |
185 | if (ar->spectral.mode == ATH11K_SPECTRAL_DISABLED) |
186 | return 0; |
187 | |
188 | ar->spectral.is_primary = true; |
189 | |
190 | ret = ath11k_wmi_vdev_spectral_enable(ar, vdev_id: arvif->vdev_id, |
191 | ATH11K_WMI_SPECTRAL_TRIGGER_CMD_CLEAR, |
192 | ATH11K_WMI_SPECTRAL_ENABLE_CMD_ENABLE); |
193 | if (ret) |
194 | return ret; |
195 | |
196 | ret = ath11k_wmi_vdev_spectral_enable(ar, vdev_id: arvif->vdev_id, |
197 | ATH11K_WMI_SPECTRAL_TRIGGER_CMD_TRIGGER, |
198 | ATH11K_WMI_SPECTRAL_ENABLE_CMD_ENABLE); |
199 | if (ret) |
200 | return ret; |
201 | |
202 | return 0; |
203 | } |
204 | |
205 | static int ath11k_spectral_scan_config(struct ath11k *ar, |
206 | enum ath11k_spectral_mode mode) |
207 | { |
208 | struct ath11k_wmi_vdev_spectral_conf_param param = { 0 }; |
209 | struct ath11k_vif *arvif; |
210 | int ret, count; |
211 | |
212 | lockdep_assert_held(&ar->conf_mutex); |
213 | |
214 | arvif = ath11k_spectral_get_vdev(ar); |
215 | if (!arvif) |
216 | return -ENODEV; |
217 | |
218 | arvif->spectral_enabled = (mode != ATH11K_SPECTRAL_DISABLED); |
219 | |
220 | spin_lock_bh(lock: &ar->spectral.lock); |
221 | ar->spectral.mode = mode; |
222 | spin_unlock_bh(lock: &ar->spectral.lock); |
223 | |
224 | ret = ath11k_wmi_vdev_spectral_enable(ar, vdev_id: arvif->vdev_id, |
225 | ATH11K_WMI_SPECTRAL_TRIGGER_CMD_CLEAR, |
226 | ATH11K_WMI_SPECTRAL_ENABLE_CMD_DISABLE); |
227 | if (ret) { |
228 | ath11k_warn(ab: ar->ab, fmt: "failed to enable spectral scan: %d\n" , ret); |
229 | return ret; |
230 | } |
231 | |
232 | if (mode == ATH11K_SPECTRAL_DISABLED) |
233 | return 0; |
234 | |
235 | if (mode == ATH11K_SPECTRAL_BACKGROUND) |
236 | count = ATH11K_WMI_SPECTRAL_COUNT_DEFAULT; |
237 | else |
238 | count = max_t(u16, 1, ar->spectral.count); |
239 | |
240 | param.vdev_id = arvif->vdev_id; |
241 | param.scan_count = count; |
242 | param.scan_fft_size = ar->spectral.fft_size; |
243 | param.scan_period = ATH11K_WMI_SPECTRAL_PERIOD_DEFAULT; |
244 | param.scan_priority = ATH11K_WMI_SPECTRAL_PRIORITY_DEFAULT; |
245 | param.scan_gc_ena = ATH11K_WMI_SPECTRAL_GC_ENA_DEFAULT; |
246 | param.scan_restart_ena = ATH11K_WMI_SPECTRAL_RESTART_ENA_DEFAULT; |
247 | param.scan_noise_floor_ref = ATH11K_WMI_SPECTRAL_NOISE_FLOOR_REF_DEFAULT; |
248 | param.scan_init_delay = ATH11K_WMI_SPECTRAL_INIT_DELAY_DEFAULT; |
249 | param.scan_nb_tone_thr = ATH11K_WMI_SPECTRAL_NB_TONE_THR_DEFAULT; |
250 | param.scan_str_bin_thr = ATH11K_WMI_SPECTRAL_STR_BIN_THR_DEFAULT; |
251 | param.scan_wb_rpt_mode = ATH11K_WMI_SPECTRAL_WB_RPT_MODE_DEFAULT; |
252 | param.scan_rssi_rpt_mode = ATH11K_WMI_SPECTRAL_RSSI_RPT_MODE_DEFAULT; |
253 | param.scan_rssi_thr = ATH11K_WMI_SPECTRAL_RSSI_THR_DEFAULT; |
254 | param.scan_pwr_format = ATH11K_WMI_SPECTRAL_PWR_FORMAT_DEFAULT; |
255 | param.scan_rpt_mode = ATH11K_WMI_SPECTRAL_RPT_MODE_DEFAULT; |
256 | param.scan_bin_scale = ATH11K_WMI_SPECTRAL_BIN_SCALE_DEFAULT; |
257 | param.scan_dbm_adj = ATH11K_WMI_SPECTRAL_DBM_ADJ_DEFAULT; |
258 | param.scan_chn_mask = ATH11K_WMI_SPECTRAL_CHN_MASK_DEFAULT; |
259 | |
260 | ret = ath11k_wmi_vdev_spectral_conf(ar, param: ¶m); |
261 | if (ret) { |
262 | ath11k_warn(ab: ar->ab, fmt: "failed to configure spectral scan: %d\n" , ret); |
263 | return ret; |
264 | } |
265 | |
266 | return 0; |
267 | } |
268 | |
269 | static ssize_t ath11k_read_file_spec_scan_ctl(struct file *file, |
270 | char __user *user_buf, |
271 | size_t count, loff_t *ppos) |
272 | { |
273 | struct ath11k *ar = file->private_data; |
274 | char *mode = "" ; |
275 | size_t len; |
276 | enum ath11k_spectral_mode spectral_mode; |
277 | |
278 | mutex_lock(&ar->conf_mutex); |
279 | spectral_mode = ar->spectral.mode; |
280 | mutex_unlock(lock: &ar->conf_mutex); |
281 | |
282 | switch (spectral_mode) { |
283 | case ATH11K_SPECTRAL_DISABLED: |
284 | mode = "disable" ; |
285 | break; |
286 | case ATH11K_SPECTRAL_BACKGROUND: |
287 | mode = "background" ; |
288 | break; |
289 | case ATH11K_SPECTRAL_MANUAL: |
290 | mode = "manual" ; |
291 | break; |
292 | } |
293 | |
294 | len = strlen(mode); |
295 | return simple_read_from_buffer(to: user_buf, count, ppos, from: mode, available: len); |
296 | } |
297 | |
298 | static ssize_t ath11k_write_file_spec_scan_ctl(struct file *file, |
299 | const char __user *user_buf, |
300 | size_t count, loff_t *ppos) |
301 | { |
302 | struct ath11k *ar = file->private_data; |
303 | char buf[32]; |
304 | ssize_t len; |
305 | int ret; |
306 | |
307 | len = min(count, sizeof(buf) - 1); |
308 | if (copy_from_user(to: buf, from: user_buf, n: len)) |
309 | return -EFAULT; |
310 | |
311 | buf[len] = '\0'; |
312 | |
313 | mutex_lock(&ar->conf_mutex); |
314 | |
315 | if (strncmp("trigger" , buf, 7) == 0) { |
316 | if (ar->spectral.mode == ATH11K_SPECTRAL_MANUAL || |
317 | ar->spectral.mode == ATH11K_SPECTRAL_BACKGROUND) { |
318 | /* reset the configuration to adopt possibly changed |
319 | * debugfs parameters |
320 | */ |
321 | ret = ath11k_spectral_scan_config(ar, mode: ar->spectral.mode); |
322 | if (ret) { |
323 | ath11k_warn(ab: ar->ab, fmt: "failed to reconfigure spectral scan: %d\n" , |
324 | ret); |
325 | goto unlock; |
326 | } |
327 | |
328 | ret = ath11k_spectral_scan_trigger(ar); |
329 | if (ret) { |
330 | ath11k_warn(ab: ar->ab, fmt: "failed to trigger spectral scan: %d\n" , |
331 | ret); |
332 | } |
333 | } else { |
334 | ret = -EINVAL; |
335 | } |
336 | } else if (strncmp("background" , buf, 10) == 0) { |
337 | ret = ath11k_spectral_scan_config(ar, mode: ATH11K_SPECTRAL_BACKGROUND); |
338 | } else if (strncmp("manual" , buf, 6) == 0) { |
339 | ret = ath11k_spectral_scan_config(ar, mode: ATH11K_SPECTRAL_MANUAL); |
340 | } else if (strncmp("disable" , buf, 7) == 0) { |
341 | ret = ath11k_spectral_scan_config(ar, mode: ATH11K_SPECTRAL_DISABLED); |
342 | } else { |
343 | ret = -EINVAL; |
344 | } |
345 | |
346 | unlock: |
347 | mutex_unlock(lock: &ar->conf_mutex); |
348 | |
349 | if (ret) |
350 | return ret; |
351 | |
352 | return count; |
353 | } |
354 | |
355 | static const struct file_operations fops_scan_ctl = { |
356 | .read = ath11k_read_file_spec_scan_ctl, |
357 | .write = ath11k_write_file_spec_scan_ctl, |
358 | .open = simple_open, |
359 | .owner = THIS_MODULE, |
360 | .llseek = default_llseek, |
361 | }; |
362 | |
363 | static ssize_t ath11k_read_file_spectral_count(struct file *file, |
364 | char __user *user_buf, |
365 | size_t count, loff_t *ppos) |
366 | { |
367 | struct ath11k *ar = file->private_data; |
368 | char buf[32]; |
369 | size_t len; |
370 | u16 spectral_count; |
371 | |
372 | mutex_lock(&ar->conf_mutex); |
373 | spectral_count = ar->spectral.count; |
374 | mutex_unlock(lock: &ar->conf_mutex); |
375 | |
376 | len = sprintf(buf, fmt: "%d\n" , spectral_count); |
377 | return simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: len); |
378 | } |
379 | |
380 | static ssize_t ath11k_write_file_spectral_count(struct file *file, |
381 | const char __user *user_buf, |
382 | size_t count, loff_t *ppos) |
383 | { |
384 | struct ath11k *ar = file->private_data; |
385 | unsigned long val; |
386 | ssize_t ret; |
387 | |
388 | ret = kstrtoul_from_user(s: user_buf, count, base: 0, res: &val); |
389 | if (ret) |
390 | return ret; |
391 | |
392 | if (val > ATH11K_SPECTRAL_SCAN_COUNT_MAX) |
393 | return -EINVAL; |
394 | |
395 | mutex_lock(&ar->conf_mutex); |
396 | ar->spectral.count = val; |
397 | mutex_unlock(lock: &ar->conf_mutex); |
398 | |
399 | return count; |
400 | } |
401 | |
402 | static const struct file_operations fops_scan_count = { |
403 | .read = ath11k_read_file_spectral_count, |
404 | .write = ath11k_write_file_spectral_count, |
405 | .open = simple_open, |
406 | .owner = THIS_MODULE, |
407 | .llseek = default_llseek, |
408 | }; |
409 | |
410 | static ssize_t ath11k_read_file_spectral_bins(struct file *file, |
411 | char __user *user_buf, |
412 | size_t count, loff_t *ppos) |
413 | { |
414 | struct ath11k *ar = file->private_data; |
415 | char buf[32]; |
416 | unsigned int bins, fft_size; |
417 | size_t len; |
418 | |
419 | mutex_lock(&ar->conf_mutex); |
420 | |
421 | fft_size = ar->spectral.fft_size; |
422 | bins = 1 << fft_size; |
423 | |
424 | mutex_unlock(lock: &ar->conf_mutex); |
425 | |
426 | len = sprintf(buf, fmt: "%d\n" , bins); |
427 | return simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: len); |
428 | } |
429 | |
430 | static ssize_t ath11k_write_file_spectral_bins(struct file *file, |
431 | const char __user *user_buf, |
432 | size_t count, loff_t *ppos) |
433 | { |
434 | struct ath11k *ar = file->private_data; |
435 | unsigned long val; |
436 | ssize_t ret; |
437 | |
438 | ret = kstrtoul_from_user(s: user_buf, count, base: 0, res: &val); |
439 | if (ret) |
440 | return ret; |
441 | |
442 | if (val < ATH11K_SPECTRAL_MIN_BINS || |
443 | val > ar->ab->hw_params.spectral.max_fft_bins) |
444 | return -EINVAL; |
445 | |
446 | if (!is_power_of_2(n: val)) |
447 | return -EINVAL; |
448 | |
449 | mutex_lock(&ar->conf_mutex); |
450 | ar->spectral.fft_size = ilog2(val); |
451 | mutex_unlock(lock: &ar->conf_mutex); |
452 | |
453 | return count; |
454 | } |
455 | |
456 | static const struct file_operations fops_scan_bins = { |
457 | .read = ath11k_read_file_spectral_bins, |
458 | .write = ath11k_write_file_spectral_bins, |
459 | .open = simple_open, |
460 | .owner = THIS_MODULE, |
461 | .llseek = default_llseek, |
462 | }; |
463 | |
464 | static int ath11k_spectral_pull_summary(struct ath11k *ar, |
465 | struct wmi_dma_buf_release_meta_data *meta, |
466 | struct spectral_summary_fft_report *summary, |
467 | struct ath11k_spectral_summary_report *report) |
468 | { |
469 | report->timestamp = __le32_to_cpu(summary->timestamp); |
470 | report->agc_total_gain = FIELD_GET(SPECTRAL_SUMMARY_INFO0_AGC_TOTAL_GAIN, |
471 | __le32_to_cpu(summary->info0)); |
472 | report->out_of_band_flag = FIELD_GET(SPECTRAL_SUMMARY_INFO0_OB_FLAG, |
473 | __le32_to_cpu(summary->info0)); |
474 | report->grp_idx = FIELD_GET(SPECTRAL_SUMMARY_INFO0_GRP_IDX, |
475 | __le32_to_cpu(summary->info0)); |
476 | report->rf_saturation = FIELD_GET(SPECTRAL_SUMMARY_INFO0_RECENT_RFSAT, |
477 | __le32_to_cpu(summary->info0)); |
478 | report->inb_pwr_db = FIELD_GET(SPECTRAL_SUMMARY_INFO0_INBAND_PWR_DB, |
479 | __le32_to_cpu(summary->info0)); |
480 | report->false_scan = FIELD_GET(SPECTRAL_SUMMARY_INFO0_FALSE_SCAN, |
481 | __le32_to_cpu(summary->info0)); |
482 | report->detector_id = FIELD_GET(SPECTRAL_SUMMARY_INFO0_DETECTOR_ID, |
483 | __le32_to_cpu(summary->info0)); |
484 | report->primary80 = FIELD_GET(SPECTRAL_SUMMARY_INFO0_PRI80, |
485 | __le32_to_cpu(summary->info0)); |
486 | report->peak_idx = FIELD_GET(SPECTRAL_SUMMARY_INFO2_PEAK_SIGNED_IDX, |
487 | __le32_to_cpu(summary->info2)); |
488 | report->peak_mag = FIELD_GET(SPECTRAL_SUMMARY_INFO2_PEAK_MAGNITUDE, |
489 | __le32_to_cpu(summary->info2)); |
490 | report->gain_change = FIELD_GET(SPECTRAL_SUMMARY_INFO2_GAIN_CHANGE, |
491 | __le32_to_cpu(summary->info2)); |
492 | |
493 | memcpy(&report->meta, meta, sizeof(*meta)); |
494 | |
495 | return 0; |
496 | } |
497 | |
498 | static int ath11k_spectral_pull_search(struct ath11k *ar, |
499 | struct spectral_search_fft_report *search, |
500 | struct ath11k_spectral_search_report *report) |
501 | { |
502 | report->timestamp = __le32_to_cpu(search->timestamp); |
503 | report->detector_id = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_DETECTOR_ID, |
504 | __le32_to_cpu(search->info0)); |
505 | report->fft_count = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_FFT_NUM, |
506 | __le32_to_cpu(search->info0)); |
507 | report->radar_check = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_RADAR_CHECK, |
508 | __le32_to_cpu(search->info0)); |
509 | report->peak_idx = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_PEAK_SIGNED_IDX, |
510 | __le32_to_cpu(search->info0)); |
511 | report->chain_idx = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_CHAIN_IDX, |
512 | __le32_to_cpu(search->info0)); |
513 | report->base_pwr_db = FIELD_GET(SPECTRAL_FFT_REPORT_INFO1_BASE_PWR_DB, |
514 | __le32_to_cpu(search->info1)); |
515 | report->total_gain_db = FIELD_GET(SPECTRAL_FFT_REPORT_INFO1_TOTAL_GAIN_DB, |
516 | __le32_to_cpu(search->info1)); |
517 | report->strong_bin_count = FIELD_GET(SPECTRAL_FFT_REPORT_INFO2_NUM_STRONG_BINS, |
518 | __le32_to_cpu(search->info2)); |
519 | report->peak_mag = FIELD_GET(SPECTRAL_FFT_REPORT_INFO2_PEAK_MAGNITUDE, |
520 | __le32_to_cpu(search->info2)); |
521 | report->avg_pwr_db = FIELD_GET(SPECTRAL_FFT_REPORT_INFO2_AVG_PWR_DB, |
522 | __le32_to_cpu(search->info2)); |
523 | report->rel_pwr_db = FIELD_GET(SPECTRAL_FFT_REPORT_INFO2_REL_PWR_DB, |
524 | __le32_to_cpu(search->info2)); |
525 | |
526 | return 0; |
527 | } |
528 | |
529 | static u8 ath11k_spectral_get_max_exp(s8 max_index, u8 max_magnitude, |
530 | int bin_len, u8 *bins) |
531 | { |
532 | int dc_pos; |
533 | u8 max_exp; |
534 | |
535 | dc_pos = bin_len / 2; |
536 | |
537 | /* peak index outside of bins */ |
538 | if (dc_pos <= max_index || -dc_pos >= max_index) |
539 | return 0; |
540 | |
541 | for (max_exp = 0; max_exp < 8; max_exp++) { |
542 | if (bins[dc_pos + max_index] == (max_magnitude >> max_exp)) |
543 | break; |
544 | } |
545 | |
546 | /* max_exp not found */ |
547 | if (bins[dc_pos + max_index] != (max_magnitude >> max_exp)) |
548 | return 0; |
549 | |
550 | return max_exp; |
551 | } |
552 | |
553 | static void ath11k_spectral_parse_fft(u8 *outbins, u8 *inbins, int num_bins, u8 fft_sz) |
554 | { |
555 | int i, j; |
556 | |
557 | i = 0; |
558 | j = 0; |
559 | while (i < num_bins) { |
560 | outbins[i] = inbins[j]; |
561 | i++; |
562 | j += fft_sz; |
563 | } |
564 | } |
565 | |
566 | static |
567 | int ath11k_spectral_process_fft(struct ath11k *ar, |
568 | struct ath11k_spectral_summary_report *summary, |
569 | void *data, |
570 | struct fft_sample_ath11k *fft_sample, |
571 | u32 data_len) |
572 | { |
573 | struct ath11k_base *ab = ar->ab; |
574 | struct spectral_search_fft_report *fft_report = data; |
575 | struct ath11k_spectral_search_report search; |
576 | struct spectral_tlv *tlv; |
577 | int tlv_len, bin_len, num_bins; |
578 | u16 length, freq; |
579 | u8 chan_width_mhz, bin_sz; |
580 | int ret; |
581 | u32 check_length; |
582 | bool fragment_sample = false; |
583 | |
584 | lockdep_assert_held(&ar->spectral.lock); |
585 | |
586 | if (!ab->hw_params.spectral.fft_sz) { |
587 | ath11k_warn(ab, fmt: "invalid bin size type for hw rev %d\n" , |
588 | ab->hw_rev); |
589 | return -EINVAL; |
590 | } |
591 | |
592 | tlv = data; |
593 | tlv_len = FIELD_GET(SPECTRAL_TLV_HDR_LEN, __le32_to_cpu(tlv->header)); |
594 | /* convert Dword into bytes */ |
595 | tlv_len *= ATH11K_SPECTRAL_DWORD_SIZE; |
596 | bin_len = tlv_len - ab->hw_params.spectral.fft_hdr_len; |
597 | |
598 | if (data_len < (bin_len + sizeof(*fft_report))) { |
599 | ath11k_warn(ab, fmt: "mismatch in expected bin len %d and data len %d\n" , |
600 | bin_len, data_len); |
601 | return -EINVAL; |
602 | } |
603 | |
604 | bin_sz = ab->hw_params.spectral.fft_sz + ab->hw_params.spectral.fft_pad_sz; |
605 | num_bins = bin_len / bin_sz; |
606 | /* Only In-band bins are useful to user for visualize */ |
607 | num_bins >>= 1; |
608 | |
609 | if (num_bins < ATH11K_SPECTRAL_MIN_IB_BINS || |
610 | num_bins > ATH11K_SPECTRAL_MAX_IB_BINS(ab) || |
611 | !is_power_of_2(n: num_bins)) { |
612 | ath11k_warn(ab, fmt: "Invalid num of bins %d\n" , num_bins); |
613 | return -EINVAL; |
614 | } |
615 | |
616 | check_length = sizeof(*fft_report) + (num_bins * ab->hw_params.spectral.fft_sz); |
617 | ret = ath11k_dbring_validate_buffer(ar, data, size: check_length); |
618 | if (ret) { |
619 | ath11k_warn(ab: ar->ab, fmt: "found magic value in fft data, dropping\n" ); |
620 | return ret; |
621 | } |
622 | |
623 | ret = ath11k_spectral_pull_search(ar, search: data, report: &search); |
624 | if (ret) { |
625 | ath11k_warn(ab, fmt: "failed to pull search report %d\n" , ret); |
626 | return ret; |
627 | } |
628 | |
629 | chan_width_mhz = summary->meta.ch_width; |
630 | |
631 | switch (chan_width_mhz) { |
632 | case ATH11K_SPECTRAL_20MHZ: |
633 | case ATH11K_SPECTRAL_40MHZ: |
634 | case ATH11K_SPECTRAL_80MHZ: |
635 | fft_sample->chan_width_mhz = chan_width_mhz; |
636 | break; |
637 | case ATH11K_SPECTRAL_160MHZ: |
638 | if (ab->hw_params.spectral.fragment_160mhz) { |
639 | chan_width_mhz /= 2; |
640 | fragment_sample = true; |
641 | } |
642 | fft_sample->chan_width_mhz = chan_width_mhz; |
643 | break; |
644 | default: |
645 | ath11k_warn(ab, fmt: "invalid channel width %d\n" , chan_width_mhz); |
646 | return -EINVAL; |
647 | } |
648 | |
649 | length = sizeof(*fft_sample) - sizeof(struct fft_sample_tlv) + num_bins; |
650 | fft_sample->tlv.type = ATH_FFT_SAMPLE_ATH11K; |
651 | fft_sample->tlv.length = __cpu_to_be16(length); |
652 | |
653 | fft_sample->tsf = __cpu_to_be32(search.timestamp); |
654 | fft_sample->max_magnitude = __cpu_to_be16(search.peak_mag); |
655 | fft_sample->max_index = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_PEAK_SIGNED_IDX, |
656 | __le32_to_cpu(fft_report->info0)); |
657 | |
658 | summary->inb_pwr_db >>= 1; |
659 | fft_sample->rssi = __cpu_to_be16(summary->inb_pwr_db); |
660 | fft_sample->noise = __cpu_to_be32(summary->meta.noise_floor[search.chain_idx]); |
661 | |
662 | freq = summary->meta.freq1; |
663 | fft_sample->freq1 = __cpu_to_be16(freq); |
664 | |
665 | freq = summary->meta.freq2; |
666 | fft_sample->freq2 = __cpu_to_be16(freq); |
667 | |
668 | /* If freq2 is available then the spectral scan results are fragmented |
669 | * as primary and secondary |
670 | */ |
671 | if (fragment_sample && freq) { |
672 | if (!ar->spectral.is_primary) |
673 | fft_sample->freq1 = cpu_to_be16(freq); |
674 | |
675 | /* We have to toggle the is_primary to handle the next report */ |
676 | ar->spectral.is_primary = !ar->spectral.is_primary; |
677 | } |
678 | |
679 | ath11k_spectral_parse_fft(outbins: fft_sample->data, inbins: fft_report->bins, num_bins, |
680 | fft_sz: ab->hw_params.spectral.fft_sz); |
681 | |
682 | fft_sample->max_exp = ath11k_spectral_get_max_exp(max_index: fft_sample->max_index, |
683 | max_magnitude: search.peak_mag, |
684 | bin_len: num_bins, |
685 | bins: fft_sample->data); |
686 | |
687 | if (ar->spectral.rfs_scan) |
688 | relay_write(chan: ar->spectral.rfs_scan, data: fft_sample, |
689 | length: length + sizeof(struct fft_sample_tlv)); |
690 | |
691 | return 0; |
692 | } |
693 | |
694 | static int ath11k_spectral_process_data(struct ath11k *ar, |
695 | struct ath11k_dbring_data *param) |
696 | { |
697 | struct ath11k_base *ab = ar->ab; |
698 | struct spectral_tlv *tlv; |
699 | struct spectral_summary_fft_report *summary = NULL; |
700 | struct ath11k_spectral_summary_report summ_rpt; |
701 | struct fft_sample_ath11k *fft_sample = NULL; |
702 | u8 *data; |
703 | u32 data_len, i; |
704 | u8 sign, tag; |
705 | int tlv_len, sample_sz; |
706 | int ret; |
707 | bool quit = false; |
708 | |
709 | spin_lock_bh(lock: &ar->spectral.lock); |
710 | |
711 | if (!ar->spectral.enabled) { |
712 | ret = -EINVAL; |
713 | goto unlock; |
714 | } |
715 | |
716 | sample_sz = sizeof(*fft_sample) + ATH11K_SPECTRAL_MAX_IB_BINS(ab); |
717 | fft_sample = kmalloc(size: sample_sz, GFP_ATOMIC); |
718 | if (!fft_sample) { |
719 | ret = -ENOBUFS; |
720 | goto unlock; |
721 | } |
722 | |
723 | data = param->data; |
724 | data_len = param->data_sz; |
725 | i = 0; |
726 | while (!quit && (i < data_len)) { |
727 | if ((i + sizeof(*tlv)) > data_len) { |
728 | ath11k_warn(ab, fmt: "failed to parse spectral tlv hdr at bytes %d\n" , |
729 | i); |
730 | ret = -EINVAL; |
731 | goto err; |
732 | } |
733 | |
734 | tlv = (struct spectral_tlv *)&data[i]; |
735 | sign = FIELD_GET(SPECTRAL_TLV_HDR_SIGN, |
736 | __le32_to_cpu(tlv->header)); |
737 | if (sign != ATH11K_SPECTRAL_SIGNATURE) { |
738 | ath11k_warn(ab, fmt: "Invalid sign 0x%x at bytes %d\n" , |
739 | sign, i); |
740 | ret = -EINVAL; |
741 | goto err; |
742 | } |
743 | |
744 | tlv_len = FIELD_GET(SPECTRAL_TLV_HDR_LEN, |
745 | __le32_to_cpu(tlv->header)); |
746 | /* convert Dword into bytes */ |
747 | tlv_len *= ATH11K_SPECTRAL_DWORD_SIZE; |
748 | if ((i + sizeof(*tlv) + tlv_len) > data_len) { |
749 | ath11k_warn(ab, fmt: "failed to parse spectral tlv payload at bytes %d tlv_len:%d data_len:%d\n" , |
750 | i, tlv_len, data_len); |
751 | ret = -EINVAL; |
752 | goto err; |
753 | } |
754 | |
755 | tag = FIELD_GET(SPECTRAL_TLV_HDR_TAG, |
756 | __le32_to_cpu(tlv->header)); |
757 | switch (tag) { |
758 | case ATH11K_SPECTRAL_TAG_SCAN_SUMMARY: |
759 | /* HW bug in tlv length of summary report, |
760 | * HW report 3 DWORD size but the data payload |
761 | * is 4 DWORD size (16 bytes). |
762 | * Need to remove this workaround once HW bug fixed |
763 | */ |
764 | tlv_len = sizeof(*summary) - sizeof(*tlv) + |
765 | ab->hw_params.spectral.summary_pad_sz; |
766 | |
767 | if (tlv_len < (sizeof(*summary) - sizeof(*tlv))) { |
768 | ath11k_warn(ab, fmt: "failed to parse spectral summary at bytes %d tlv_len:%d\n" , |
769 | i, tlv_len); |
770 | ret = -EINVAL; |
771 | goto err; |
772 | } |
773 | |
774 | ret = ath11k_dbring_validate_buffer(ar, data, size: tlv_len); |
775 | if (ret) { |
776 | ath11k_warn(ab: ar->ab, fmt: "found magic value in spectral summary, dropping\n" ); |
777 | goto err; |
778 | } |
779 | |
780 | summary = (struct spectral_summary_fft_report *)tlv; |
781 | ath11k_spectral_pull_summary(ar, meta: ¶m->meta, |
782 | summary, report: &summ_rpt); |
783 | break; |
784 | case ATH11K_SPECTRAL_TAG_SCAN_SEARCH: |
785 | if (tlv_len < (sizeof(struct spectral_search_fft_report) - |
786 | sizeof(*tlv))) { |
787 | ath11k_warn(ab, fmt: "failed to parse spectral search fft at bytes %d\n" , |
788 | i); |
789 | ret = -EINVAL; |
790 | goto err; |
791 | } |
792 | |
793 | memset(fft_sample, 0, sample_sz); |
794 | ret = ath11k_spectral_process_fft(ar, summary: &summ_rpt, data: tlv, |
795 | fft_sample, |
796 | data_len: data_len - i); |
797 | if (ret) { |
798 | ath11k_warn(ab, fmt: "failed to process spectral fft at bytes %d\n" , |
799 | i); |
800 | goto err; |
801 | } |
802 | quit = true; |
803 | break; |
804 | } |
805 | |
806 | i += sizeof(*tlv) + tlv_len; |
807 | } |
808 | |
809 | ret = 0; |
810 | |
811 | err: |
812 | kfree(objp: fft_sample); |
813 | unlock: |
814 | spin_unlock_bh(lock: &ar->spectral.lock); |
815 | return ret; |
816 | } |
817 | |
818 | static int ath11k_spectral_ring_alloc(struct ath11k *ar, |
819 | struct ath11k_dbring_cap *db_cap) |
820 | { |
821 | struct ath11k_spectral *sp = &ar->spectral; |
822 | int ret; |
823 | |
824 | ret = ath11k_dbring_srng_setup(ar, ring: &sp->rx_ring, |
825 | ring_num: 0, num_entries: db_cap->min_elem); |
826 | if (ret) { |
827 | ath11k_warn(ab: ar->ab, fmt: "failed to setup db ring\n" ); |
828 | return ret; |
829 | } |
830 | |
831 | ath11k_dbring_set_cfg(ar, ring: &sp->rx_ring, |
832 | ATH11K_SPECTRAL_NUM_RESP_PER_EVENT, |
833 | ATH11K_SPECTRAL_EVENT_TIMEOUT_MS, |
834 | handler: ath11k_spectral_process_data); |
835 | |
836 | ret = ath11k_dbring_buf_setup(ar, ring: &sp->rx_ring, db_cap); |
837 | if (ret) { |
838 | ath11k_warn(ab: ar->ab, fmt: "failed to setup db ring buffer\n" ); |
839 | goto srng_cleanup; |
840 | } |
841 | |
842 | ret = ath11k_dbring_wmi_cfg_setup(ar, ring: &sp->rx_ring, |
843 | id: WMI_DIRECT_BUF_SPECTRAL); |
844 | if (ret) { |
845 | ath11k_warn(ab: ar->ab, fmt: "failed to setup db ring cfg\n" ); |
846 | goto buffer_cleanup; |
847 | } |
848 | |
849 | return 0; |
850 | |
851 | buffer_cleanup: |
852 | ath11k_dbring_buf_cleanup(ar, ring: &sp->rx_ring); |
853 | srng_cleanup: |
854 | ath11k_dbring_srng_cleanup(ar, ring: &sp->rx_ring); |
855 | return ret; |
856 | } |
857 | |
858 | static inline void ath11k_spectral_ring_free(struct ath11k *ar) |
859 | { |
860 | struct ath11k_spectral *sp = &ar->spectral; |
861 | |
862 | ath11k_dbring_srng_cleanup(ar, ring: &sp->rx_ring); |
863 | ath11k_dbring_buf_cleanup(ar, ring: &sp->rx_ring); |
864 | } |
865 | |
866 | static inline void ath11k_spectral_debug_unregister(struct ath11k *ar) |
867 | { |
868 | debugfs_remove(dentry: ar->spectral.scan_bins); |
869 | ar->spectral.scan_bins = NULL; |
870 | |
871 | debugfs_remove(dentry: ar->spectral.scan_count); |
872 | ar->spectral.scan_count = NULL; |
873 | |
874 | debugfs_remove(dentry: ar->spectral.scan_ctl); |
875 | ar->spectral.scan_ctl = NULL; |
876 | |
877 | if (ar->spectral.rfs_scan) { |
878 | relay_close(chan: ar->spectral.rfs_scan); |
879 | ar->spectral.rfs_scan = NULL; |
880 | } |
881 | } |
882 | |
883 | int ath11k_spectral_vif_stop(struct ath11k_vif *arvif) |
884 | { |
885 | if (!arvif->spectral_enabled) |
886 | return 0; |
887 | |
888 | return ath11k_spectral_scan_config(ar: arvif->ar, mode: ATH11K_SPECTRAL_DISABLED); |
889 | } |
890 | |
891 | void ath11k_spectral_reset_buffer(struct ath11k *ar) |
892 | { |
893 | if (!ar->spectral.enabled) |
894 | return; |
895 | |
896 | if (ar->spectral.rfs_scan) |
897 | relay_reset(chan: ar->spectral.rfs_scan); |
898 | } |
899 | |
900 | void ath11k_spectral_deinit(struct ath11k_base *ab) |
901 | { |
902 | struct ath11k *ar; |
903 | struct ath11k_spectral *sp; |
904 | int i; |
905 | |
906 | for (i = 0; i < ab->num_radios; i++) { |
907 | ar = ab->pdevs[i].ar; |
908 | sp = &ar->spectral; |
909 | |
910 | if (!sp->enabled) |
911 | continue; |
912 | |
913 | mutex_lock(&ar->conf_mutex); |
914 | ath11k_spectral_scan_config(ar, mode: ATH11K_SPECTRAL_DISABLED); |
915 | mutex_unlock(lock: &ar->conf_mutex); |
916 | |
917 | spin_lock_bh(lock: &sp->lock); |
918 | sp->enabled = false; |
919 | spin_unlock_bh(lock: &sp->lock); |
920 | |
921 | ath11k_spectral_debug_unregister(ar); |
922 | ath11k_spectral_ring_free(ar); |
923 | } |
924 | } |
925 | |
926 | static inline int ath11k_spectral_debug_register(struct ath11k *ar) |
927 | { |
928 | int ret; |
929 | |
930 | ar->spectral.rfs_scan = relay_open(base_filename: "spectral_scan" , |
931 | parent: ar->debug.debugfs_pdev, |
932 | ATH11K_SPECTRAL_SUB_BUFF_SIZE(ar->ab), |
933 | ATH11K_SPECTRAL_NUM_SUB_BUF, |
934 | cb: &rfs_scan_cb, NULL); |
935 | if (!ar->spectral.rfs_scan) { |
936 | ath11k_warn(ab: ar->ab, fmt: "failed to open relay in pdev %d\n" , |
937 | ar->pdev_idx); |
938 | return -EINVAL; |
939 | } |
940 | |
941 | ar->spectral.scan_ctl = debugfs_create_file(name: "spectral_scan_ctl" , |
942 | mode: 0600, |
943 | parent: ar->debug.debugfs_pdev, data: ar, |
944 | fops: &fops_scan_ctl); |
945 | if (!ar->spectral.scan_ctl) { |
946 | ath11k_warn(ab: ar->ab, fmt: "failed to open debugfs in pdev %d\n" , |
947 | ar->pdev_idx); |
948 | ret = -EINVAL; |
949 | goto debug_unregister; |
950 | } |
951 | |
952 | ar->spectral.scan_count = debugfs_create_file(name: "spectral_count" , |
953 | mode: 0600, |
954 | parent: ar->debug.debugfs_pdev, data: ar, |
955 | fops: &fops_scan_count); |
956 | if (!ar->spectral.scan_count) { |
957 | ath11k_warn(ab: ar->ab, fmt: "failed to open debugfs in pdev %d\n" , |
958 | ar->pdev_idx); |
959 | ret = -EINVAL; |
960 | goto debug_unregister; |
961 | } |
962 | |
963 | ar->spectral.scan_bins = debugfs_create_file(name: "spectral_bins" , |
964 | mode: 0600, |
965 | parent: ar->debug.debugfs_pdev, data: ar, |
966 | fops: &fops_scan_bins); |
967 | if (!ar->spectral.scan_bins) { |
968 | ath11k_warn(ab: ar->ab, fmt: "failed to open debugfs in pdev %d\n" , |
969 | ar->pdev_idx); |
970 | ret = -EINVAL; |
971 | goto debug_unregister; |
972 | } |
973 | |
974 | return 0; |
975 | |
976 | debug_unregister: |
977 | ath11k_spectral_debug_unregister(ar); |
978 | return ret; |
979 | } |
980 | |
981 | int ath11k_spectral_init(struct ath11k_base *ab) |
982 | { |
983 | struct ath11k *ar; |
984 | struct ath11k_spectral *sp; |
985 | struct ath11k_dbring_cap db_cap; |
986 | int ret; |
987 | int i; |
988 | |
989 | if (!test_bit(WMI_TLV_SERVICE_FREQINFO_IN_METADATA, |
990 | ab->wmi_ab.svc_map)) |
991 | return 0; |
992 | |
993 | if (!ab->hw_params.spectral.fft_sz) |
994 | return 0; |
995 | |
996 | for (i = 0; i < ab->num_radios; i++) { |
997 | ar = ab->pdevs[i].ar; |
998 | sp = &ar->spectral; |
999 | |
1000 | ret = ath11k_dbring_get_cap(ab: ar->ab, pdev_idx: ar->pdev_idx, |
1001 | id: WMI_DIRECT_BUF_SPECTRAL, |
1002 | db_cap: &db_cap); |
1003 | if (ret) |
1004 | continue; |
1005 | |
1006 | idr_init(idr: &sp->rx_ring.bufs_idr); |
1007 | spin_lock_init(&sp->rx_ring.idr_lock); |
1008 | spin_lock_init(&sp->lock); |
1009 | |
1010 | ret = ath11k_spectral_ring_alloc(ar, db_cap: &db_cap); |
1011 | if (ret) { |
1012 | ath11k_warn(ab, fmt: "failed to init spectral ring for pdev %d\n" , |
1013 | i); |
1014 | goto deinit; |
1015 | } |
1016 | |
1017 | spin_lock_bh(lock: &sp->lock); |
1018 | |
1019 | sp->mode = ATH11K_SPECTRAL_DISABLED; |
1020 | sp->count = ATH11K_WMI_SPECTRAL_COUNT_DEFAULT; |
1021 | sp->fft_size = ATH11K_WMI_SPECTRAL_FFT_SIZE_DEFAULT; |
1022 | sp->enabled = true; |
1023 | |
1024 | spin_unlock_bh(lock: &sp->lock); |
1025 | |
1026 | ret = ath11k_spectral_debug_register(ar); |
1027 | if (ret) { |
1028 | ath11k_warn(ab, fmt: "failed to register spectral for pdev %d\n" , |
1029 | i); |
1030 | goto deinit; |
1031 | } |
1032 | } |
1033 | |
1034 | return 0; |
1035 | |
1036 | deinit: |
1037 | ath11k_spectral_deinit(ab); |
1038 | return ret; |
1039 | } |
1040 | |
1041 | enum ath11k_spectral_mode ath11k_spectral_get_mode(struct ath11k *ar) |
1042 | { |
1043 | if (ar->spectral.enabled) |
1044 | return ar->spectral.mode; |
1045 | else |
1046 | return ATH11K_SPECTRAL_DISABLED; |
1047 | } |
1048 | |
1049 | struct ath11k_dbring *ath11k_spectral_get_dbring(struct ath11k *ar) |
1050 | { |
1051 | if (ar->spectral.enabled) |
1052 | return &ar->spectral.rx_ring; |
1053 | else |
1054 | return NULL; |
1055 | } |
1056 | |