xref: /linux/drivers/net/wireless/ath/ath9k/common-spectral.c (revision c3bdd5e65185f46150b3bac103b3854040487857)
1f65c0825SSujith Manoharan /*
2f65c0825SSujith Manoharan  * Copyright (c) 2013 Qualcomm Atheros, Inc.
3f65c0825SSujith Manoharan  *
4f65c0825SSujith Manoharan  * Permission to use, copy, modify, and/or distribute this software for any
5f65c0825SSujith Manoharan  * purpose with or without fee is hereby granted, provided that the above
6f65c0825SSujith Manoharan  * copyright notice and this permission notice appear in all copies.
7f65c0825SSujith Manoharan  *
8f65c0825SSujith Manoharan  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9f65c0825SSujith Manoharan  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10f65c0825SSujith Manoharan  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11f65c0825SSujith Manoharan  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12f65c0825SSujith Manoharan  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13f65c0825SSujith Manoharan  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14f65c0825SSujith Manoharan  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15f65c0825SSujith Manoharan  */
16f65c0825SSujith Manoharan 
17f65c0825SSujith Manoharan #include <linux/relay.h>
182aa56ccaSNick Kossifidis #include <linux/random.h>
19f65c0825SSujith Manoharan #include "ath9k.h"
20f65c0825SSujith Manoharan 
21f65c0825SSujith Manoharan static s8 fix_rssi_inv_only(u8 rssi_val)
22f65c0825SSujith Manoharan {
23f65c0825SSujith Manoharan 	if (rssi_val == 128)
24f65c0825SSujith Manoharan 		rssi_val = 0;
25f65c0825SSujith Manoharan 	return (s8) rssi_val;
26f65c0825SSujith Manoharan }
27f65c0825SSujith Manoharan 
281111d426SOleksij Rempel static void ath_debug_send_fft_sample(struct ath_spec_scan_priv *spec_priv,
29f65c0825SSujith Manoharan 				      struct fft_sample_tlv *fft_sample_tlv)
30f65c0825SSujith Manoharan {
31f65c0825SSujith Manoharan 	int length;
321111d426SOleksij Rempel 	if (!spec_priv->rfs_chan_spec_scan)
33f65c0825SSujith Manoharan 		return;
34f65c0825SSujith Manoharan 
35f65c0825SSujith Manoharan 	length = __be16_to_cpu(fft_sample_tlv->length) +
36f65c0825SSujith Manoharan 		 sizeof(*fft_sample_tlv);
371111d426SOleksij Rempel 	relay_write(spec_priv->rfs_chan_spec_scan, fft_sample_tlv, length);
38f65c0825SSujith Manoharan }
39f65c0825SSujith Manoharan 
4072dd2cdaSNick Kossifidis typedef int (ath_cmn_fft_idx_validator) (u8 *sample_end, int bytes_read);
4172dd2cdaSNick Kossifidis 
4272dd2cdaSNick Kossifidis static int
4372dd2cdaSNick Kossifidis ath_cmn_max_idx_verify_ht20_fft(u8 *sample_end, int bytes_read)
4472dd2cdaSNick Kossifidis {
4572dd2cdaSNick Kossifidis 	struct ath_ht20_mag_info *mag_info;
4672dd2cdaSNick Kossifidis 	u8 *sample;
4772dd2cdaSNick Kossifidis 	u16 max_magnitude;
4872dd2cdaSNick Kossifidis 	u8 max_index;
4972dd2cdaSNick Kossifidis 	u8 max_exp;
5072dd2cdaSNick Kossifidis 
5172dd2cdaSNick Kossifidis 	/* Sanity check so that we don't read outside the read
5272dd2cdaSNick Kossifidis 	 * buffer
5372dd2cdaSNick Kossifidis 	 */
5472dd2cdaSNick Kossifidis 	if (bytes_read < SPECTRAL_HT20_SAMPLE_LEN - 1)
5572dd2cdaSNick Kossifidis 		return -1;
5672dd2cdaSNick Kossifidis 
5772dd2cdaSNick Kossifidis 	mag_info = (struct ath_ht20_mag_info *) (sample_end -
5872dd2cdaSNick Kossifidis 				sizeof(struct ath_ht20_mag_info) + 1);
5972dd2cdaSNick Kossifidis 
6072dd2cdaSNick Kossifidis 	sample = sample_end - SPECTRAL_HT20_SAMPLE_LEN + 1;
6172dd2cdaSNick Kossifidis 
622f85786bSSimon Wunderlich 	max_index = spectral_max_index_ht20(mag_info->all_bins);
6372dd2cdaSNick Kossifidis 	max_magnitude = spectral_max_magnitude(mag_info->all_bins);
6472dd2cdaSNick Kossifidis 
6572dd2cdaSNick Kossifidis 	max_exp = mag_info->max_exp & 0xf;
6672dd2cdaSNick Kossifidis 
6772dd2cdaSNick Kossifidis 	/* Don't try to read something outside the read buffer
6872dd2cdaSNick Kossifidis 	 * in case of a missing byte (so bins[0] will be outside
6972dd2cdaSNick Kossifidis 	 * the read buffer)
7072dd2cdaSNick Kossifidis 	 */
7172dd2cdaSNick Kossifidis 	if (bytes_read < SPECTRAL_HT20_SAMPLE_LEN && max_index < 1)
7272dd2cdaSNick Kossifidis 		return -1;
7372dd2cdaSNick Kossifidis 
744e7a3fa5SSimon Wunderlich 	if ((sample[max_index] & 0xf8) != ((max_magnitude >> max_exp) & 0xf8))
7572dd2cdaSNick Kossifidis 		return -1;
7672dd2cdaSNick Kossifidis 	else
7772dd2cdaSNick Kossifidis 		return 0;
7872dd2cdaSNick Kossifidis }
7972dd2cdaSNick Kossifidis 
8072dd2cdaSNick Kossifidis static int
8172dd2cdaSNick Kossifidis ath_cmn_max_idx_verify_ht20_40_fft(u8 *sample_end, int bytes_read)
8272dd2cdaSNick Kossifidis {
8372dd2cdaSNick Kossifidis 	struct ath_ht20_40_mag_info *mag_info;
8472dd2cdaSNick Kossifidis 	u8 *sample;
8572dd2cdaSNick Kossifidis 	u16 lower_mag, upper_mag;
8672dd2cdaSNick Kossifidis 	u8 lower_max_index, upper_max_index;
8772dd2cdaSNick Kossifidis 	u8 max_exp;
8872dd2cdaSNick Kossifidis 	int dc_pos = SPECTRAL_HT20_40_NUM_BINS / 2;
8972dd2cdaSNick Kossifidis 
9072dd2cdaSNick Kossifidis 	/* Sanity check so that we don't read outside the read
9172dd2cdaSNick Kossifidis 	 * buffer
9272dd2cdaSNick Kossifidis 	 */
9372dd2cdaSNick Kossifidis 	if (bytes_read < SPECTRAL_HT20_40_SAMPLE_LEN - 1)
9472dd2cdaSNick Kossifidis 		return -1;
9572dd2cdaSNick Kossifidis 
9672dd2cdaSNick Kossifidis 	mag_info = (struct ath_ht20_40_mag_info *) (sample_end -
9772dd2cdaSNick Kossifidis 				sizeof(struct ath_ht20_40_mag_info) + 1);
9872dd2cdaSNick Kossifidis 
9972dd2cdaSNick Kossifidis 	sample = sample_end - SPECTRAL_HT20_40_SAMPLE_LEN + 1;
10072dd2cdaSNick Kossifidis 
10172dd2cdaSNick Kossifidis 	lower_mag = spectral_max_magnitude(mag_info->lower_bins);
1022f85786bSSimon Wunderlich 	lower_max_index = spectral_max_index_ht40(mag_info->lower_bins);
10372dd2cdaSNick Kossifidis 
10472dd2cdaSNick Kossifidis 	upper_mag = spectral_max_magnitude(mag_info->upper_bins);
1052f85786bSSimon Wunderlich 	upper_max_index = spectral_max_index_ht40(mag_info->upper_bins);
10672dd2cdaSNick Kossifidis 
10772dd2cdaSNick Kossifidis 	max_exp = mag_info->max_exp & 0xf;
10872dd2cdaSNick Kossifidis 
10972dd2cdaSNick Kossifidis 	/* Don't try to read something outside the read buffer
11072dd2cdaSNick Kossifidis 	 * in case of a missing byte (so bins[0] will be outside
11172dd2cdaSNick Kossifidis 	 * the read buffer)
11272dd2cdaSNick Kossifidis 	 */
11372dd2cdaSNick Kossifidis 	if (bytes_read < SPECTRAL_HT20_40_SAMPLE_LEN &&
11472dd2cdaSNick Kossifidis 	   ((upper_max_index < 1) || (lower_max_index < 1)))
11572dd2cdaSNick Kossifidis 		return -1;
11672dd2cdaSNick Kossifidis 
1174e7a3fa5SSimon Wunderlich 	if (((sample[upper_max_index + dc_pos] & 0xf8) !=
1184e7a3fa5SSimon Wunderlich 	     ((upper_mag >> max_exp) & 0xf8)) ||
1194e7a3fa5SSimon Wunderlich 	    ((sample[lower_max_index] & 0xf8) !=
1204e7a3fa5SSimon Wunderlich 	     ((lower_mag >> max_exp) & 0xf8)))
12172dd2cdaSNick Kossifidis 		return -1;
12272dd2cdaSNick Kossifidis 	else
12372dd2cdaSNick Kossifidis 		return 0;
12472dd2cdaSNick Kossifidis }
12572dd2cdaSNick Kossifidis 
12658b5e4c7SNick Kossifidis typedef int (ath_cmn_fft_sample_handler) (struct ath_rx_status *rs,
12758b5e4c7SNick Kossifidis 			struct ath_spec_scan_priv *spec_priv,
12858b5e4c7SNick Kossifidis 			u8 *sample_buf, u64 tsf, u16 freq, int chan_type);
12958b5e4c7SNick Kossifidis 
13058b5e4c7SNick Kossifidis static int
13158b5e4c7SNick Kossifidis ath_cmn_process_ht20_fft(struct ath_rx_status *rs,
13258b5e4c7SNick Kossifidis 			struct ath_spec_scan_priv *spec_priv,
13358b5e4c7SNick Kossifidis 			u8 *sample_buf,
13458b5e4c7SNick Kossifidis 			u64 tsf, u16 freq, int chan_type)
135f65c0825SSujith Manoharan {
136f65c0825SSujith Manoharan 	struct fft_sample_ht20 fft_sample_20;
1377fa580c1SNick Kossifidis 	struct ath_common *common = ath9k_hw_common(spec_priv->ah);
13858b5e4c7SNick Kossifidis 	struct ath_hw *ah = spec_priv->ah;
13958b5e4c7SNick Kossifidis 	struct ath_ht20_mag_info *mag_info;
140f65c0825SSujith Manoharan 	struct fft_sample_tlv *tlv;
1417fa580c1SNick Kossifidis 	int i = 0;
1427fa580c1SNick Kossifidis 	int ret = 0;
14358b5e4c7SNick Kossifidis 	int dc_pos = SPECTRAL_HT20_NUM_BINS / 2;
1447fa580c1SNick Kossifidis 	u16 magnitude, tmp_mag, length;
1457fa580c1SNick Kossifidis 	u8 max_index, bitmap_w, max_exp;
146f65c0825SSujith Manoharan 
14758b5e4c7SNick Kossifidis 	length = sizeof(fft_sample_20) - sizeof(struct fft_sample_tlv);
14858b5e4c7SNick Kossifidis 	fft_sample_20.tlv.type = ATH_FFT_SAMPLE_HT20;
14958b5e4c7SNick Kossifidis 	fft_sample_20.tlv.length = __cpu_to_be16(length);
15058b5e4c7SNick Kossifidis 	fft_sample_20.freq = __cpu_to_be16(freq);
15158b5e4c7SNick Kossifidis 	fft_sample_20.rssi = fix_rssi_inv_only(rs->rs_rssi_ctl[0]);
15258b5e4c7SNick Kossifidis 	fft_sample_20.noise = ah->noise;
153f65c0825SSujith Manoharan 
15458b5e4c7SNick Kossifidis 	mag_info = (struct ath_ht20_mag_info *) (sample_buf +
15558b5e4c7SNick Kossifidis 					SPECTRAL_HT20_NUM_BINS);
156f65c0825SSujith Manoharan 
15758b5e4c7SNick Kossifidis 	magnitude = spectral_max_magnitude(mag_info->all_bins);
15858b5e4c7SNick Kossifidis 	fft_sample_20.max_magnitude = __cpu_to_be16(magnitude);
159f65c0825SSujith Manoharan 
1602f85786bSSimon Wunderlich 	max_index = spectral_max_index_ht20(mag_info->all_bins);
16158b5e4c7SNick Kossifidis 	fft_sample_20.max_index = max_index;
162f65c0825SSujith Manoharan 
16358b5e4c7SNick Kossifidis 	bitmap_w = spectral_bitmap_weight(mag_info->all_bins);
16458b5e4c7SNick Kossifidis 	fft_sample_20.bitmap_weight = bitmap_w;
16558b5e4c7SNick Kossifidis 
1667fa580c1SNick Kossifidis 	max_exp = mag_info->max_exp & 0xf;
1677fa580c1SNick Kossifidis 	fft_sample_20.max_exp = max_exp;
16858b5e4c7SNick Kossifidis 
16958b5e4c7SNick Kossifidis 	fft_sample_20.tsf = __cpu_to_be64(tsf);
17058b5e4c7SNick Kossifidis 
17158b5e4c7SNick Kossifidis 	memcpy(fft_sample_20.data, sample_buf, SPECTRAL_HT20_NUM_BINS);
172f65c0825SSujith Manoharan 
1737fa580c1SNick Kossifidis 	ath_dbg(common, SPECTRAL_SCAN, "FFT HT20 frame: max mag 0x%X,"
1747fa580c1SNick Kossifidis 					"max_mag_idx %i\n",
1757fa580c1SNick Kossifidis 					magnitude >> max_exp,
1767fa580c1SNick Kossifidis 					max_index);
1777fa580c1SNick Kossifidis 
1784e7a3fa5SSimon Wunderlich 	if ((fft_sample_20.data[max_index] & 0xf8) !=
1794e7a3fa5SSimon Wunderlich 	    ((magnitude >> max_exp) & 0xf8)) {
1807fa580c1SNick Kossifidis 		ath_dbg(common, SPECTRAL_SCAN, "Magnitude mismatch !\n");
1817fa580c1SNick Kossifidis 		ret = -1;
1827fa580c1SNick Kossifidis 	}
1837fa580c1SNick Kossifidis 
184f65c0825SSujith Manoharan 	/* DC value (value in the middle) is the blind spot of the spectral
185f65c0825SSujith Manoharan 	 * sample and invalid, interpolate it.
186f65c0825SSujith Manoharan 	 */
18758b5e4c7SNick Kossifidis 	fft_sample_20.data[dc_pos] = (fft_sample_20.data[dc_pos + 1] +
18858b5e4c7SNick Kossifidis 					fft_sample_20.data[dc_pos - 1]) / 2;
189f65c0825SSujith Manoharan 
1907fa580c1SNick Kossifidis 	/* Check if the maximum magnitude is indeed maximum,
1917fa580c1SNick Kossifidis 	 * also if the maximum value was at dc_pos, calculate
1927fa580c1SNick Kossifidis 	 * a new one (since value at dc_pos is invalid).
1937fa580c1SNick Kossifidis 	 */
1947fa580c1SNick Kossifidis 	if (max_index == dc_pos) {
1957fa580c1SNick Kossifidis 		tmp_mag = 0;
1967fa580c1SNick Kossifidis 		for (i = 0; i < dc_pos; i++) {
1977fa580c1SNick Kossifidis 			if (fft_sample_20.data[i] > tmp_mag) {
1987fa580c1SNick Kossifidis 				tmp_mag = fft_sample_20.data[i];
1997fa580c1SNick Kossifidis 				fft_sample_20.max_index = i;
2007fa580c1SNick Kossifidis 			}
2017fa580c1SNick Kossifidis 		}
2027fa580c1SNick Kossifidis 
2037fa580c1SNick Kossifidis 		magnitude = tmp_mag << max_exp;
2047fa580c1SNick Kossifidis 		fft_sample_20.max_magnitude = __cpu_to_be16(magnitude);
2057fa580c1SNick Kossifidis 
2067fa580c1SNick Kossifidis 		ath_dbg(common, SPECTRAL_SCAN,
2077fa580c1SNick Kossifidis 			"Calculated new lower max 0x%X at %i\n",
2087fa580c1SNick Kossifidis 			tmp_mag, fft_sample_20.max_index);
2097fa580c1SNick Kossifidis 	} else
2107fa580c1SNick Kossifidis 	for (i = 0; i < SPECTRAL_HT20_NUM_BINS; i++) {
2117fa580c1SNick Kossifidis 		if (fft_sample_20.data[i] == (magnitude >> max_exp))
2127fa580c1SNick Kossifidis 			ath_dbg(common, SPECTRAL_SCAN,
2137fa580c1SNick Kossifidis 				"Got max: 0x%X at index %i\n",
2147fa580c1SNick Kossifidis 				fft_sample_20.data[i], i);
2157fa580c1SNick Kossifidis 
2167fa580c1SNick Kossifidis 		if (fft_sample_20.data[i] > (magnitude >> max_exp)) {
2177fa580c1SNick Kossifidis 			ath_dbg(common, SPECTRAL_SCAN,
2187fa580c1SNick Kossifidis 				"Got bin %i greater than max: 0x%X\n",
2197fa580c1SNick Kossifidis 				i, fft_sample_20.data[i]);
2207fa580c1SNick Kossifidis 			ret = -1;
2217fa580c1SNick Kossifidis 		}
2227fa580c1SNick Kossifidis 	}
2237fa580c1SNick Kossifidis 
2247fa580c1SNick Kossifidis 	if (ret < 0)
2257fa580c1SNick Kossifidis 		return ret;
2267fa580c1SNick Kossifidis 
22758b5e4c7SNick Kossifidis 	tlv = (struct fft_sample_tlv *)&fft_sample_20;
22858b5e4c7SNick Kossifidis 
22958b5e4c7SNick Kossifidis 	ath_debug_send_fft_sample(spec_priv, tlv);
23058b5e4c7SNick Kossifidis 
23158b5e4c7SNick Kossifidis 	return 0;
23258b5e4c7SNick Kossifidis }
23358b5e4c7SNick Kossifidis 
23458b5e4c7SNick Kossifidis static int
23558b5e4c7SNick Kossifidis ath_cmn_process_ht20_40_fft(struct ath_rx_status *rs,
23658b5e4c7SNick Kossifidis 			struct ath_spec_scan_priv *spec_priv,
23758b5e4c7SNick Kossifidis 			u8 *sample_buf,
23858b5e4c7SNick Kossifidis 			u64 tsf, u16 freq, int chan_type)
23958b5e4c7SNick Kossifidis {
24058b5e4c7SNick Kossifidis 	struct fft_sample_ht20_40 fft_sample_40;
2417fa580c1SNick Kossifidis 	struct ath_common *common = ath9k_hw_common(spec_priv->ah);
24258b5e4c7SNick Kossifidis 	struct ath_hw *ah = spec_priv->ah;
243f65c0825SSujith Manoharan 	struct ath9k_hw_cal_data *caldata = ah->caldata;
244f65c0825SSujith Manoharan 	struct ath_ht20_40_mag_info *mag_info;
24558b5e4c7SNick Kossifidis 	struct fft_sample_tlv *tlv;
24658b5e4c7SNick Kossifidis 	int dc_pos = SPECTRAL_HT20_40_NUM_BINS / 2;
2477fa580c1SNick Kossifidis 	int i = 0;
2487fa580c1SNick Kossifidis 	int ret = 0;
24958b5e4c7SNick Kossifidis 	s16 ext_nf;
2507fa580c1SNick Kossifidis 	u16 lower_mag, upper_mag, tmp_mag, length;
25158b5e4c7SNick Kossifidis 	s8 lower_rssi, upper_rssi;
25258b5e4c7SNick Kossifidis 	u8 lower_max_index, upper_max_index;
2537fa580c1SNick Kossifidis 	u8 lower_bitmap_w, upper_bitmap_w, max_exp;
254f65c0825SSujith Manoharan 
255f65c0825SSujith Manoharan 	if (caldata)
256f65c0825SSujith Manoharan 		ext_nf = ath9k_hw_getchan_noise(ah, ah->curchan,
257f65c0825SSujith Manoharan 				caldata->nfCalHist[3].privNF);
258f65c0825SSujith Manoharan 	else
259f65c0825SSujith Manoharan 		ext_nf = ATH_DEFAULT_NOISE_FLOOR;
260f65c0825SSujith Manoharan 
261f65c0825SSujith Manoharan 	length = sizeof(fft_sample_40) - sizeof(struct fft_sample_tlv);
262f65c0825SSujith Manoharan 	fft_sample_40.tlv.type = ATH_FFT_SAMPLE_HT20_40;
263f65c0825SSujith Manoharan 	fft_sample_40.tlv.length = __cpu_to_be16(length);
264f65c0825SSujith Manoharan 	fft_sample_40.freq = __cpu_to_be16(freq);
265f65c0825SSujith Manoharan 	fft_sample_40.channel_type = chan_type;
266f65c0825SSujith Manoharan 
267f65c0825SSujith Manoharan 	if (chan_type == NL80211_CHAN_HT40PLUS) {
268f65c0825SSujith Manoharan 		lower_rssi = fix_rssi_inv_only(rs->rs_rssi_ctl[0]);
269f65c0825SSujith Manoharan 		upper_rssi = fix_rssi_inv_only(rs->rs_rssi_ext[0]);
270f65c0825SSujith Manoharan 
271f65c0825SSujith Manoharan 		fft_sample_40.lower_noise = ah->noise;
272f65c0825SSujith Manoharan 		fft_sample_40.upper_noise = ext_nf;
273f65c0825SSujith Manoharan 	} else {
274f65c0825SSujith Manoharan 		lower_rssi = fix_rssi_inv_only(rs->rs_rssi_ext[0]);
275f65c0825SSujith Manoharan 		upper_rssi = fix_rssi_inv_only(rs->rs_rssi_ctl[0]);
276f65c0825SSujith Manoharan 
277f65c0825SSujith Manoharan 		fft_sample_40.lower_noise = ext_nf;
278f65c0825SSujith Manoharan 		fft_sample_40.upper_noise = ah->noise;
279f65c0825SSujith Manoharan 	}
28058b5e4c7SNick Kossifidis 
281f65c0825SSujith Manoharan 	fft_sample_40.lower_rssi = lower_rssi;
282f65c0825SSujith Manoharan 	fft_sample_40.upper_rssi = upper_rssi;
283f65c0825SSujith Manoharan 
28458b5e4c7SNick Kossifidis 	mag_info = (struct ath_ht20_40_mag_info *) (sample_buf +
28558b5e4c7SNick Kossifidis 					SPECTRAL_HT20_40_NUM_BINS);
28658b5e4c7SNick Kossifidis 
287f65c0825SSujith Manoharan 	lower_mag = spectral_max_magnitude(mag_info->lower_bins);
288f65c0825SSujith Manoharan 	fft_sample_40.lower_max_magnitude = __cpu_to_be16(lower_mag);
28958b5e4c7SNick Kossifidis 
29058b5e4c7SNick Kossifidis 	upper_mag = spectral_max_magnitude(mag_info->upper_bins);
291f65c0825SSujith Manoharan 	fft_sample_40.upper_max_magnitude = __cpu_to_be16(upper_mag);
29258b5e4c7SNick Kossifidis 
2932f85786bSSimon Wunderlich 	lower_max_index = spectral_max_index_ht40(mag_info->lower_bins);
294f65c0825SSujith Manoharan 	fft_sample_40.lower_max_index = lower_max_index;
29558b5e4c7SNick Kossifidis 
2962f85786bSSimon Wunderlich 	upper_max_index = spectral_max_index_ht40(mag_info->upper_bins);
297f65c0825SSujith Manoharan 	fft_sample_40.upper_max_index = upper_max_index;
29858b5e4c7SNick Kossifidis 
299f65c0825SSujith Manoharan 	lower_bitmap_w = spectral_bitmap_weight(mag_info->lower_bins);
300f65c0825SSujith Manoharan 	fft_sample_40.lower_bitmap_weight = lower_bitmap_w;
30158b5e4c7SNick Kossifidis 
30258b5e4c7SNick Kossifidis 	upper_bitmap_w = spectral_bitmap_weight(mag_info->upper_bins);
303f65c0825SSujith Manoharan 	fft_sample_40.upper_bitmap_weight = upper_bitmap_w;
30458b5e4c7SNick Kossifidis 
3057fa580c1SNick Kossifidis 	max_exp = mag_info->max_exp & 0xf;
3067fa580c1SNick Kossifidis 	fft_sample_40.max_exp = max_exp;
307f65c0825SSujith Manoharan 
308f65c0825SSujith Manoharan 	fft_sample_40.tsf = __cpu_to_be64(tsf);
309f65c0825SSujith Manoharan 
31058b5e4c7SNick Kossifidis 	memcpy(fft_sample_40.data, sample_buf, SPECTRAL_HT20_40_NUM_BINS);
31158b5e4c7SNick Kossifidis 
3127fa580c1SNick Kossifidis 	ath_dbg(common, SPECTRAL_SCAN, "FFT HT20/40 frame: lower mag 0x%X,"
3137fa580c1SNick Kossifidis 					"lower_mag_idx %i, upper mag 0x%X,"
3147fa580c1SNick Kossifidis 					"upper_mag_idx %i\n",
3157fa580c1SNick Kossifidis 					lower_mag >> max_exp,
3167fa580c1SNick Kossifidis 					lower_max_index,
3177fa580c1SNick Kossifidis 					upper_mag >> max_exp,
3187fa580c1SNick Kossifidis 					upper_max_index);
3197fa580c1SNick Kossifidis 
3207fa580c1SNick Kossifidis 	/* Check if we got the expected magnitude values at
3217fa580c1SNick Kossifidis 	 * the expected bins
3227fa580c1SNick Kossifidis 	 */
3234e7a3fa5SSimon Wunderlich 	if (((fft_sample_40.data[upper_max_index + dc_pos] & 0xf8)
3244e7a3fa5SSimon Wunderlich 	    != ((upper_mag >> max_exp) & 0xf8)) ||
3254e7a3fa5SSimon Wunderlich 	   ((fft_sample_40.data[lower_max_index] & 0xf8)
3264e7a3fa5SSimon Wunderlich 	    != ((lower_mag >> max_exp) & 0xf8))) {
3277fa580c1SNick Kossifidis 		ath_dbg(common, SPECTRAL_SCAN, "Magnitude mismatch !\n");
3287fa580c1SNick Kossifidis 		ret = -1;
3297fa580c1SNick Kossifidis 	}
3307fa580c1SNick Kossifidis 
33158b5e4c7SNick Kossifidis 	/* DC value (value in the middle) is the blind spot of the spectral
33258b5e4c7SNick Kossifidis 	 * sample and invalid, interpolate it.
33358b5e4c7SNick Kossifidis 	 */
33458b5e4c7SNick Kossifidis 	fft_sample_40.data[dc_pos] = (fft_sample_40.data[dc_pos + 1] +
33558b5e4c7SNick Kossifidis 					fft_sample_40.data[dc_pos - 1]) / 2;
33658b5e4c7SNick Kossifidis 
3377fa580c1SNick Kossifidis 	/* Check if the maximum magnitudes are indeed maximum,
3387fa580c1SNick Kossifidis 	 * also if the maximum value was at dc_pos, calculate
3397fa580c1SNick Kossifidis 	 * a new one (since value at dc_pos is invalid).
3407fa580c1SNick Kossifidis 	 */
3417fa580c1SNick Kossifidis 	if (lower_max_index == dc_pos) {
3427fa580c1SNick Kossifidis 		tmp_mag = 0;
3437fa580c1SNick Kossifidis 		for (i = 0; i < dc_pos; i++) {
3447fa580c1SNick Kossifidis 			if (fft_sample_40.data[i] > tmp_mag) {
3457fa580c1SNick Kossifidis 				tmp_mag = fft_sample_40.data[i];
3467fa580c1SNick Kossifidis 				fft_sample_40.lower_max_index = i;
3477fa580c1SNick Kossifidis 			}
3487fa580c1SNick Kossifidis 		}
3497fa580c1SNick Kossifidis 
3507fa580c1SNick Kossifidis 		lower_mag = tmp_mag << max_exp;
3517fa580c1SNick Kossifidis 		fft_sample_40.lower_max_magnitude = __cpu_to_be16(lower_mag);
3527fa580c1SNick Kossifidis 
3537fa580c1SNick Kossifidis 		ath_dbg(common, SPECTRAL_SCAN,
3547fa580c1SNick Kossifidis 			"Calculated new lower max 0x%X at %i\n",
3557fa580c1SNick Kossifidis 			tmp_mag, fft_sample_40.lower_max_index);
3567fa580c1SNick Kossifidis 	} else
3577fa580c1SNick Kossifidis 	for (i = 0; i < dc_pos; i++) {
3587fa580c1SNick Kossifidis 		if (fft_sample_40.data[i] == (lower_mag >> max_exp))
3597fa580c1SNick Kossifidis 			ath_dbg(common, SPECTRAL_SCAN,
3607fa580c1SNick Kossifidis 				"Got lower mag: 0x%X at index %i\n",
3617fa580c1SNick Kossifidis 				fft_sample_40.data[i], i);
3627fa580c1SNick Kossifidis 
3637fa580c1SNick Kossifidis 		if (fft_sample_40.data[i] > (lower_mag >> max_exp)) {
3647fa580c1SNick Kossifidis 			ath_dbg(common, SPECTRAL_SCAN,
3657fa580c1SNick Kossifidis 				"Got lower bin %i higher than max: 0x%X\n",
3667fa580c1SNick Kossifidis 				i, fft_sample_40.data[i]);
3677fa580c1SNick Kossifidis 			ret = -1;
3687fa580c1SNick Kossifidis 		}
3697fa580c1SNick Kossifidis 	}
3707fa580c1SNick Kossifidis 
3717fa580c1SNick Kossifidis 	if (upper_max_index == dc_pos) {
3727fa580c1SNick Kossifidis 		tmp_mag = 0;
3737fa580c1SNick Kossifidis 		for (i = dc_pos; i < SPECTRAL_HT20_40_NUM_BINS; i++) {
3747fa580c1SNick Kossifidis 			if (fft_sample_40.data[i] > tmp_mag) {
3757fa580c1SNick Kossifidis 				tmp_mag = fft_sample_40.data[i];
3767fa580c1SNick Kossifidis 				fft_sample_40.upper_max_index = i;
3777fa580c1SNick Kossifidis 			}
3787fa580c1SNick Kossifidis 		}
3797fa580c1SNick Kossifidis 		upper_mag = tmp_mag << max_exp;
3807fa580c1SNick Kossifidis 		fft_sample_40.upper_max_magnitude = __cpu_to_be16(upper_mag);
3817fa580c1SNick Kossifidis 
3827fa580c1SNick Kossifidis 		ath_dbg(common, SPECTRAL_SCAN,
3837fa580c1SNick Kossifidis 			"Calculated new upper max 0x%X at %i\n",
3844fb5837aSSimon Wunderlich 			tmp_mag, fft_sample_40.upper_max_index);
3857fa580c1SNick Kossifidis 	} else
3867fa580c1SNick Kossifidis 	for (i = dc_pos; i < SPECTRAL_HT20_40_NUM_BINS; i++) {
3877fa580c1SNick Kossifidis 		if (fft_sample_40.data[i] == (upper_mag >> max_exp))
3887fa580c1SNick Kossifidis 			ath_dbg(common, SPECTRAL_SCAN,
3897fa580c1SNick Kossifidis 				"Got upper mag: 0x%X at index %i\n",
3907fa580c1SNick Kossifidis 				fft_sample_40.data[i], i);
3917fa580c1SNick Kossifidis 
3927fa580c1SNick Kossifidis 		if (fft_sample_40.data[i] > (upper_mag >> max_exp)) {
3937fa580c1SNick Kossifidis 			ath_dbg(common, SPECTRAL_SCAN,
3947fa580c1SNick Kossifidis 				"Got upper bin %i higher than max: 0x%X\n",
3957fa580c1SNick Kossifidis 				i, fft_sample_40.data[i]);
3967fa580c1SNick Kossifidis 
3977fa580c1SNick Kossifidis 			ret = -1;
3987fa580c1SNick Kossifidis 		}
3997fa580c1SNick Kossifidis 	}
4007fa580c1SNick Kossifidis 
4017fa580c1SNick Kossifidis 	if (ret < 0)
4027fa580c1SNick Kossifidis 		return ret;
4037fa580c1SNick Kossifidis 
404f65c0825SSujith Manoharan 	tlv = (struct fft_sample_tlv *)&fft_sample_40;
405f65c0825SSujith Manoharan 
4061111d426SOleksij Rempel 	ath_debug_send_fft_sample(spec_priv, tlv);
407f65c0825SSujith Manoharan 
40858b5e4c7SNick Kossifidis 	return 0;
40958b5e4c7SNick Kossifidis }
41058b5e4c7SNick Kossifidis 
4110f2c75deSNick Kossifidis static inline void
4120f2c75deSNick Kossifidis ath_cmn_copy_fft_frame(u8 *in, u8 *out, int sample_len, int sample_bytes)
4130f2c75deSNick Kossifidis {
4140f2c75deSNick Kossifidis 	switch (sample_bytes - sample_len) {
4150f2c75deSNick Kossifidis 	case -1:
4160f2c75deSNick Kossifidis 		/* First byte missing */
4170f2c75deSNick Kossifidis 		memcpy(&out[1], in,
4180f2c75deSNick Kossifidis 		       sample_len - 1);
4190f2c75deSNick Kossifidis 		break;
4200f2c75deSNick Kossifidis 	case 0:
4210f2c75deSNick Kossifidis 		/* Length correct, nothing to do. */
4220f2c75deSNick Kossifidis 		memcpy(out, in, sample_len);
4230f2c75deSNick Kossifidis 		break;
4240f2c75deSNick Kossifidis 	case 1:
4250f2c75deSNick Kossifidis 		/* MAC added 2 extra bytes AND first byte
4260f2c75deSNick Kossifidis 		 * is missing.
4270f2c75deSNick Kossifidis 		 */
4280f2c75deSNick Kossifidis 		memcpy(&out[1], in, 30);
4290f2c75deSNick Kossifidis 		out[31] = in[31];
4300f2c75deSNick Kossifidis 		memcpy(&out[32], &in[33],
4310f2c75deSNick Kossifidis 		       sample_len - 32);
4320f2c75deSNick Kossifidis 		break;
4330f2c75deSNick Kossifidis 	case 2:
4340f2c75deSNick Kossifidis 		/* MAC added 2 extra bytes at bin 30 and 32,
4350f2c75deSNick Kossifidis 		 * remove them.
4360f2c75deSNick Kossifidis 		 */
4370f2c75deSNick Kossifidis 		memcpy(out, in, 30);
4380f2c75deSNick Kossifidis 		out[30] = in[31];
4390f2c75deSNick Kossifidis 		memcpy(&out[31], &in[33],
4400f2c75deSNick Kossifidis 		       sample_len - 31);
4410f2c75deSNick Kossifidis 		break;
4420f2c75deSNick Kossifidis 	default:
4430f2c75deSNick Kossifidis 		break;
4440f2c75deSNick Kossifidis 	}
4450f2c75deSNick Kossifidis }
4460f2c75deSNick Kossifidis 
4476b8f85a9SNick Kossifidis static int
4486b8f85a9SNick Kossifidis ath_cmn_is_fft_buf_full(struct ath_spec_scan_priv *spec_priv)
4496b8f85a9SNick Kossifidis {
4506b8f85a9SNick Kossifidis 	int i = 0;
4516b8f85a9SNick Kossifidis 	int ret = 0;
452221b6ec6SSebastian Gottschall 	struct rchan_buf *buf;
4536b8f85a9SNick Kossifidis 	struct rchan *rc = spec_priv->rfs_chan_spec_scan;
4546b8f85a9SNick Kossifidis 
455221b6ec6SSebastian Gottschall 	for_each_possible_cpu(i) {
456221b6ec6SSebastian Gottschall 		if ((buf = *per_cpu_ptr(rc->buf, i))) {
457221b6ec6SSebastian Gottschall 			ret += relay_buf_full(buf);
458221b6ec6SSebastian Gottschall 		}
459221b6ec6SSebastian Gottschall 	}
4606b8f85a9SNick Kossifidis 
461221b6ec6SSebastian Gottschall 	if (ret)
4626b8f85a9SNick Kossifidis 		return 1;
4636b8f85a9SNick Kossifidis 	else
4646b8f85a9SNick Kossifidis 		return 0;
4656b8f85a9SNick Kossifidis }
4666b8f85a9SNick Kossifidis 
46758b5e4c7SNick Kossifidis /* returns 1 if this was a spectral frame, even if not handled. */
46858b5e4c7SNick Kossifidis int ath_cmn_process_fft(struct ath_spec_scan_priv *spec_priv, struct ieee80211_hdr *hdr,
46958b5e4c7SNick Kossifidis 		    struct ath_rx_status *rs, u64 tsf)
47058b5e4c7SNick Kossifidis {
47158b5e4c7SNick Kossifidis 	u8 sample_buf[SPECTRAL_SAMPLE_MAX_LEN] = {0};
47258b5e4c7SNick Kossifidis 	struct ath_hw *ah = spec_priv->ah;
47358b5e4c7SNick Kossifidis 	struct ath_common *common = ath9k_hw_common(spec_priv->ah);
47403224678SSimon Wunderlich 	struct ath_softc *sc = (struct ath_softc *)common->priv;
47558b5e4c7SNick Kossifidis 	u8 num_bins, *vdata = (u8 *)hdr;
47658b5e4c7SNick Kossifidis 	struct ath_radar_info *radar_info;
47758b5e4c7SNick Kossifidis 	int len = rs->rs_datalen;
47872dd2cdaSNick Kossifidis 	int i;
47972dd2cdaSNick Kossifidis 	int got_slen = 0;
48072dd2cdaSNick Kossifidis 	u8  *sample_start;
48172dd2cdaSNick Kossifidis 	int sample_bytes = 0;
48272dd2cdaSNick Kossifidis 	int ret = 0;
48358b5e4c7SNick Kossifidis 	u16 fft_len, sample_len, freq = ah->curchan->chan->center_freq;
48458b5e4c7SNick Kossifidis 	enum nl80211_channel_type chan_type;
48572dd2cdaSNick Kossifidis 	ath_cmn_fft_idx_validator *fft_idx_validator;
48658b5e4c7SNick Kossifidis 	ath_cmn_fft_sample_handler *fft_handler;
48758b5e4c7SNick Kossifidis 
48858b5e4c7SNick Kossifidis 	/* AR9280 and before report via ATH9K_PHYERR_RADAR, AR93xx and newer
48958b5e4c7SNick Kossifidis 	 * via ATH9K_PHYERR_SPECTRAL. Haven't seen ATH9K_PHYERR_FALSE_RADAR_EXT
49058b5e4c7SNick Kossifidis 	 * yet, but this is supposed to be possible as well.
49158b5e4c7SNick Kossifidis 	 */
49258b5e4c7SNick Kossifidis 	if (rs->rs_phyerr != ATH9K_PHYERR_RADAR &&
49358b5e4c7SNick Kossifidis 	    rs->rs_phyerr != ATH9K_PHYERR_FALSE_RADAR_EXT &&
49458b5e4c7SNick Kossifidis 	    rs->rs_phyerr != ATH9K_PHYERR_SPECTRAL)
49558b5e4c7SNick Kossifidis 		return 0;
49658b5e4c7SNick Kossifidis 
49758b5e4c7SNick Kossifidis 	/* check if spectral scan bit is set. This does not have to be checked
49858b5e4c7SNick Kossifidis 	 * if received through a SPECTRAL phy error, but shouldn't hurt.
49958b5e4c7SNick Kossifidis 	 */
50058b5e4c7SNick Kossifidis 	radar_info = ((struct ath_radar_info *)&vdata[len]) - 1;
50158b5e4c7SNick Kossifidis 	if (!(radar_info->pulse_bw_info & SPECTRAL_SCAN_BITMASK))
50258b5e4c7SNick Kossifidis 		return 0;
50358b5e4c7SNick Kossifidis 
50440bea976SMiaoqing Pan 	if (!spec_priv->rfs_chan_spec_scan)
50540bea976SMiaoqing Pan 		return 1;
50640bea976SMiaoqing Pan 
5076b8f85a9SNick Kossifidis 	/* Output buffers are full, no need to process anything
5086b8f85a9SNick Kossifidis 	 * since there is no space to put the result anyway
5096b8f85a9SNick Kossifidis 	 */
5106b8f85a9SNick Kossifidis 	ret = ath_cmn_is_fft_buf_full(spec_priv);
5116b8f85a9SNick Kossifidis 	if (ret == 1) {
5126b8f85a9SNick Kossifidis 		ath_dbg(common, SPECTRAL_SCAN, "FFT report ignored, no space "
5136b8f85a9SNick Kossifidis 						"left on output buffers\n");
5146b8f85a9SNick Kossifidis 		return 1;
5156b8f85a9SNick Kossifidis 	}
5166b8f85a9SNick Kossifidis 
51758b5e4c7SNick Kossifidis 	chan_type = cfg80211_get_chandef_type(&common->hw->conf.chandef);
51858b5e4c7SNick Kossifidis 	if ((chan_type == NL80211_CHAN_HT40MINUS) ||
51958b5e4c7SNick Kossifidis 	    (chan_type == NL80211_CHAN_HT40PLUS)) {
52058b5e4c7SNick Kossifidis 		fft_len = SPECTRAL_HT20_40_TOTAL_DATA_LEN;
52158b5e4c7SNick Kossifidis 		sample_len = SPECTRAL_HT20_40_SAMPLE_LEN;
52258b5e4c7SNick Kossifidis 		num_bins = SPECTRAL_HT20_40_NUM_BINS;
52372dd2cdaSNick Kossifidis 		fft_idx_validator = &ath_cmn_max_idx_verify_ht20_40_fft;
52458b5e4c7SNick Kossifidis 		fft_handler = &ath_cmn_process_ht20_40_fft;
52558b5e4c7SNick Kossifidis 	} else {
52658b5e4c7SNick Kossifidis 		fft_len = SPECTRAL_HT20_TOTAL_DATA_LEN;
52758b5e4c7SNick Kossifidis 		sample_len = SPECTRAL_HT20_SAMPLE_LEN;
52858b5e4c7SNick Kossifidis 		num_bins = SPECTRAL_HT20_NUM_BINS;
52972dd2cdaSNick Kossifidis 		fft_idx_validator = ath_cmn_max_idx_verify_ht20_fft;
53058b5e4c7SNick Kossifidis 		fft_handler = &ath_cmn_process_ht20_fft;
53158b5e4c7SNick Kossifidis 	}
53258b5e4c7SNick Kossifidis 
53372dd2cdaSNick Kossifidis 	ath_dbg(common, SPECTRAL_SCAN, "Got radar dump bw_info: 0x%X,"
53472dd2cdaSNick Kossifidis 					"len: %i fft_len: %i\n",
53572dd2cdaSNick Kossifidis 					radar_info->pulse_bw_info,
53672dd2cdaSNick Kossifidis 					len,
53772dd2cdaSNick Kossifidis 					fft_len);
53872dd2cdaSNick Kossifidis 	sample_start = vdata;
53972dd2cdaSNick Kossifidis 	for (i = 0; i < len - 2; i++) {
54072dd2cdaSNick Kossifidis 		sample_bytes++;
54158b5e4c7SNick Kossifidis 
54272dd2cdaSNick Kossifidis 		/* Only a single sample received, no need to look
54372dd2cdaSNick Kossifidis 		 * for the sample's end, do the correction based
54472dd2cdaSNick Kossifidis 		 * on the packet's length instead. Note that hw
54572dd2cdaSNick Kossifidis 		 * will always put the radar_info structure on
54672dd2cdaSNick Kossifidis 		 * the end.
54772dd2cdaSNick Kossifidis 		 */
54872dd2cdaSNick Kossifidis 		if (len <= fft_len + 2) {
54972dd2cdaSNick Kossifidis 			sample_bytes = len - sizeof(struct ath_radar_info);
55072dd2cdaSNick Kossifidis 			got_slen = 1;
55158b5e4c7SNick Kossifidis 		}
55258b5e4c7SNick Kossifidis 
55372dd2cdaSNick Kossifidis 		/* Search for the end of the FFT frame between
55472dd2cdaSNick Kossifidis 		 * sample_len - 1 and sample_len + 2. exp_max is 3
55572dd2cdaSNick Kossifidis 		 * bits long and it's the only value on the last
55672dd2cdaSNick Kossifidis 		 * byte of the frame so since it'll be smaller than
55772dd2cdaSNick Kossifidis 		 * the next byte (the first bin of the next sample)
55872dd2cdaSNick Kossifidis 		 * 90% of the time, we can use it as a separator.
55972dd2cdaSNick Kossifidis 		 */
56072dd2cdaSNick Kossifidis 		if (vdata[i] <= 0x7 && sample_bytes >= sample_len - 1) {
56158b5e4c7SNick Kossifidis 
56272dd2cdaSNick Kossifidis 			/* Got a frame length within boundaries, there are
56372dd2cdaSNick Kossifidis 			 * four scenarios here:
56472dd2cdaSNick Kossifidis 			 *
56572dd2cdaSNick Kossifidis 			 * a) sample_len -> We got the correct length
56672dd2cdaSNick Kossifidis 			 * b) sample_len + 2 -> 2 bytes added around bin[31]
56772dd2cdaSNick Kossifidis 			 * c) sample_len - 1 -> The first byte is missing
56872dd2cdaSNick Kossifidis 			 * d) sample_len + 1 -> b + c at the same time
56972dd2cdaSNick Kossifidis 			 *
57072dd2cdaSNick Kossifidis 			 * When MAC adds 2 extra bytes, bin[31] and bin[32]
57172dd2cdaSNick Kossifidis 			 * have the same value, so we can use that for further
57272dd2cdaSNick Kossifidis 			 * verification in cases b and d.
57372dd2cdaSNick Kossifidis 			 */
57472dd2cdaSNick Kossifidis 
57572dd2cdaSNick Kossifidis 			/* Did we go too far ? If so we couldn't determine
57672dd2cdaSNick Kossifidis 			 * this sample's boundaries, discard any further
57772dd2cdaSNick Kossifidis 			 * data
57872dd2cdaSNick Kossifidis 			 */
57972dd2cdaSNick Kossifidis 			if ((sample_bytes > sample_len + 2) ||
58072dd2cdaSNick Kossifidis 			   ((sample_bytes > sample_len) &&
58172dd2cdaSNick Kossifidis 			   (sample_start[31] != sample_start[32])))
58272dd2cdaSNick Kossifidis 				break;
58372dd2cdaSNick Kossifidis 
58472dd2cdaSNick Kossifidis 			/* See if we got a valid frame by checking the
58572dd2cdaSNick Kossifidis 			 * consistency of mag_info fields. This is to
58672dd2cdaSNick Kossifidis 			 * prevent from "fixing" a correct frame.
58772dd2cdaSNick Kossifidis 			 * Failure is non-fatal, later frames may
58872dd2cdaSNick Kossifidis 			 * be valid.
58972dd2cdaSNick Kossifidis 			 */
59072dd2cdaSNick Kossifidis 			if (!fft_idx_validator(&vdata[i], i)) {
59172dd2cdaSNick Kossifidis 				ath_dbg(common, SPECTRAL_SCAN,
59272dd2cdaSNick Kossifidis 					"Found valid fft frame at %i\n", i);
59372dd2cdaSNick Kossifidis 				got_slen = 1;
59472dd2cdaSNick Kossifidis 			}
59572dd2cdaSNick Kossifidis 
59672dd2cdaSNick Kossifidis 			/* We expect 1 - 2 more bytes */
59772dd2cdaSNick Kossifidis 			else if ((sample_start[31] == sample_start[32]) &&
59872dd2cdaSNick Kossifidis 				(sample_bytes >= sample_len) &&
59972dd2cdaSNick Kossifidis 				(sample_bytes < sample_len + 2) &&
60072dd2cdaSNick Kossifidis 				(vdata[i + 1] <= 0x7))
60172dd2cdaSNick Kossifidis 				continue;
60272dd2cdaSNick Kossifidis 
60372dd2cdaSNick Kossifidis 			/* Try to distinguish cases a and c */
60472dd2cdaSNick Kossifidis 			else if ((sample_bytes == sample_len - 1) &&
60572dd2cdaSNick Kossifidis 				(vdata[i + 1] <= 0x7))
60672dd2cdaSNick Kossifidis 				continue;
60772dd2cdaSNick Kossifidis 
60872dd2cdaSNick Kossifidis 			got_slen = 1;
60972dd2cdaSNick Kossifidis 		}
61072dd2cdaSNick Kossifidis 
61172dd2cdaSNick Kossifidis 		if (got_slen) {
61272dd2cdaSNick Kossifidis 			ath_dbg(common, SPECTRAL_SCAN, "FFT frame len: %i\n",
61372dd2cdaSNick Kossifidis 				sample_bytes);
6140f2c75deSNick Kossifidis 
6150f2c75deSNick Kossifidis 			/* Only try to fix a frame if it's the only one
6160f2c75deSNick Kossifidis 			 * on the report, else just skip it.
61772dd2cdaSNick Kossifidis 			 */
6180f2c75deSNick Kossifidis 			if (sample_bytes != sample_len && len <= fft_len + 2) {
6190f2c75deSNick Kossifidis 				ath_cmn_copy_fft_frame(sample_start,
6200f2c75deSNick Kossifidis 						       sample_buf, sample_len,
6210f2c75deSNick Kossifidis 						       sample_bytes);
6220f2c75deSNick Kossifidis 
62303224678SSimon Wunderlich 				ret = fft_handler(rs, spec_priv, sample_buf,
6240f2c75deSNick Kossifidis 						  tsf, freq, chan_type);
6253ea2ce3fSNick Kossifidis 
62603224678SSimon Wunderlich 				if (ret == 0)
62772569b7bSArnd Bergmann 					RX_STAT_INC(sc, rx_spectral_sample_good);
62803224678SSimon Wunderlich 				else
62972569b7bSArnd Bergmann 					RX_STAT_INC(sc, rx_spectral_sample_err);
63003224678SSimon Wunderlich 
6313ea2ce3fSNick Kossifidis 				memset(sample_buf, 0, SPECTRAL_SAMPLE_MAX_LEN);
6322aa56ccaSNick Kossifidis 
6332aa56ccaSNick Kossifidis 				/* Mix the received bins to the /dev/random
6342aa56ccaSNick Kossifidis 				 * pool
6352aa56ccaSNick Kossifidis 				 */
6362aa56ccaSNick Kossifidis 				add_device_randomness(sample_buf, num_bins);
63772dd2cdaSNick Kossifidis 			}
63872dd2cdaSNick Kossifidis 
6390f2c75deSNick Kossifidis 			/* Process a normal frame */
6402aa56ccaSNick Kossifidis 			if (sample_bytes == sample_len) {
6419acc98b9SNick Kossifidis 				ret = fft_handler(rs, spec_priv, sample_start,
6420f2c75deSNick Kossifidis 						  tsf, freq, chan_type);
6430f2c75deSNick Kossifidis 
64403224678SSimon Wunderlich 				if (ret == 0)
64572569b7bSArnd Bergmann 					RX_STAT_INC(sc, rx_spectral_sample_good);
64603224678SSimon Wunderlich 				else
64772569b7bSArnd Bergmann 					RX_STAT_INC(sc, rx_spectral_sample_err);
64803224678SSimon Wunderlich 
6492aa56ccaSNick Kossifidis 				/* Mix the received bins to the /dev/random
6502aa56ccaSNick Kossifidis 				 * pool
6512aa56ccaSNick Kossifidis 				 */
6522aa56ccaSNick Kossifidis 				add_device_randomness(sample_start, num_bins);
6532aa56ccaSNick Kossifidis 			}
6542aa56ccaSNick Kossifidis 
6550f2c75deSNick Kossifidis 			/* Short report processed, break out of the
6560f2c75deSNick Kossifidis 			 * loop.
6570f2c75deSNick Kossifidis 			 */
6580f2c75deSNick Kossifidis 			if (len <= fft_len + 2)
659b796a6c0SSimon Wunderlich 				return 1;
6600f2c75deSNick Kossifidis 
66172dd2cdaSNick Kossifidis 			sample_start = &vdata[i + 1];
6620f2c75deSNick Kossifidis 
66372dd2cdaSNick Kossifidis 			/* -1 to grab sample_len -1, -2 since
66472dd2cdaSNick Kossifidis 			 * they 'll get increased by one. In case
66572dd2cdaSNick Kossifidis 			 * of failure try to recover by going byte
6660f2c75deSNick Kossifidis 			 * by byte instead.
6670f2c75deSNick Kossifidis 			 */
66872dd2cdaSNick Kossifidis 			if (ret == 0) {
66972dd2cdaSNick Kossifidis 				i += num_bins - 2;
67072dd2cdaSNick Kossifidis 				sample_bytes = num_bins - 2;
67172dd2cdaSNick Kossifidis 			}
67272dd2cdaSNick Kossifidis 			got_slen = 0;
67372dd2cdaSNick Kossifidis 		}
67472dd2cdaSNick Kossifidis 	}
67572dd2cdaSNick Kossifidis 
67672dd2cdaSNick Kossifidis 	i -= num_bins - 2;
67772dd2cdaSNick Kossifidis 	if (len - i != sizeof(struct ath_radar_info))
67872dd2cdaSNick Kossifidis 		ath_dbg(common, SPECTRAL_SCAN, "FFT report truncated"
67972dd2cdaSNick Kossifidis 						"(bytes left: %i)\n",
68072dd2cdaSNick Kossifidis 						len - i);
681f65c0825SSujith Manoharan 	return 1;
682f65c0825SSujith Manoharan }
68367dc74f1SOleksij Rempel EXPORT_SYMBOL(ath_cmn_process_fft);
684f65c0825SSujith Manoharan 
685f65c0825SSujith Manoharan /*********************/
686f65c0825SSujith Manoharan /* spectral_scan_ctl */
687f65c0825SSujith Manoharan /*********************/
688f65c0825SSujith Manoharan 
689f65c0825SSujith Manoharan static ssize_t read_file_spec_scan_ctl(struct file *file, char __user *user_buf,
690f65c0825SSujith Manoharan 				       size_t count, loff_t *ppos)
691f65c0825SSujith Manoharan {
6921111d426SOleksij Rempel 	struct ath_spec_scan_priv *spec_priv = file->private_data;
693f65c0825SSujith Manoharan 	char *mode = "";
694f65c0825SSujith Manoharan 	unsigned int len;
695f65c0825SSujith Manoharan 
6961111d426SOleksij Rempel 	switch (spec_priv->spectral_mode) {
697f65c0825SSujith Manoharan 	case SPECTRAL_DISABLED:
698f65c0825SSujith Manoharan 		mode = "disable";
699f65c0825SSujith Manoharan 		break;
700f65c0825SSujith Manoharan 	case SPECTRAL_BACKGROUND:
701f65c0825SSujith Manoharan 		mode = "background";
702f65c0825SSujith Manoharan 		break;
703f65c0825SSujith Manoharan 	case SPECTRAL_CHANSCAN:
704f65c0825SSujith Manoharan 		mode = "chanscan";
705f65c0825SSujith Manoharan 		break;
706f65c0825SSujith Manoharan 	case SPECTRAL_MANUAL:
707f65c0825SSujith Manoharan 		mode = "manual";
708f65c0825SSujith Manoharan 		break;
709f65c0825SSujith Manoharan 	}
710f65c0825SSujith Manoharan 	len = strlen(mode);
711f65c0825SSujith Manoharan 	return simple_read_from_buffer(user_buf, count, ppos, mode, len);
712f65c0825SSujith Manoharan }
713f65c0825SSujith Manoharan 
71467dc74f1SOleksij Rempel void ath9k_cmn_spectral_scan_trigger(struct ath_common *common,
715f00a422cSOleksij Rempel 				 struct ath_spec_scan_priv *spec_priv)
716f00a422cSOleksij Rempel {
717f00a422cSOleksij Rempel 	struct ath_hw *ah = spec_priv->ah;
718f00a422cSOleksij Rempel 	u32 rxfilter;
719f00a422cSOleksij Rempel 
72097f2645fSMasahiro Yamada 	if (IS_ENABLED(CONFIG_ATH9K_TX99))
721f00a422cSOleksij Rempel 		return;
722f00a422cSOleksij Rempel 
723f00a422cSOleksij Rempel 	if (!ath9k_hw_ops(ah)->spectral_scan_trigger) {
724f00a422cSOleksij Rempel 		ath_err(common, "spectrum analyzer not implemented on this hardware\n");
725f00a422cSOleksij Rempel 		return;
726f00a422cSOleksij Rempel 	}
727f00a422cSOleksij Rempel 
72845c3d550SZefir Kurtisi 	if (!spec_priv->spec_config.enabled)
72945c3d550SZefir Kurtisi 		return;
73045c3d550SZefir Kurtisi 
731f00a422cSOleksij Rempel 	ath_ps_ops(common)->wakeup(common);
732f00a422cSOleksij Rempel 	rxfilter = ath9k_hw_getrxfilter(ah);
733f00a422cSOleksij Rempel 	ath9k_hw_setrxfilter(ah, rxfilter |
734f00a422cSOleksij Rempel 				 ATH9K_RX_FILTER_PHYRADAR |
735f00a422cSOleksij Rempel 				 ATH9K_RX_FILTER_PHYERR);
736f00a422cSOleksij Rempel 
737f00a422cSOleksij Rempel 	/* TODO: usually this should not be neccesary, but for some reason
738f00a422cSOleksij Rempel 	 * (or in some mode?) the trigger must be called after the
739f00a422cSOleksij Rempel 	 * configuration, otherwise the register will have its values reset
740f00a422cSOleksij Rempel 	 * (on my ar9220 to value 0x01002310)
741f00a422cSOleksij Rempel 	 */
74267dc74f1SOleksij Rempel 	ath9k_cmn_spectral_scan_config(common, spec_priv, spec_priv->spectral_mode);
743f00a422cSOleksij Rempel 	ath9k_hw_ops(ah)->spectral_scan_trigger(ah);
744f00a422cSOleksij Rempel 	ath_ps_ops(common)->restore(common);
745f00a422cSOleksij Rempel }
74667dc74f1SOleksij Rempel EXPORT_SYMBOL(ath9k_cmn_spectral_scan_trigger);
747f00a422cSOleksij Rempel 
74867dc74f1SOleksij Rempel int ath9k_cmn_spectral_scan_config(struct ath_common *common,
749f00a422cSOleksij Rempel 			       struct ath_spec_scan_priv *spec_priv,
750f00a422cSOleksij Rempel 			       enum spectral_mode spectral_mode)
751f00a422cSOleksij Rempel {
752f00a422cSOleksij Rempel 	struct ath_hw *ah = spec_priv->ah;
753f00a422cSOleksij Rempel 
754f00a422cSOleksij Rempel 	if (!ath9k_hw_ops(ah)->spectral_scan_trigger) {
755f00a422cSOleksij Rempel 		ath_err(common, "spectrum analyzer not implemented on this hardware\n");
756f00a422cSOleksij Rempel 		return -1;
757f00a422cSOleksij Rempel 	}
758f00a422cSOleksij Rempel 
759f00a422cSOleksij Rempel 	switch (spectral_mode) {
760f00a422cSOleksij Rempel 	case SPECTRAL_DISABLED:
761f00a422cSOleksij Rempel 		spec_priv->spec_config.enabled = 0;
762f00a422cSOleksij Rempel 		break;
763f00a422cSOleksij Rempel 	case SPECTRAL_BACKGROUND:
764f00a422cSOleksij Rempel 		/* send endless samples.
765f00a422cSOleksij Rempel 		 * TODO: is this really useful for "background"?
766f00a422cSOleksij Rempel 		 */
767f00a422cSOleksij Rempel 		spec_priv->spec_config.endless = 1;
768f00a422cSOleksij Rempel 		spec_priv->spec_config.enabled = 1;
769f00a422cSOleksij Rempel 		break;
770f00a422cSOleksij Rempel 	case SPECTRAL_CHANSCAN:
771f00a422cSOleksij Rempel 	case SPECTRAL_MANUAL:
772f00a422cSOleksij Rempel 		spec_priv->spec_config.endless = 0;
773f00a422cSOleksij Rempel 		spec_priv->spec_config.enabled = 1;
774f00a422cSOleksij Rempel 		break;
775f00a422cSOleksij Rempel 	default:
776f00a422cSOleksij Rempel 		return -1;
777f00a422cSOleksij Rempel 	}
778f00a422cSOleksij Rempel 
779f00a422cSOleksij Rempel 	ath_ps_ops(common)->wakeup(common);
780f00a422cSOleksij Rempel 	ath9k_hw_ops(ah)->spectral_scan_config(ah, &spec_priv->spec_config);
781f00a422cSOleksij Rempel 	ath_ps_ops(common)->restore(common);
782f00a422cSOleksij Rempel 
783f00a422cSOleksij Rempel 	spec_priv->spectral_mode = spectral_mode;
784f00a422cSOleksij Rempel 
785f00a422cSOleksij Rempel 	return 0;
786f00a422cSOleksij Rempel }
78767dc74f1SOleksij Rempel EXPORT_SYMBOL(ath9k_cmn_spectral_scan_config);
788f00a422cSOleksij Rempel 
789f65c0825SSujith Manoharan static ssize_t write_file_spec_scan_ctl(struct file *file,
790f65c0825SSujith Manoharan 					const char __user *user_buf,
791f65c0825SSujith Manoharan 					size_t count, loff_t *ppos)
792f65c0825SSujith Manoharan {
7931111d426SOleksij Rempel 	struct ath_spec_scan_priv *spec_priv = file->private_data;
7941111d426SOleksij Rempel 	struct ath_common *common = ath9k_hw_common(spec_priv->ah);
795f65c0825SSujith Manoharan 	char buf[32];
796f65c0825SSujith Manoharan 	ssize_t len;
797f65c0825SSujith Manoharan 
79897f2645fSMasahiro Yamada 	if (IS_ENABLED(CONFIG_ATH9K_TX99))
799f65c0825SSujith Manoharan 		return -EOPNOTSUPP;
800f65c0825SSujith Manoharan 
801f65c0825SSujith Manoharan 	len = min(count, sizeof(buf) - 1);
802f65c0825SSujith Manoharan 	if (copy_from_user(buf, user_buf, len))
803f65c0825SSujith Manoharan 		return -EFAULT;
804f65c0825SSujith Manoharan 
805f65c0825SSujith Manoharan 	buf[len] = '\0';
806f65c0825SSujith Manoharan 
807f65c0825SSujith Manoharan 	if (strncmp("trigger", buf, 7) == 0) {
80867dc74f1SOleksij Rempel 		ath9k_cmn_spectral_scan_trigger(common, spec_priv);
809ded3fb4cSMaks Naumov 	} else if (strncmp("background", buf, 10) == 0) {
81067dc74f1SOleksij Rempel 		ath9k_cmn_spectral_scan_config(common, spec_priv, SPECTRAL_BACKGROUND);
811f65c0825SSujith Manoharan 		ath_dbg(common, CONFIG, "spectral scan: background mode enabled\n");
812f65c0825SSujith Manoharan 	} else if (strncmp("chanscan", buf, 8) == 0) {
81367dc74f1SOleksij Rempel 		ath9k_cmn_spectral_scan_config(common, spec_priv, SPECTRAL_CHANSCAN);
814f65c0825SSujith Manoharan 		ath_dbg(common, CONFIG, "spectral scan: channel scan mode enabled\n");
815f65c0825SSujith Manoharan 	} else if (strncmp("manual", buf, 6) == 0) {
81667dc74f1SOleksij Rempel 		ath9k_cmn_spectral_scan_config(common, spec_priv, SPECTRAL_MANUAL);
817f65c0825SSujith Manoharan 		ath_dbg(common, CONFIG, "spectral scan: manual mode enabled\n");
818f65c0825SSujith Manoharan 	} else if (strncmp("disable", buf, 7) == 0) {
81967dc74f1SOleksij Rempel 		ath9k_cmn_spectral_scan_config(common, spec_priv, SPECTRAL_DISABLED);
820f65c0825SSujith Manoharan 		ath_dbg(common, CONFIG, "spectral scan: disabled\n");
821f65c0825SSujith Manoharan 	} else {
822f65c0825SSujith Manoharan 		return -EINVAL;
823f65c0825SSujith Manoharan 	}
824f65c0825SSujith Manoharan 
825f65c0825SSujith Manoharan 	return count;
826f65c0825SSujith Manoharan }
827f65c0825SSujith Manoharan 
828f65c0825SSujith Manoharan static const struct file_operations fops_spec_scan_ctl = {
829f65c0825SSujith Manoharan 	.read = read_file_spec_scan_ctl,
830f65c0825SSujith Manoharan 	.write = write_file_spec_scan_ctl,
831f65c0825SSujith Manoharan 	.open = simple_open,
832f65c0825SSujith Manoharan 	.owner = THIS_MODULE,
833f65c0825SSujith Manoharan 	.llseek = default_llseek,
834f65c0825SSujith Manoharan };
835f65c0825SSujith Manoharan 
836f65c0825SSujith Manoharan /*************************/
837f65c0825SSujith Manoharan /* spectral_short_repeat */
838f65c0825SSujith Manoharan /*************************/
839f65c0825SSujith Manoharan 
840f65c0825SSujith Manoharan static ssize_t read_file_spectral_short_repeat(struct file *file,
841f65c0825SSujith Manoharan 					       char __user *user_buf,
842f65c0825SSujith Manoharan 					       size_t count, loff_t *ppos)
843f65c0825SSujith Manoharan {
8441111d426SOleksij Rempel 	struct ath_spec_scan_priv *spec_priv = file->private_data;
845f65c0825SSujith Manoharan 	char buf[32];
846f65c0825SSujith Manoharan 	unsigned int len;
847f65c0825SSujith Manoharan 
8481111d426SOleksij Rempel 	len = sprintf(buf, "%d\n", spec_priv->spec_config.short_repeat);
849f65c0825SSujith Manoharan 	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
850f65c0825SSujith Manoharan }
851f65c0825SSujith Manoharan 
852f65c0825SSujith Manoharan static ssize_t write_file_spectral_short_repeat(struct file *file,
853f65c0825SSujith Manoharan 						const char __user *user_buf,
854f65c0825SSujith Manoharan 						size_t count, loff_t *ppos)
855f65c0825SSujith Manoharan {
8561111d426SOleksij Rempel 	struct ath_spec_scan_priv *spec_priv = file->private_data;
857f65c0825SSujith Manoharan 	unsigned long val;
858f65c0825SSujith Manoharan 	char buf[32];
859f65c0825SSujith Manoharan 	ssize_t len;
860f65c0825SSujith Manoharan 
861f65c0825SSujith Manoharan 	len = min(count, sizeof(buf) - 1);
862f65c0825SSujith Manoharan 	if (copy_from_user(buf, user_buf, len))
863f65c0825SSujith Manoharan 		return -EFAULT;
864f65c0825SSujith Manoharan 
865f65c0825SSujith Manoharan 	buf[len] = '\0';
866f65c0825SSujith Manoharan 	if (kstrtoul(buf, 0, &val))
867f65c0825SSujith Manoharan 		return -EINVAL;
868f65c0825SSujith Manoharan 
8693f557202SAndrey Utkin 	if (val > 1)
870f65c0825SSujith Manoharan 		return -EINVAL;
871f65c0825SSujith Manoharan 
8721111d426SOleksij Rempel 	spec_priv->spec_config.short_repeat = val;
873f65c0825SSujith Manoharan 	return count;
874f65c0825SSujith Manoharan }
875f65c0825SSujith Manoharan 
876f65c0825SSujith Manoharan static const struct file_operations fops_spectral_short_repeat = {
877f65c0825SSujith Manoharan 	.read = read_file_spectral_short_repeat,
878f65c0825SSujith Manoharan 	.write = write_file_spectral_short_repeat,
879f65c0825SSujith Manoharan 	.open = simple_open,
880f65c0825SSujith Manoharan 	.owner = THIS_MODULE,
881f65c0825SSujith Manoharan 	.llseek = default_llseek,
882f65c0825SSujith Manoharan };
883f65c0825SSujith Manoharan 
884f65c0825SSujith Manoharan /******************/
885f65c0825SSujith Manoharan /* spectral_count */
886f65c0825SSujith Manoharan /******************/
887f65c0825SSujith Manoharan 
888f65c0825SSujith Manoharan static ssize_t read_file_spectral_count(struct file *file,
889f65c0825SSujith Manoharan 					char __user *user_buf,
890f65c0825SSujith Manoharan 					size_t count, loff_t *ppos)
891f65c0825SSujith Manoharan {
8921111d426SOleksij Rempel 	struct ath_spec_scan_priv *spec_priv = file->private_data;
893f65c0825SSujith Manoharan 	char buf[32];
894f65c0825SSujith Manoharan 	unsigned int len;
895f65c0825SSujith Manoharan 
8961111d426SOleksij Rempel 	len = sprintf(buf, "%d\n", spec_priv->spec_config.count);
897f65c0825SSujith Manoharan 	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
898f65c0825SSujith Manoharan }
899f65c0825SSujith Manoharan 
900f65c0825SSujith Manoharan static ssize_t write_file_spectral_count(struct file *file,
901f65c0825SSujith Manoharan 					 const char __user *user_buf,
902f65c0825SSujith Manoharan 					 size_t count, loff_t *ppos)
903f65c0825SSujith Manoharan {
9041111d426SOleksij Rempel 	struct ath_spec_scan_priv *spec_priv = file->private_data;
905f65c0825SSujith Manoharan 	unsigned long val;
906f65c0825SSujith Manoharan 	char buf[32];
907f65c0825SSujith Manoharan 	ssize_t len;
908f65c0825SSujith Manoharan 
909f65c0825SSujith Manoharan 	len = min(count, sizeof(buf) - 1);
910f65c0825SSujith Manoharan 	if (copy_from_user(buf, user_buf, len))
911f65c0825SSujith Manoharan 		return -EFAULT;
912f65c0825SSujith Manoharan 
913f65c0825SSujith Manoharan 	buf[len] = '\0';
914f65c0825SSujith Manoharan 	if (kstrtoul(buf, 0, &val))
915f65c0825SSujith Manoharan 		return -EINVAL;
916f65c0825SSujith Manoharan 
9173f557202SAndrey Utkin 	if (val > 255)
918f65c0825SSujith Manoharan 		return -EINVAL;
919f65c0825SSujith Manoharan 
9201111d426SOleksij Rempel 	spec_priv->spec_config.count = val;
921f65c0825SSujith Manoharan 	return count;
922f65c0825SSujith Manoharan }
923f65c0825SSujith Manoharan 
924f65c0825SSujith Manoharan static const struct file_operations fops_spectral_count = {
925f65c0825SSujith Manoharan 	.read = read_file_spectral_count,
926f65c0825SSujith Manoharan 	.write = write_file_spectral_count,
927f65c0825SSujith Manoharan 	.open = simple_open,
928f65c0825SSujith Manoharan 	.owner = THIS_MODULE,
929f65c0825SSujith Manoharan 	.llseek = default_llseek,
930f65c0825SSujith Manoharan };
931f65c0825SSujith Manoharan 
932f65c0825SSujith Manoharan /*******************/
933f65c0825SSujith Manoharan /* spectral_period */
934f65c0825SSujith Manoharan /*******************/
935f65c0825SSujith Manoharan 
936f65c0825SSujith Manoharan static ssize_t read_file_spectral_period(struct file *file,
937f65c0825SSujith Manoharan 					 char __user *user_buf,
938f65c0825SSujith Manoharan 					 size_t count, loff_t *ppos)
939f65c0825SSujith Manoharan {
9401111d426SOleksij Rempel 	struct ath_spec_scan_priv *spec_priv = file->private_data;
941f65c0825SSujith Manoharan 	char buf[32];
942f65c0825SSujith Manoharan 	unsigned int len;
943f65c0825SSujith Manoharan 
9441111d426SOleksij Rempel 	len = sprintf(buf, "%d\n", spec_priv->spec_config.period);
945f65c0825SSujith Manoharan 	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
946f65c0825SSujith Manoharan }
947f65c0825SSujith Manoharan 
948f65c0825SSujith Manoharan static ssize_t write_file_spectral_period(struct file *file,
949f65c0825SSujith Manoharan 					  const char __user *user_buf,
950f65c0825SSujith Manoharan 					  size_t count, loff_t *ppos)
951f65c0825SSujith Manoharan {
9521111d426SOleksij Rempel 	struct ath_spec_scan_priv *spec_priv = file->private_data;
953f65c0825SSujith Manoharan 	unsigned long val;
954f65c0825SSujith Manoharan 	char buf[32];
955f65c0825SSujith Manoharan 	ssize_t len;
956f65c0825SSujith Manoharan 
957f65c0825SSujith Manoharan 	len = min(count, sizeof(buf) - 1);
958f65c0825SSujith Manoharan 	if (copy_from_user(buf, user_buf, len))
959f65c0825SSujith Manoharan 		return -EFAULT;
960f65c0825SSujith Manoharan 
961f65c0825SSujith Manoharan 	buf[len] = '\0';
962f65c0825SSujith Manoharan 	if (kstrtoul(buf, 0, &val))
963f65c0825SSujith Manoharan 		return -EINVAL;
964f65c0825SSujith Manoharan 
9653f557202SAndrey Utkin 	if (val > 255)
966f65c0825SSujith Manoharan 		return -EINVAL;
967f65c0825SSujith Manoharan 
9681111d426SOleksij Rempel 	spec_priv->spec_config.period = val;
969f65c0825SSujith Manoharan 	return count;
970f65c0825SSujith Manoharan }
971f65c0825SSujith Manoharan 
972f65c0825SSujith Manoharan static const struct file_operations fops_spectral_period = {
973f65c0825SSujith Manoharan 	.read = read_file_spectral_period,
974f65c0825SSujith Manoharan 	.write = write_file_spectral_period,
975f65c0825SSujith Manoharan 	.open = simple_open,
976f65c0825SSujith Manoharan 	.owner = THIS_MODULE,
977f65c0825SSujith Manoharan 	.llseek = default_llseek,
978f65c0825SSujith Manoharan };
979f65c0825SSujith Manoharan 
980f65c0825SSujith Manoharan /***********************/
981f65c0825SSujith Manoharan /* spectral_fft_period */
982f65c0825SSujith Manoharan /***********************/
983f65c0825SSujith Manoharan 
984f65c0825SSujith Manoharan static ssize_t read_file_spectral_fft_period(struct file *file,
985f65c0825SSujith Manoharan 					     char __user *user_buf,
986f65c0825SSujith Manoharan 					     size_t count, loff_t *ppos)
987f65c0825SSujith Manoharan {
9881111d426SOleksij Rempel 	struct ath_spec_scan_priv *spec_priv = file->private_data;
989f65c0825SSujith Manoharan 	char buf[32];
990f65c0825SSujith Manoharan 	unsigned int len;
991f65c0825SSujith Manoharan 
9921111d426SOleksij Rempel 	len = sprintf(buf, "%d\n", spec_priv->spec_config.fft_period);
993f65c0825SSujith Manoharan 	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
994f65c0825SSujith Manoharan }
995f65c0825SSujith Manoharan 
996f65c0825SSujith Manoharan static ssize_t write_file_spectral_fft_period(struct file *file,
997f65c0825SSujith Manoharan 					      const char __user *user_buf,
998f65c0825SSujith Manoharan 					      size_t count, loff_t *ppos)
999f65c0825SSujith Manoharan {
10001111d426SOleksij Rempel 	struct ath_spec_scan_priv *spec_priv = file->private_data;
1001f65c0825SSujith Manoharan 	unsigned long val;
1002f65c0825SSujith Manoharan 	char buf[32];
1003f65c0825SSujith Manoharan 	ssize_t len;
1004f65c0825SSujith Manoharan 
1005f65c0825SSujith Manoharan 	len = min(count, sizeof(buf) - 1);
1006f65c0825SSujith Manoharan 	if (copy_from_user(buf, user_buf, len))
1007f65c0825SSujith Manoharan 		return -EFAULT;
1008f65c0825SSujith Manoharan 
1009f65c0825SSujith Manoharan 	buf[len] = '\0';
1010f65c0825SSujith Manoharan 	if (kstrtoul(buf, 0, &val))
1011f65c0825SSujith Manoharan 		return -EINVAL;
1012f65c0825SSujith Manoharan 
10133f557202SAndrey Utkin 	if (val > 15)
1014f65c0825SSujith Manoharan 		return -EINVAL;
1015f65c0825SSujith Manoharan 
10161111d426SOleksij Rempel 	spec_priv->spec_config.fft_period = val;
1017f65c0825SSujith Manoharan 	return count;
1018f65c0825SSujith Manoharan }
1019f65c0825SSujith Manoharan 
1020f65c0825SSujith Manoharan static const struct file_operations fops_spectral_fft_period = {
1021f65c0825SSujith Manoharan 	.read = read_file_spectral_fft_period,
1022f65c0825SSujith Manoharan 	.write = write_file_spectral_fft_period,
1023f65c0825SSujith Manoharan 	.open = simple_open,
1024f65c0825SSujith Manoharan 	.owner = THIS_MODULE,
1025f65c0825SSujith Manoharan 	.llseek = default_llseek,
1026f65c0825SSujith Manoharan };
1027f65c0825SSujith Manoharan 
1028f65c0825SSujith Manoharan /*******************/
1029f65c0825SSujith Manoharan /* Relay interface */
1030f65c0825SSujith Manoharan /*******************/
1031f65c0825SSujith Manoharan 
1032f65c0825SSujith Manoharan static struct dentry *create_buf_file_handler(const char *filename,
1033f65c0825SSujith Manoharan 					      struct dentry *parent,
1034f65c0825SSujith Manoharan 					      umode_t mode,
1035f65c0825SSujith Manoharan 					      struct rchan_buf *buf,
1036f65c0825SSujith Manoharan 					      int *is_global)
1037f65c0825SSujith Manoharan {
1038f65c0825SSujith Manoharan 	struct dentry *buf_file;
1039f65c0825SSujith Manoharan 
1040f65c0825SSujith Manoharan 	buf_file = debugfs_create_file(filename, mode, parent, buf,
1041f65c0825SSujith Manoharan 				       &relay_file_operations);
1042*cd98625bSGreg Kroah-Hartman 	if (IS_ERR(buf_file))
1043*cd98625bSGreg Kroah-Hartman 		return NULL;
1044*cd98625bSGreg Kroah-Hartman 
1045f65c0825SSujith Manoharan 	*is_global = 1;
1046f65c0825SSujith Manoharan 	return buf_file;
1047f65c0825SSujith Manoharan }
1048f65c0825SSujith Manoharan 
1049f65c0825SSujith Manoharan static int remove_buf_file_handler(struct dentry *dentry)
1050f65c0825SSujith Manoharan {
1051f65c0825SSujith Manoharan 	debugfs_remove(dentry);
1052f65c0825SSujith Manoharan 
1053f65c0825SSujith Manoharan 	return 0;
1054f65c0825SSujith Manoharan }
1055f65c0825SSujith Manoharan 
1056f84d1b49SWei Yongjun static struct rchan_callbacks rfs_spec_scan_cb = {
1057f65c0825SSujith Manoharan 	.create_buf_file = create_buf_file_handler,
1058f65c0825SSujith Manoharan 	.remove_buf_file = remove_buf_file_handler,
1059f65c0825SSujith Manoharan };
1060f65c0825SSujith Manoharan 
1061f65c0825SSujith Manoharan /*********************/
1062f65c0825SSujith Manoharan /* Debug Init/Deinit */
1063f65c0825SSujith Manoharan /*********************/
1064f65c0825SSujith Manoharan 
106567dc74f1SOleksij Rempel void ath9k_cmn_spectral_deinit_debug(struct ath_spec_scan_priv *spec_priv)
1066f65c0825SSujith Manoharan {
10671077ec47SChristian Lamparter 	if (spec_priv->rfs_chan_spec_scan) {
10681111d426SOleksij Rempel 		relay_close(spec_priv->rfs_chan_spec_scan);
10691111d426SOleksij Rempel 		spec_priv->rfs_chan_spec_scan = NULL;
1070f65c0825SSujith Manoharan 	}
1071f65c0825SSujith Manoharan }
107267dc74f1SOleksij Rempel EXPORT_SYMBOL(ath9k_cmn_spectral_deinit_debug);
1073f65c0825SSujith Manoharan 
107467dc74f1SOleksij Rempel void ath9k_cmn_spectral_init_debug(struct ath_spec_scan_priv *spec_priv,
107567dc74f1SOleksij Rempel 				   struct dentry *debugfs_phy)
1076f65c0825SSujith Manoharan {
10771111d426SOleksij Rempel 	spec_priv->rfs_chan_spec_scan = relay_open("spectral_scan",
1078c10b75afSOleksij Rempel 					    debugfs_phy,
1079f65c0825SSujith Manoharan 					    1024, 256, &rfs_spec_scan_cb,
1080f65c0825SSujith Manoharan 					    NULL);
108140bea976SMiaoqing Pan 	if (!spec_priv->rfs_chan_spec_scan)
108240bea976SMiaoqing Pan 		return;
108340bea976SMiaoqing Pan 
1084f65c0825SSujith Manoharan 	debugfs_create_file("spectral_scan_ctl",
10852ef00c53SJoe Perches 			    0600,
10861111d426SOleksij Rempel 			    debugfs_phy, spec_priv,
1087f65c0825SSujith Manoharan 			    &fops_spec_scan_ctl);
1088f65c0825SSujith Manoharan 	debugfs_create_file("spectral_short_repeat",
10892ef00c53SJoe Perches 			    0600,
10901111d426SOleksij Rempel 			    debugfs_phy, spec_priv,
1091f65c0825SSujith Manoharan 			    &fops_spectral_short_repeat);
1092f65c0825SSujith Manoharan 	debugfs_create_file("spectral_count",
10932ef00c53SJoe Perches 			    0600,
10941111d426SOleksij Rempel 			    debugfs_phy, spec_priv,
1095f65c0825SSujith Manoharan 			    &fops_spectral_count);
1096f65c0825SSujith Manoharan 	debugfs_create_file("spectral_period",
10972ef00c53SJoe Perches 			    0600,
10981111d426SOleksij Rempel 			    debugfs_phy, spec_priv,
1099f65c0825SSujith Manoharan 			    &fops_spectral_period);
1100f65c0825SSujith Manoharan 	debugfs_create_file("spectral_fft_period",
11012ef00c53SJoe Perches 			    0600,
11021111d426SOleksij Rempel 			    debugfs_phy, spec_priv,
1103f65c0825SSujith Manoharan 			    &fops_spectral_fft_period);
1104f65c0825SSujith Manoharan }
110567dc74f1SOleksij Rempel EXPORT_SYMBOL(ath9k_cmn_spectral_init_debug);
1106