diff mbox

[PATCHv2,2/2] ath10k: add spectral scan feature

Message ID 1405945943-8303-3-git-send-email-sw@simonwunderlich.de (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Simon Wunderlich July 21, 2014, 12:32 p.m. UTC
Adds the spectral scan feature for ath10k. The spectral scan is triggered by
configuring a mode through a debugfs control file. Samples can be gathered via
another relay debugfs file.

Essentially, to try it out:

ip link set dev wlan0 up
echo background > /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan_ctl
iw dev wlan0 scan
echo disable > /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan_ctl
cat /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan0 > samples

This feature is still experimental. Based on the original RFC patch of
Sven Eckelmann.

Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>
Signed-off-by: Mathias Kretschmer <mathias.kretschmer@fokus.fraunhofer.de>
--
PATCHv2:
 * fixed usage example in commit message (Thanks Chun-Yeow Yeoh)
 * adding lockdep asserts and comment (Thanks Michal)
 * debug message cleanups (Michal)
 * disable spectral on firmware crash (Michal)

PATCH:
 * change the tlv format to a variable-length format, depending on the
   number of bins
 * perform dc interpolation and max_exp calculation in the driver (as
   for ath9k)
 * make fft_size configurable
 * background scan will only start after beeing trigger, like in ath9k
 * document at least some spectral configuration members (thanks Kathy)
 * fix sparse problems
 * sample bandwidth appears to be 110% of what is reported by the
   hardware according to the plotter (e.g. 20->22, 40->44 MHz etc),
   so fix that in the driver.
 * disable spectral for a vif when it's beeing deleted and still active
   (Michals comment)
 * modify ath_ prefix of function/struct/enum to ath10k_ (Michal)
 * changed variable ret to res (Michal)
 * added ath10k_warn messages (Michal)
 * renamed wmi functions (Michal)
 * use ar->conf_mutex for locking (Michal)
 * renamed mhz to chan_width_mhz (Michal)
 * only enable spectral scan files when ATH10K_DEBUGFS is enabled
---
 drivers/net/wireless/ath/ath10k/Kconfig    |   1 +
 drivers/net/wireless/ath/ath10k/Makefile   |   3 +-
 drivers/net/wireless/ath/ath10k/core.c     |   4 +
 drivers/net/wireless/ath/ath10k/core.h     |   8 +
 drivers/net/wireless/ath/ath10k/debug.c    |   3 +
 drivers/net/wireless/ath/ath10k/mac.c      |   9 +
 drivers/net/wireless/ath/ath10k/spectral.c | 563 +++++++++++++++++++++++++++++
 drivers/net/wireless/ath/ath10k/spectral.h |  64 ++++
 drivers/net/wireless/ath/ath10k/wmi.c      | 104 +++++-
 drivers/net/wireless/ath/ath10k/wmi.h      |  94 +++++
 drivers/net/wireless/ath/spectral_common.h |  24 ++
 11 files changed, 875 insertions(+), 2 deletions(-)
 create mode 100644 drivers/net/wireless/ath/ath10k/spectral.c
 create mode 100644 drivers/net/wireless/ath/ath10k/spectral.h

Comments

Kalle Valo July 21, 2014, 6:35 p.m. UTC | #1
Simon Wunderlich <sw@simonwunderlich.de> writes:

> Adds the spectral scan feature for ath10k. The spectral scan is triggered by
> configuring a mode through a debugfs control file. Samples can be gathered via
> another relay debugfs file.
>
> Essentially, to try it out:
>
> ip link set dev wlan0 up
> echo background > /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan_ctl
> iw dev wlan0 scan
> echo disable > /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan_ctl
> cat /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan0 > samples
>
> This feature is still experimental. Based on the original RFC patch of
> Sven Eckelmann.
>
> Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>
> Signed-off-by: Mathias Kretschmer <mathias.kretschmer@fokus.fraunhofer.de>

While I was commiting this to ath-next-test for build tests and review,
I had a trivial conflict. Please check the patch:

https://github.com/kvalo/ath/commit/85dec746d1f4728176bd06a53b6da4ef014f2a27

Oddly enough with this patch I see a new warning:

include/uapi/linux/swab.h:71:16: error: undefined identifier '__builtin_bswap64'

But I guess there's not much we can do about that?
Sven Eckelmann July 21, 2014, 7:02 p.m. UTC | #2
On Monday 21 July 2014 14:32:23 Simon Wunderlich wrote:
> diff --git a/drivers/net/wireless/ath/ath10k/Makefile
> b/drivers/net/wireless/ath/ath10k/Makefile index 2110a5c..e1dcefd 100644
> --- a/drivers/net/wireless/ath/ath10k/Makefile
> +++ b/drivers/net/wireless/ath/ath10k/Makefile
> @@ -8,7 +8,8 @@ ath10k_core-y += mac.o \
>  		 htt_tx.o \
>  		 txrx.o \
>  		 wmi.o \
> -		 bmi.o
> +		 bmi.o \
> +		 spectral.o


ath9k doesn't compile the debugfs+spectral source files when 
CONFIG_ATH9K_DEBUGFS is not enabled. So this most likely has to be changed to 

ath10k_core-$(CONFIG_ATH10K_DEBUGFS) += spectral.o

Otherwise the static inline functions in the spectral.h may collide with the 
functions inside spectral.c when CONFIG_ATH10K_DEBUGFS is disabled.

Kind regards,
	Sven
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Michal Kazior July 22, 2014, 8:07 a.m. UTC | #3
On 21 July 2014 14:32, Simon Wunderlich <sw@simonwunderlich.de> wrote:
> Adds the spectral scan feature for ath10k. The spectral scan is triggered by
> configuring a mode through a debugfs control file. Samples can be gathered via
> another relay debugfs file.
[...]
>  #endif /* CONFIG_ATH10K_DEBUGFS */
> diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c
> index 348a639..14d6234 100644
> --- a/drivers/net/wireless/ath/ath10k/mac.c
> +++ b/drivers/net/wireless/ath/ath10k/mac.c
> @@ -2201,6 +2201,7 @@ void ath10k_halt(struct ath10k *ar)
>         ath10k_peer_cleanup_all(ar);
>         ath10k_core_stop(ar);
>         ath10k_hif_power_down(ar);
> +       ath10k_disable_spectral(ar);

It makes little sense to call it after powering off the chip, no?
ath10k_disable_spectral() calls ath10k_wmi_vdev_spectral_enable()
which requires WMI (and thus firmware as a whole) to be running,
otherwise you'll fail to submit the command. Anyway, if you try to
send a WMI command when hw is restarting firmware won't respond
properly anyway and it'll print a warning.


>
>         spin_lock_bh(&ar->data_lock);
>         if (ar->scan.in_progress) {
> @@ -2671,8 +2672,16 @@ static void ath10k_remove_interface(struct ieee80211_hw *hw,
>                 dev_kfree_skb_any(arvif->beacon);
>                 arvif->beacon = NULL;
>         }
> +
>         spin_unlock_bh(&ar->data_lock);
>
> +       if (arvif->spectral_enabled) {
> +               ret = ath10k_disable_spectral(ar);
> +               if (ret)
> +                       ath10k_warn("Failed to disable spectral for vdev %i: %d\n",
> +                                   arvif->vdev_id, ret);

I'm aware ath10k still has some capitalized warnings but it'd be nice
to have this lower case (this is something that has been agreed upon
some time ago but still not all prints have been updated since then).


Micha?
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Kalle Valo July 22, 2014, 6:27 p.m. UTC | #4
Simon Wunderlich <sw@simonwunderlich.de> writes:

> Adds the spectral scan feature for ath10k. The spectral scan is triggered by
> configuring a mode through a debugfs control file. Samples can be gathered via
> another relay debugfs file.
>
> Essentially, to try it out:
>
> ip link set dev wlan0 up
> echo background > /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan_ctl
> iw dev wlan0 scan
> echo disable > /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan_ctl
> cat /sys/kernel/debug/ieee80211/phy0/ath10k/spectral_scan0 > samples
>
> This feature is still experimental. Based on the original RFC patch of
> Sven Eckelmann.
>
> Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>
> Signed-off-by: Mathias Kretschmer <mathias.kretschmer@fokus.fraunhofer.de>

Quick comments about the style.

> --- a/drivers/net/wireless/ath/ath10k/core.c
> +++ b/drivers/net/wireless/ath/ath10k/core.c
> @@ -699,6 +699,10 @@ struct ath10k *ath10k_core_create(void *hif_priv, struct device *dev,
>  	ar->hif.priv = hif_priv;
>  	ar->hif.ops = hif_ops;
>  
> +	ar->spectral_mode = SPECTRAL_DISABLED;
> +	ar->spec_config.count = WMI_SPECTRAL_COUNT_DEFAULT;
> +	ar->spec_config.fft_size = WMI_SPECTRAL_FFT_SIZE_DEFAULT;

I would rather have ath10k_spectral_init() or similar to avoid
sprinkling this stuff all over the driver.

> --- a/drivers/net/wireless/ath/ath10k/core.h
> +++ b/drivers/net/wireless/ath/ath10k/core.h
> @@ -230,6 +231,7 @@ struct ath10k_vif {
>  
>  	bool is_started;
>  	bool is_up;
> +	bool spectral_enabled;
>  	u32 aid;
>  	u8 bssid[ETH_ALEN];
>  
> @@ -469,6 +471,12 @@ struct ath10k {
>  #ifdef CONFIG_ATH10K_DEBUGFS
>  	struct ath10k_debug debug;
>  #endif
> +
> +	/* relay(fs) channel for spectral scan */
> +	struct rchan *rfs_chan_spec_scan;
> +	/* spectral_mode and spec_config are protected by conf_mutex */
> +	enum ath10k_spectral_mode spectral_mode;
> +	struct ath10k_spec_scan spec_config;
>  };

For all spectral stuff I would prefer to have a "substructure", just
like mac has. That way it's easier to know who owns what fields.

I know this structure is a mess now, I have been sloppy on review...

> diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c
> index 348a639..14d6234 100644
> --- a/drivers/net/wireless/ath/ath10k/mac.c
> +++ b/drivers/net/wireless/ath/ath10k/mac.c
> @@ -2201,6 +2201,7 @@ void ath10k_halt(struct ath10k *ar)
>  	ath10k_peer_cleanup_all(ar);
>  	ath10k_core_stop(ar);
>  	ath10k_hif_power_down(ar);
> +	ath10k_disable_spectral(ar);

Maybe ath10k_spectral_stop()?

>  
>  	spin_lock_bh(&ar->data_lock);
>  	if (ar->scan.in_progress) {
> @@ -2671,8 +2672,16 @@ static void ath10k_remove_interface(struct ieee80211_hw *hw,
>  		dev_kfree_skb_any(arvif->beacon);
>  		arvif->beacon = NULL;
>  	}
> +
>  	spin_unlock_bh(&ar->data_lock);
>  
> +	if (arvif->spectral_enabled) {
> +		ret = ath10k_disable_spectral(ar);
> +		if (ret)
> +			ath10k_warn("Failed to disable spectral for vdev %i: %d\n",
> +				    arvif->vdev_id, ret);
> +	}

And here as well? And move the check for arvif->spectral_enabled inside
spectral.c?

> diff --git a/drivers/net/wireless/ath/ath10k/spectral.c b/drivers/net/wireless/ath/ath10k/spectral.c
> new file mode 100644
> index 0000000..4179efb
> --- /dev/null
> +++ b/drivers/net/wireless/ath/ath10k/spectral.c
> @@ -0,0 +1,563 @@
> +/*
> + * Copyright (c) 2013 Qualcomm Atheros, Inc.
> + *
> + * Permission to use, copy, modify, and/or distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +#include <linux/relay.h>
> +#include "core.h"
> +#include "debug.h"
> +
> +static void
> +ath10k_debug_send_fft_sample(struct ath10k *ar,
> +			     const struct fft_sample_tlv *fft_sample_tlv)
> +{
> +	int length;
> +
> +	if (!ar->rfs_chan_spec_scan)
> +		return;
> +
> +	length = __be16_to_cpu(fft_sample_tlv->length) +
> +		 sizeof(*fft_sample_tlv);
> +	relay_write(ar->rfs_chan_spec_scan, fft_sample_tlv, length);
> +}
> +
> +static uint8_t get_max_exp(s8 max_index, u16 max_magnitude, size_t bin_len,
> +			   u8 *data)
> +{
> +	int dc_pos;
> +	u8 max_exp;
> +
> +	dc_pos = bin_len / 2;
> +	/* peak index outside of bins */
> +	if (dc_pos < max_index || -dc_pos >= max_index)
> +		return 0;

Empty line before the comment, please.

> +int ath10k_process_fft(struct ath10k *ar,
> +		       struct wmi_single_phyerr_rx_event *event,
> +		       struct phyerr_fft_report *fftr, size_t bin_len, u64 tsf)
> +{
> +	struct fft_sample_ath10k *fft_sample;
> +	u8 buf[sizeof(*fft_sample) + SPECTRAL_ATH10K_MAX_NUM_BINS];
> +	int dc_pos;
> +	u32 reg0, reg1;
> +	u16 peak_mag;
> +	u8 *bins;
> +	u16 freq1;
> +	u16 freq2;
> +	u16 total_gain_db;
> +	u16 base_pwr_db;
> +	u8 chain_idx;
> +	u32 nf_list1;
> +	u32 nf_list2;
> +	u16 length;

You could combine the variable declarations, for example:

u16 freq1, freq2, total_gain_db, and_so_on;

> +	fft_sample = (struct fft_sample_ath10k *)&buf;
> +
> +	if (bin_len < 64 || bin_len > SPECTRAL_ATH10K_MAX_NUM_BINS)
> +		return -EINVAL;
> +
> +	reg0 = __le32_to_cpu(fftr->reg0);
> +	reg1 = __le32_to_cpu(fftr->reg1);
> +
> +	length = sizeof(*fft_sample) - sizeof(struct fft_sample_tlv) + bin_len;
> +	fft_sample->tlv.type = ATH_FFT_SAMPLE_ATH10K;
> +	fft_sample->tlv.length = __cpu_to_be16(length);
> +
> +	/* TODO: there might be a reason why the hardware reports 20/40/80 MHz,
> +	 * but the results/plots suggest that its actually 22/44/88 MHz.
> +	 */
> +	switch (event->hdr.chan_width_mhz) {
> +	case 20:
> +		fft_sample->chan_width_mhz = 22;
> +		break;
> +	case 40:
> +		fft_sample->chan_width_mhz = 44;
> +		break;
> +	case 80:
> +		/* TODO: As experiments with an analogue sender and various
> +		 * configuaritions (fft-sizes of 64/128/256 and 20/40/80 Mhz)
> +		 * show, the particular configuration of 80 MHz/64 bins does
> +		 * not match with the other smaples at all. Until the reason
> +		 * for that is found, don't report these samples.
> +		 */
> +		if (bin_len == 64)
> +			return -EINVAL;
> +		fft_sample->chan_width_mhz = 88;
> +		break;
> +	default:
> +		fft_sample->chan_width_mhz = event->hdr.chan_width_mhz;
> +	}

Empty line here.

> +	fft_sample->relpwr_db = MS(reg1, SEARCH_FFT_REPORT_REG1_RELPWR_DB);
> +	fft_sample->avgpwr_db = MS(reg1, SEARCH_FFT_REPORT_REG1_AVGPWR_DB);
> +
> +	peak_mag = MS(reg1, SEARCH_FFT_REPORT_REG1_PEAK_MAG);
> +	fft_sample->max_magnitude = __cpu_to_be16(peak_mag);
> +	fft_sample->max_index = MS(reg0, SEARCH_FFT_REPORT_REG0_PEAK_SIDX);
> +	fft_sample->rssi = event->hdr.rssi_combined;
> +
> +	total_gain_db = MS(reg0, SEARCH_FFT_REPORT_REG0_TOTAL_GAIN_DB);
> +	base_pwr_db = MS(reg0, SEARCH_FFT_REPORT_REG0_BASE_PWR_DB);
> +	fft_sample->total_gain_db = __cpu_to_be16(total_gain_db);
> +	fft_sample->base_pwr_db = __cpu_to_be16(base_pwr_db);
> +
> +	freq1 = __le16_to_cpu(event->hdr.freq1);
> +	freq2 = __le16_to_cpu(event->hdr.freq2);
> +	fft_sample->freq1 = __cpu_to_be16(freq1);
> +	fft_sample->freq2 = __cpu_to_be16(freq2);
> +
> +	nf_list1 = __le32_to_cpu(event->hdr.nf_list_1);
> +	nf_list2 = __le32_to_cpu(event->hdr.nf_list_2);
> +	chain_idx = MS(reg0, SEARCH_FFT_REPORT_REG0_FFT_CHN_IDX);

Empty line here.

> +	switch (chain_idx) {
> +	case 0:
> +		fft_sample->noise = __cpu_to_be16(nf_list1 & 0xffffu);
> +		break;
> +	case 1:
> +		fft_sample->noise = __cpu_to_be16((nf_list1 >> 16) & 0xffffu);
> +		break;
> +	case 2:
> +		fft_sample->noise = __cpu_to_be16(nf_list2 & 0xffffu);
> +		break;
> +	case 3:
> +		fft_sample->noise = __cpu_to_be16((nf_list2 >> 16) & 0xffffu);
> +		break;
> +	}
> +
> +	bins = (u8 *)fftr;
> +	bins += sizeof(*fftr);
> +
> +	fft_sample->tsf = __cpu_to_be64(tsf);

Empty line here.

> +	arg.vdev_id              = vdev_id;
> +	arg.scan_count           = count;
> +	arg.scan_period          = WMI_SPECTRAL_PERIOD_DEFAULT;
> +	arg.scan_priority        = WMI_SPECTRAL_PRIORITY_DEFAULT;
> +	arg.scan_fft_size        = ar->spec_config.fft_size;
> +	arg.scan_gc_ena          = WMI_SPECTRAL_GC_ENA_DEFAULT;
> +	arg.scan_restart_ena     = WMI_SPECTRAL_RESTART_ENA_DEFAULT;
> +	arg.scan_noise_floor_ref = WMI_SPECTRAL_NOISE_FLOOR_REF_DEFAULT;
> +	arg.scan_init_delay      = WMI_SPECTRAL_INIT_DELAY_DEFAULT;
> +	arg.scan_nb_tone_thr     = WMI_SPECTRAL_NB_TONE_THR_DEFAULT;
> +	arg.scan_str_bin_thr     = WMI_SPECTRAL_STR_BIN_THR_DEFAULT;
> +	arg.scan_wb_rpt_mode     = WMI_SPECTRAL_WB_RPT_MODE_DEFAULT;
> +	arg.scan_rssi_rpt_mode   = WMI_SPECTRAL_RSSI_RPT_MODE_DEFAULT;
> +	arg.scan_rssi_thr        = WMI_SPECTRAL_RSSI_THR_DEFAULT;
> +	arg.scan_pwr_format      = WMI_SPECTRAL_PWR_FORMAT_DEFAULT;
> +	arg.scan_rpt_mode        = WMI_SPECTRAL_RPT_MODE_DEFAULT;
> +	arg.scan_bin_scale       = WMI_SPECTRAL_BIN_SCALE_DEFAULT;
> +	arg.scan_dbm_adj         = WMI_SPECTRAL_DBM_ADJ_DEFAULT;
> +	arg.scan_chn_mask        = WMI_SPECTRAL_CHN_MASK_DEFAULT;

Please don't indent the '=':

        arg.scan_dbm_adj = WMI_SPECTRAL_DBM_ADJ_DEFAULT
        arg.scan_chn_mask = WMI_SPECTRAL_CHN_MASK_DEFAULT;

> +/******************/
> +/* spectral_count */
> +/******************/

I don't think there's any point of adding these comments for each
debugfs file. I don't see how spectral.c even needs these kind of
comments, it's only 500 lines long.

> +void ath10k_spectral_deinit_debug(struct ath10k *ar)
> +{
> +	if (config_enabled(CONFIG_ATH10K_DEBUGFS) && ar->rfs_chan_spec_scan) {
> +		relay_close(ar->rfs_chan_spec_scan);
> +		ar->rfs_chan_spec_scan = NULL;
> +	}
> +}
> +
> +void ath10k_spectral_init_debug(struct ath10k *ar)
> +{
> +#ifdef CONFIG_ATH10K_DEBUGFS
> +	ar->rfs_chan_spec_scan = relay_open("spectral_scan",
> +					    ar->debug.debugfs_phy,
> +					    1024, 256, &rfs_spec_scan_cb,
> +					    NULL);
> +	debugfs_create_file("spectral_scan_ctl",
> +			    S_IRUSR | S_IWUSR,
> +			    ar->debug.debugfs_phy, ar,
> +			    &fops_spec_scan_ctl);
> +	debugfs_create_file("spectral_count",
> +			    S_IRUSR | S_IWUSR,
> +			    ar->debug.debugfs_phy, ar,
> +			    &fops_spectral_count);
> +	debugfs_create_file("spectral_bins",
> +			    S_IRUSR | S_IWUSR,
> +			    ar->debug.debugfs_phy, ar,
> +			    &fops_spectral_bins);
> +#endif
> +}

Do we even bother compiling spectral.c when ATH10K_DEBUGFS is disabled?
Should we instead just do this:

ath10k_core-$(CONFIG_ATH10K_DEBUGFS) += spectral.o


> diff --git a/drivers/net/wireless/ath/ath10k/spectral.h b/drivers/net/wireless/ath/ath10k/spectral.h
> new file mode 100644
> index 0000000..ee10ad2
> --- /dev/null
> +++ b/drivers/net/wireless/ath/ath10k/spectral.h

[...]

> +void ath10k_spectral_init_debug(struct ath10k *ar);
> +void ath10k_spectral_deinit_debug(struct ath10k *ar);
> +int ath10k_disable_spectral(struct ath10k *ar);
> +
> +#ifdef CONFIG_ATH10K_DEBUGFS
> +int ath10k_process_fft(struct ath10k *ar,
> +		       struct wmi_single_phyerr_rx_event *event,
> +		       struct phyerr_fft_report *fftr, size_t bin_len, u64 tsf);
> +#else
> +static inline int
> +ath10k_process_fft(struct ath10k *ar,
> +		   struct wmi_single_phyerr_rx_event *event,
> +		   struct phyerr_fft_report *fftr, size_t bin_len, u64 tsf)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_ATH10K_DEBUGFS */

Why ath10k_spectral_init_debug() are not within the ifdef?

> +int ath10k_wmi_vdev_spectral_conf(struct ath10k *ar,
> +				  const struct wmi_vdev_spectral_conf_arg *arg)
> +{
> +	struct wmi_vdev_spectral_conf_cmd *cmd;
> +	struct sk_buff *skb;
> +	u32 cmdid;
> +
> +	skb = ath10k_wmi_alloc_skb(sizeof(*cmd));
> +	if (!skb)
> +		return -ENOMEM;
> +
> +	cmd = (struct wmi_vdev_spectral_conf_cmd *)skb->data;
> +	cmd->vdev_id              = __cpu_to_le32(arg->vdev_id);
> +	cmd->scan_count           = __cpu_to_le32(arg->scan_count);
> +	cmd->scan_period          = __cpu_to_le32(arg->scan_period);
> +	cmd->scan_priority        = __cpu_to_le32(arg->scan_priority);

Please do not indent the '='.

> +int ath10k_wmi_vdev_spectral_enable(struct ath10k *ar, u32 vdev_id, u32 trigger,
> +				    u32 enable)
> +{
> +	struct wmi_vdev_spectral_enable_cmd *cmd;
> +	struct sk_buff *skb;
> +	u32 cmdid;
> +
> +	skb = ath10k_wmi_alloc_skb(sizeof(*cmd));
> +	if (!skb)
> +		return -ENOMEM;
> +
> +	cmd = (struct wmi_vdev_spectral_enable_cmd *)skb->data;
> +	cmd->vdev_id     = __cpu_to_le32(vdev_id);
> +	cmd->trigger_cmd = __cpu_to_le32(trigger);
> +	cmd->enable_cmd  = __cpu_to_le32(enable);

Here as well.

> +/* TODO: please add more comments if you have in-depth information */
> +struct wmi_vdev_spectral_conf_cmd {
> +	__le32 vdev_id;
> +	/* number of fft samples to send (0 for infinite) */
> +	__le32 scan_count;
> +	__le32 scan_period;
> +	__le32 scan_priority;
> +	/* number of bins in the FFT: 2^(fft_size - bin_scale) */
> +	__le32 scan_fft_size;
> +	__le32 scan_gc_ena;
> +	__le32 scan_restart_ena;
> +	__le32 scan_noise_floor_ref;
> +	__le32 scan_init_delay;
> +	__le32 scan_nb_tone_thr;
> +	__le32 scan_str_bin_thr;
> +	__le32 scan_wb_rpt_mode;
> +	__le32 scan_rssi_rpt_mode;
> +	__le32 scan_rssi_thr;
> +	__le32 scan_pwr_format;
> +	/* rpt_mode: Format of FFT report to software for spectral scan
> +	 * triggered FFTs:
> +	 *	0: No FFT report (only spectral scan summary report)
> +	 *	1: 2-dword summary of metrics for each completed FFT + spectral
> +	 *	   scan	summary report
> +	 *	2: 2-dword summary of metrics for each completed FFT +
> +	 *	   1x- oversampled bins(in-band) per FFT + spectral scan summary
> +	 *	   report
> +	 *	3: 2-dword summary of metrics for each completed FFT +
> +	 *	   2x- oversampled bins	(all) per FFT + spectral scan summary
> +	 */
> +	__le32 scan_rpt_mode;
> +	__le32 scan_bin_scale;
> +	__le32 scan_dbm_adj;
> +	__le32 scan_chn_mask;
> +} __packed;

Empty lines before the comments.

> --- a/drivers/net/wireless/ath/spectral_common.h
> +++ b/drivers/net/wireless/ath/spectral_common.h
> @@ -19,6 +19,10 @@
>  
>  #define SPECTRAL_HT20_NUM_BINS		56
>  #define SPECTRAL_HT20_40_NUM_BINS		128
> +/* TODO: could possibly be 512, but no samples this large
> + * could be acquired so far.
> + */
> +#define SPECTRAL_ATH10K_MAX_NUM_BINS		256

Empty line before the comment.

> +struct fft_sample_ath10k {
> +	struct fft_sample_tlv tlv;
> +	u8 chan_width_mhz;
> +	__be16 freq1;
> +	__be16 freq2;
> +	__be16 noise;
> +	__be16 max_magnitude;
> +	__be16 total_gain_db;
> +	__be16 base_pwr_db;
> +	__be64 tsf;
> +	s8 max_index;
> +	u8 rssi;
> +	u8 relpwr_db;
> +	u8 avgpwr_db;
> +	u8 max_exp;
> +
> +	u8 data[0];
> +} __packed;

__be16, that's a first. Just making sure that this really is big endian?
Simon Wunderlich July 23, 2014, 3:12 p.m. UTC | #5
Hey Kalle,

thanks for all the comments!

I'll make the changes as you suggested, although I'd like to point out that 
the "empty line before comment" and the "no indentation for WMI command 
parameters" things are not really consistent - there are also empty lines 
missing (e.g. struct wmi_start_scan_cmd) and indentation present (e.g. 
ath10k_wmi_peer_assoc and many others). Maybe that should be cleaned up at 
some point if you think that's important. :)

> > +struct fft_sample_ath10k {
> > +	struct fft_sample_tlv tlv;
> > +	u8 chan_width_mhz;
> > +	__be16 freq1;
> > +	__be16 freq2;
> > +	__be16 noise;
> > +	__be16 max_magnitude;
> > +	__be16 total_gain_db;
> > +	__be16 base_pwr_db;
> > +	__be64 tsf;
> > +	s8 max_index;
> > +	u8 rssi;
> > +	u8 relpwr_db;
> > +	u8 avgpwr_db;
> > +	u8 max_exp;
> > +
> > +	u8 data[0];
> > +} __packed;
> 
> __be16, that's a first. Just making sure that this really is big endian?

As the __le32 you use in other ath10k structs, this is just for checking - at 
least sparse should check that, maybe other tools as well.

I'll send a new patchset with the suggestions of Michal, Chun-Yeow and you 
soon. :)

Thanks!
    Simon
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Kalle Valo July 23, 2014, 4:34 p.m. UTC | #6
Simon Wunderlich <sw@simonwunderlich.de> writes:

> Hey Kalle,
>
> thanks for all the comments!
>
> I'll make the changes as you suggested, although I'd like to point out that 
> the "empty line before comment" and the "no indentation for WMI command 
> parameters" things are not really consistent - there are also empty lines 
> missing (e.g. struct wmi_start_scan_cmd) and indentation present (e.g. 
> ath10k_wmi_peer_assoc and many others). Maybe that should be cleaned up at 
> some point if you think that's important. :)

Yeah, I have been sloppy in reviewing those part before but not anymore
:) Also struct ath10k is quite a mess now. Sorry for the confusion.

I'm planning to fix these at some point, but if someone else wants to do
it I'm very happy to take patches.

>> > +struct fft_sample_ath10k {
>> > +	struct fft_sample_tlv tlv;
>> > +	u8 chan_width_mhz;
>> > +	__be16 freq1;
>> > +	__be16 freq2;
>> > +	__be16 noise;
>> > +	__be16 max_magnitude;
>> > +	__be16 total_gain_db;
>> > +	__be16 base_pwr_db;
>> > +	__be64 tsf;
>> > +	s8 max_index;
>> > +	u8 rssi;
>> > +	u8 relpwr_db;
>> > +	u8 avgpwr_db;
>> > +	u8 max_exp;
>> > +
>> > +	u8 data[0];
>> > +} __packed;
>> 
>> __be16, that's a first. Just making sure that this really is big endian?
>
> As the __le32 you use in other ath10k structs, this is just for checking - at 
> least sparse should check that, maybe other tools as well.

Sorry, I didn't understand your comment here. But basically I was asking
is the fft sample really in big endian? I assume it would be little
endian as everything else coming from the firmware.
Simon Wunderlich July 23, 2014, 4:40 p.m. UTC | #7
Hey Kalle,

> Simon Wunderlich <sw@simonwunderlich.de> writes:
> >> > +struct fft_sample_ath10k {
> >> > +	struct fft_sample_tlv tlv;
> >> > +	u8 chan_width_mhz;
> >> > +	__be16 freq1;
> >> > +	__be16 freq2;
> >> > +	__be16 noise;
> >> > +	__be16 max_magnitude;
> >> > +	__be16 total_gain_db;
> >> > +	__be16 base_pwr_db;
> >> > +	__be64 tsf;
> >> > +	s8 max_index;
> >> > +	u8 rssi;
> >> > +	u8 relpwr_db;
> >> > +	u8 avgpwr_db;
> >> > +	u8 max_exp;
> >> > +
> >> > +	u8 data[0];
> >> > +} __packed;
> >> 
> >> __be16, that's a first. Just making sure that this really is big endian?
> > 
> > As the __le32 you use in other ath10k structs, this is just for checking
> > - at least sparse should check that, maybe other tools as well.
> 
> Sorry, I didn't understand your comment here. But basically I was asking
> is the fft sample really in big endian? I assume it would be little
> endian as everything else coming from the firmware.

Yeah that is intended - we are also using big endian in ath9k to send fft 
samples to userspace, because that is the network byte order and we then just 
use ntohs() and friends in userspace to read samples from any system. 
Therefore we intent to use the same encoding in ath10k. :)

Cheers,
    Simon
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Kalle Valo July 23, 2014, 4:44 p.m. UTC | #8
Simon Wunderlich <sw@simonwunderlich.de> writes:

> Hey Kalle,
>
>> Simon Wunderlich <sw@simonwunderlich.de> writes:
>> >> > +struct fft_sample_ath10k {
>> >> > +	struct fft_sample_tlv tlv;
>> >> > +	u8 chan_width_mhz;
>> >> > +	__be16 freq1;
>> >> > +	__be16 freq2;
>> >> > +	__be16 noise;
>> >> > +	__be16 max_magnitude;
>> >> > +	__be16 total_gain_db;
>> >> > +	__be16 base_pwr_db;
>> >> > +	__be64 tsf;
>> >> > +	s8 max_index;
>> >> > +	u8 rssi;
>> >> > +	u8 relpwr_db;
>> >> > +	u8 avgpwr_db;
>> >> > +	u8 max_exp;
>> >> > +
>> >> > +	u8 data[0];
>> >> > +} __packed;
>> >> 
>> >> __be16, that's a first. Just making sure that this really is big endian?
>> > 
>> > As the __le32 you use in other ath10k structs, this is just for checking
>> > - at least sparse should check that, maybe other tools as well.
>> 
>> Sorry, I didn't understand your comment here. But basically I was asking
>> is the fft sample really in big endian? I assume it would be little
>> endian as everything else coming from the firmware.
>
> Yeah that is intended - we are also using big endian in ath9k to send fft 
> samples to userspace, because that is the network byte order and we then just 
> use ntohs() and friends in userspace to read samples from any system. 
> Therefore we intent to use the same encoding in ath10k. :)

Ah, this is the struct going to user space? I thought it's coming from
the firmware. Then forget my question :)
diff mbox

Patch

diff --git a/drivers/net/wireless/ath/ath10k/Kconfig b/drivers/net/wireless/ath/ath10k/Kconfig
index 489bf8b..095dcaf 100644
--- a/drivers/net/wireless/ath/ath10k/Kconfig
+++ b/drivers/net/wireless/ath/ath10k/Kconfig
@@ -27,6 +27,7 @@  config ATH10K_DEBUG
 config ATH10K_DEBUGFS
 	bool "Atheros ath10k debugfs support"
 	depends on ATH10K
+	select RELAY
 	---help---
 	  Enabled debugfs support
 
diff --git a/drivers/net/wireless/ath/ath10k/Makefile b/drivers/net/wireless/ath/ath10k/Makefile
index 2110a5c..e1dcefd 100644
--- a/drivers/net/wireless/ath/ath10k/Makefile
+++ b/drivers/net/wireless/ath/ath10k/Makefile
@@ -8,7 +8,8 @@  ath10k_core-y += mac.o \
 		 htt_tx.o \
 		 txrx.o \
 		 wmi.o \
-		 bmi.o
+		 bmi.o \
+		 spectral.o
 
 ath10k_core-$(CONFIG_ATH10K_TRACING) += trace.o
 
diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c
index ebc5fc2..3a113fb 100644
--- a/drivers/net/wireless/ath/ath10k/core.c
+++ b/drivers/net/wireless/ath/ath10k/core.c
@@ -699,6 +699,10 @@  struct ath10k *ath10k_core_create(void *hif_priv, struct device *dev,
 	ar->hif.priv = hif_priv;
 	ar->hif.ops = hif_ops;
 
+	ar->spectral_mode = SPECTRAL_DISABLED;
+	ar->spec_config.count = WMI_SPECTRAL_COUNT_DEFAULT;
+	ar->spec_config.fft_size = WMI_SPECTRAL_FFT_SIZE_DEFAULT;
+
 	init_completion(&ar->scan.started);
 	init_completion(&ar->scan.completed);
 	init_completion(&ar->scan.on_channel);
diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h
index 80aa5a4..4af527a 100644
--- a/drivers/net/wireless/ath/ath10k/core.h
+++ b/drivers/net/wireless/ath/ath10k/core.h
@@ -31,6 +31,7 @@ 
 #include "../ath.h"
 #include "../regd.h"
 #include "../dfs_pattern_detector.h"
+#include "spectral.h"
 
 #define MS(_v, _f) (((_v) & _f##_MASK) >> _f##_LSB)
 #define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)
@@ -230,6 +231,7 @@  struct ath10k_vif {
 
 	bool is_started;
 	bool is_up;
+	bool spectral_enabled;
 	u32 aid;
 	u8 bssid[ETH_ALEN];
 
@@ -469,6 +471,12 @@  struct ath10k {
 #ifdef CONFIG_ATH10K_DEBUGFS
 	struct ath10k_debug debug;
 #endif
+
+	/* relay(fs) channel for spectral scan */
+	struct rchan *rfs_chan_spec_scan;
+	/* spectral_mode and spec_config are protected by conf_mutex */
+	enum ath10k_spectral_mode spectral_mode;
+	struct ath10k_spec_scan spec_config;
 };
 
 struct ath10k *ath10k_core_create(void *hif_priv, struct device *dev,
diff --git a/drivers/net/wireless/ath/ath10k/debug.c b/drivers/net/wireless/ath/ath10k/debug.c
index f56f358..f8f4f6f 100644
--- a/drivers/net/wireless/ath/ath10k/debug.c
+++ b/drivers/net/wireless/ath/ath10k/debug.c
@@ -853,12 +853,15 @@  int ath10k_debug_create(struct ath10k *ar)
 				    &fops_dfs_stats);
 	}
 
+	ath10k_spectral_init_debug(ar);
+
 	return 0;
 }
 
 void ath10k_debug_destroy(struct ath10k *ar)
 {
 	cancel_delayed_work_sync(&ar->debug.htt_stats_dwork);
+	ath10k_spectral_deinit_debug(ar);
 }
 
 #endif /* CONFIG_ATH10K_DEBUGFS */
diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c
index 348a639..14d6234 100644
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -2201,6 +2201,7 @@  void ath10k_halt(struct ath10k *ar)
 	ath10k_peer_cleanup_all(ar);
 	ath10k_core_stop(ar);
 	ath10k_hif_power_down(ar);
+	ath10k_disable_spectral(ar);
 
 	spin_lock_bh(&ar->data_lock);
 	if (ar->scan.in_progress) {
@@ -2671,8 +2672,16 @@  static void ath10k_remove_interface(struct ieee80211_hw *hw,
 		dev_kfree_skb_any(arvif->beacon);
 		arvif->beacon = NULL;
 	}
+
 	spin_unlock_bh(&ar->data_lock);
 
+	if (arvif->spectral_enabled) {
+		ret = ath10k_disable_spectral(ar);
+		if (ret)
+			ath10k_warn("Failed to disable spectral for vdev %i: %d\n",
+				    arvif->vdev_id, ret);
+	}
+
 	ar->free_vdev_map |= 1 << (arvif->vdev_id);
 	list_del(&arvif->list);
 
diff --git a/drivers/net/wireless/ath/ath10k/spectral.c b/drivers/net/wireless/ath/ath10k/spectral.c
new file mode 100644
index 0000000..4179efb
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/spectral.c
@@ -0,0 +1,563 @@ 
+/*
+ * Copyright (c) 2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/relay.h>
+#include "core.h"
+#include "debug.h"
+
+static void
+ath10k_debug_send_fft_sample(struct ath10k *ar,
+			     const struct fft_sample_tlv *fft_sample_tlv)
+{
+	int length;
+
+	if (!ar->rfs_chan_spec_scan)
+		return;
+
+	length = __be16_to_cpu(fft_sample_tlv->length) +
+		 sizeof(*fft_sample_tlv);
+	relay_write(ar->rfs_chan_spec_scan, fft_sample_tlv, length);
+}
+
+static uint8_t get_max_exp(s8 max_index, u16 max_magnitude, size_t bin_len,
+			   u8 *data)
+{
+	int dc_pos;
+	u8 max_exp;
+
+	dc_pos = bin_len / 2;
+	/* peak index outside of bins */
+	if (dc_pos < max_index || -dc_pos >= max_index)
+		return 0;
+
+	for (max_exp = 0; max_exp < 8; max_exp++) {
+		if (data[dc_pos + max_index] == (max_magnitude >> max_exp))
+			break;
+	}
+
+	/* max_exp not found */
+	if (data[dc_pos + max_index] != (max_magnitude >> max_exp))
+		return 0;
+
+	return max_exp;
+}
+
+int ath10k_process_fft(struct ath10k *ar,
+		       struct wmi_single_phyerr_rx_event *event,
+		       struct phyerr_fft_report *fftr, size_t bin_len, u64 tsf)
+{
+	struct fft_sample_ath10k *fft_sample;
+	u8 buf[sizeof(*fft_sample) + SPECTRAL_ATH10K_MAX_NUM_BINS];
+	int dc_pos;
+	u32 reg0, reg1;
+	u16 peak_mag;
+	u8 *bins;
+	u16 freq1;
+	u16 freq2;
+	u16 total_gain_db;
+	u16 base_pwr_db;
+	u8 chain_idx;
+	u32 nf_list1;
+	u32 nf_list2;
+	u16 length;
+
+	fft_sample = (struct fft_sample_ath10k *)&buf;
+
+	if (bin_len < 64 || bin_len > SPECTRAL_ATH10K_MAX_NUM_BINS)
+		return -EINVAL;
+
+	reg0 = __le32_to_cpu(fftr->reg0);
+	reg1 = __le32_to_cpu(fftr->reg1);
+
+	length = sizeof(*fft_sample) - sizeof(struct fft_sample_tlv) + bin_len;
+	fft_sample->tlv.type = ATH_FFT_SAMPLE_ATH10K;
+	fft_sample->tlv.length = __cpu_to_be16(length);
+
+	/* TODO: there might be a reason why the hardware reports 20/40/80 MHz,
+	 * but the results/plots suggest that its actually 22/44/88 MHz.
+	 */
+	switch (event->hdr.chan_width_mhz) {
+	case 20:
+		fft_sample->chan_width_mhz = 22;
+		break;
+	case 40:
+		fft_sample->chan_width_mhz = 44;
+		break;
+	case 80:
+		/* TODO: As experiments with an analogue sender and various
+		 * configuaritions (fft-sizes of 64/128/256 and 20/40/80 Mhz)
+		 * show, the particular configuration of 80 MHz/64 bins does
+		 * not match with the other smaples at all. Until the reason
+		 * for that is found, don't report these samples.
+		 */
+		if (bin_len == 64)
+			return -EINVAL;
+		fft_sample->chan_width_mhz = 88;
+		break;
+	default:
+		fft_sample->chan_width_mhz = event->hdr.chan_width_mhz;
+	}
+	fft_sample->relpwr_db = MS(reg1, SEARCH_FFT_REPORT_REG1_RELPWR_DB);
+	fft_sample->avgpwr_db = MS(reg1, SEARCH_FFT_REPORT_REG1_AVGPWR_DB);
+
+	peak_mag = MS(reg1, SEARCH_FFT_REPORT_REG1_PEAK_MAG);
+	fft_sample->max_magnitude = __cpu_to_be16(peak_mag);
+	fft_sample->max_index = MS(reg0, SEARCH_FFT_REPORT_REG0_PEAK_SIDX);
+	fft_sample->rssi = event->hdr.rssi_combined;
+
+	total_gain_db = MS(reg0, SEARCH_FFT_REPORT_REG0_TOTAL_GAIN_DB);
+	base_pwr_db = MS(reg0, SEARCH_FFT_REPORT_REG0_BASE_PWR_DB);
+	fft_sample->total_gain_db = __cpu_to_be16(total_gain_db);
+	fft_sample->base_pwr_db = __cpu_to_be16(base_pwr_db);
+
+	freq1 = __le16_to_cpu(event->hdr.freq1);
+	freq2 = __le16_to_cpu(event->hdr.freq2);
+	fft_sample->freq1 = __cpu_to_be16(freq1);
+	fft_sample->freq2 = __cpu_to_be16(freq2);
+
+	nf_list1 = __le32_to_cpu(event->hdr.nf_list_1);
+	nf_list2 = __le32_to_cpu(event->hdr.nf_list_2);
+	chain_idx = MS(reg0, SEARCH_FFT_REPORT_REG0_FFT_CHN_IDX);
+	switch (chain_idx) {
+	case 0:
+		fft_sample->noise = __cpu_to_be16(nf_list1 & 0xffffu);
+		break;
+	case 1:
+		fft_sample->noise = __cpu_to_be16((nf_list1 >> 16) & 0xffffu);
+		break;
+	case 2:
+		fft_sample->noise = __cpu_to_be16(nf_list2 & 0xffffu);
+		break;
+	case 3:
+		fft_sample->noise = __cpu_to_be16((nf_list2 >> 16) & 0xffffu);
+		break;
+	}
+
+	bins = (u8 *)fftr;
+	bins += sizeof(*fftr);
+
+	fft_sample->tsf = __cpu_to_be64(tsf);
+	/* max_exp has been directly reported by previous hardware (ath9k),
+	 * maybe its possible to get it by other means?
+	 */
+	fft_sample->max_exp = get_max_exp(fft_sample->max_index, peak_mag,
+					  bin_len, bins);
+
+	memcpy(fft_sample->data, bins, bin_len);
+
+	/* DC value (value in the middle) is the blind spot of the spectral
+	 * sample and invalid, interpolate it.
+	 */
+	dc_pos = bin_len / 2;
+	fft_sample->data[dc_pos] = (fft_sample->data[dc_pos + 1] +
+				    fft_sample->data[dc_pos - 1]) / 2;
+
+	ath10k_debug_send_fft_sample(ar, &fft_sample->tlv);
+
+	return 0;
+}
+
+static struct ath10k_vif *ath10k_get_spectral_vdev(struct ath10k *ar)
+{
+	struct ath10k_vif *arvif;
+
+	lockdep_assert_held(&ar->conf_mutex);
+
+	if (list_empty(&ar->arvifs))
+		return NULL;
+
+	/* if there already is a vif doing spectral, return that. */
+	list_for_each_entry(arvif, &ar->arvifs, list)
+		if (arvif->spectral_enabled)
+			return arvif;
+
+	/* otherwise, return the first vif. */
+	return list_first_entry(&ar->arvifs, typeof(*arvif), list);
+}
+
+static int ath10k_spectral_scan_trigger(struct ath10k *ar)
+{
+	struct ath10k_vif *arvif;
+	int res;
+	int vdev_id;
+
+	lockdep_assert_held(&ar->conf_mutex);
+
+	arvif = ath10k_get_spectral_vdev(ar);
+	if (!arvif)
+		return -ENODEV;
+	vdev_id = arvif->vdev_id;
+
+	if (ar->spectral_mode == SPECTRAL_DISABLED)
+		return 0;
+
+	res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
+					      WMI_SPECTRAL_TRIGGER_CMD_CLEAR,
+					      WMI_SPECTRAL_ENABLE_CMD_ENABLE);
+	if (res < 0)
+		return res;
+
+	res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
+					      WMI_SPECTRAL_TRIGGER_CMD_TRIGGER,
+					      WMI_SPECTRAL_ENABLE_CMD_ENABLE);
+	if (res < 0)
+		return res;
+
+	return 0;
+}
+
+static int ath10k_spectral_scan_config(struct ath10k *ar,
+				       enum ath10k_spectral_mode mode)
+{
+	struct wmi_vdev_spectral_conf_arg arg;
+	struct ath10k_vif *arvif;
+	int vdev_id, count, res = 0;
+
+	lockdep_assert_held(&ar->conf_mutex);
+
+	arvif = ath10k_get_spectral_vdev(ar);
+	if (!arvif)
+		return -ENODEV;
+
+	vdev_id = arvif->vdev_id;
+
+	arvif->spectral_enabled = (mode != SPECTRAL_DISABLED);
+	ar->spectral_mode = mode;
+
+	res = ath10k_wmi_vdev_spectral_enable(ar, vdev_id,
+					      WMI_SPECTRAL_TRIGGER_CMD_CLEAR,
+					      WMI_SPECTRAL_ENABLE_CMD_DISABLE);
+	if (res < 0) {
+		ath10k_warn("failed to enable spectral scan: %d\n", res);
+		return res;
+	}
+
+	if (mode == SPECTRAL_DISABLED)
+		return 0;
+
+	if (mode == SPECTRAL_BACKGROUND)
+		count = WMI_SPECTRAL_COUNT_DEFAULT;
+	else
+		count = max_t(u8, 1, ar->spec_config.count);
+
+	arg.vdev_id              = vdev_id;
+	arg.scan_count           = count;
+	arg.scan_period          = WMI_SPECTRAL_PERIOD_DEFAULT;
+	arg.scan_priority        = WMI_SPECTRAL_PRIORITY_DEFAULT;
+	arg.scan_fft_size        = ar->spec_config.fft_size;
+	arg.scan_gc_ena          = WMI_SPECTRAL_GC_ENA_DEFAULT;
+	arg.scan_restart_ena     = WMI_SPECTRAL_RESTART_ENA_DEFAULT;
+	arg.scan_noise_floor_ref = WMI_SPECTRAL_NOISE_FLOOR_REF_DEFAULT;
+	arg.scan_init_delay      = WMI_SPECTRAL_INIT_DELAY_DEFAULT;
+	arg.scan_nb_tone_thr     = WMI_SPECTRAL_NB_TONE_THR_DEFAULT;
+	arg.scan_str_bin_thr     = WMI_SPECTRAL_STR_BIN_THR_DEFAULT;
+	arg.scan_wb_rpt_mode     = WMI_SPECTRAL_WB_RPT_MODE_DEFAULT;
+	arg.scan_rssi_rpt_mode   = WMI_SPECTRAL_RSSI_RPT_MODE_DEFAULT;
+	arg.scan_rssi_thr        = WMI_SPECTRAL_RSSI_THR_DEFAULT;
+	arg.scan_pwr_format      = WMI_SPECTRAL_PWR_FORMAT_DEFAULT;
+	arg.scan_rpt_mode        = WMI_SPECTRAL_RPT_MODE_DEFAULT;
+	arg.scan_bin_scale       = WMI_SPECTRAL_BIN_SCALE_DEFAULT;
+	arg.scan_dbm_adj         = WMI_SPECTRAL_DBM_ADJ_DEFAULT;
+	arg.scan_chn_mask        = WMI_SPECTRAL_CHN_MASK_DEFAULT;
+
+	res = ath10k_wmi_vdev_spectral_conf(ar, &arg);
+	if (res < 0) {
+		ath10k_warn("failed to configure spectral scan: %d\n", res);
+		return res;
+	}
+
+	return 0;
+}
+
+int ath10k_disable_spectral(struct ath10k *ar)
+{
+	return ath10k_spectral_scan_config(ar, SPECTRAL_DISABLED);
+}
+
+/*********************/
+/* spectral_scan_ctl */
+/*********************/
+
+static ssize_t read_file_spec_scan_ctl(struct file *file, char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	char *mode = "";
+	unsigned int len;
+	enum ath10k_spectral_mode spectral_mode;
+
+	mutex_lock(&ar->conf_mutex);
+	spectral_mode = ar->spectral_mode;
+	mutex_unlock(&ar->conf_mutex);
+
+	switch (spectral_mode) {
+	case SPECTRAL_DISABLED:
+		mode = "disable";
+		break;
+	case SPECTRAL_BACKGROUND:
+		mode = "background";
+		break;
+	case SPECTRAL_MANUAL:
+		mode = "manual";
+		break;
+	}
+
+	len = strlen(mode);
+	return simple_read_from_buffer(user_buf, count, ppos, mode, len);
+}
+
+static ssize_t write_file_spec_scan_ctl(struct file *file,
+					const char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	char buf[32];
+	ssize_t len;
+	int res;
+
+	len = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+
+	mutex_lock(&ar->conf_mutex);
+
+	if (strncmp("trigger", buf, 7) == 0) {
+		if (ar->spectral_mode == SPECTRAL_MANUAL ||
+		    ar->spectral_mode == SPECTRAL_BACKGROUND) {
+			/* reset the configuration to adopt possibly changed
+			 * debugfs parameters
+			 */
+			res = ath10k_spectral_scan_config(ar,
+							  ar->spectral_mode);
+			if (res < 0) {
+				ath10k_warn("failed to reconfigure spectral scan: %d\n",
+					    res);
+			}
+			res = ath10k_spectral_scan_trigger(ar);
+			if (res < 0) {
+				ath10k_warn("failed to trigger spectral scan: %d\n",
+					    res);
+			}
+		} else {
+			res = -EINVAL;
+		}
+	} else if (strncmp("background", buf, 9) == 0) {
+		res = ath10k_spectral_scan_config(ar, SPECTRAL_BACKGROUND);
+	} else if (strncmp("manual", buf, 6) == 0) {
+		res = ath10k_spectral_scan_config(ar, SPECTRAL_MANUAL);
+	} else if (strncmp("disable", buf, 7) == 0) {
+		res = ath10k_spectral_scan_config(ar, SPECTRAL_DISABLED);
+	} else {
+		res = -EINVAL;
+	}
+
+	mutex_unlock(&ar->conf_mutex);
+
+	if (res < 0)
+		return res;
+
+	return count;
+}
+
+static const struct file_operations fops_spec_scan_ctl = {
+	.read = read_file_spec_scan_ctl,
+	.write = write_file_spec_scan_ctl,
+	.open = simple_open,
+	.owner = THIS_MODULE,
+	.llseek = default_llseek,
+};
+
+/******************/
+/* spectral_count */
+/******************/
+
+static ssize_t read_file_spectral_count(struct file *file,
+					char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	char buf[32];
+	unsigned int len;
+	u8 spectral_count;
+
+	mutex_lock(&ar->conf_mutex);
+	spectral_count = ar->spec_config.count;
+	mutex_unlock(&ar->conf_mutex);
+
+	len = sprintf(buf, "%d\n", spectral_count);
+	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static ssize_t write_file_spectral_count(struct file *file,
+					 const char __user *user_buf,
+					 size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	unsigned long val;
+	char buf[32];
+	ssize_t len;
+
+	len = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	if (val < 0 || val > 255)
+		return -EINVAL;
+
+	mutex_lock(&ar->conf_mutex);
+	ar->spec_config.count = val;
+	mutex_unlock(&ar->conf_mutex);
+
+	return count;
+}
+
+static const struct file_operations fops_spectral_count = {
+	.read = read_file_spectral_count,
+	.write = write_file_spectral_count,
+	.open = simple_open,
+	.owner = THIS_MODULE,
+	.llseek = default_llseek,
+};
+
+/*****************/
+/* spectral_bins */
+/*****************/
+
+static ssize_t read_file_spectral_bins(struct file *file,
+				       char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	char buf[32];
+	unsigned int len;
+	unsigned int bins;
+
+	mutex_lock(&ar->conf_mutex);
+	bins = 1 << (ar->spec_config.fft_size - WMI_SPECTRAL_BIN_SCALE_DEFAULT);
+	mutex_unlock(&ar->conf_mutex);
+
+	len = sprintf(buf, "%d\n", bins);
+	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static ssize_t write_file_spectral_bins(struct file *file,
+					const char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	unsigned long val;
+	char buf[32];
+	ssize_t len;
+
+	len = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	if (val < 64 || val > SPECTRAL_ATH10K_MAX_NUM_BINS)
+		return -EINVAL;
+
+	if (!is_power_of_2(val))
+		return -EINVAL;
+
+	mutex_lock(&ar->conf_mutex);
+	ar->spec_config.fft_size = ilog2(val);
+	ar->spec_config.fft_size += WMI_SPECTRAL_BIN_SCALE_DEFAULT;
+	mutex_unlock(&ar->conf_mutex);
+
+	return count;
+}
+
+static const struct file_operations fops_spectral_bins = {
+	.read = read_file_spectral_bins,
+	.write = write_file_spectral_bins,
+	.open = simple_open,
+	.owner = THIS_MODULE,
+	.llseek = default_llseek,
+};
+
+/*******************/
+/* Relay interface */
+/*******************/
+
+static struct dentry *create_buf_file_handler(const char *filename,
+					      struct dentry *parent,
+					      umode_t mode,
+					      struct rchan_buf *buf,
+					      int *is_global)
+{
+	struct dentry *buf_file;
+
+	buf_file = debugfs_create_file(filename, mode, parent, buf,
+				       &relay_file_operations);
+	*is_global = 1;
+	return buf_file;
+}
+
+static int remove_buf_file_handler(struct dentry *dentry)
+{
+	debugfs_remove(dentry);
+
+	return 0;
+}
+
+static struct rchan_callbacks rfs_spec_scan_cb = {
+	.create_buf_file = create_buf_file_handler,
+	.remove_buf_file = remove_buf_file_handler,
+};
+
+/*********************/
+/* Debug Init/Deinit */
+/*********************/
+
+void ath10k_spectral_deinit_debug(struct ath10k *ar)
+{
+	if (config_enabled(CONFIG_ATH10K_DEBUGFS) && ar->rfs_chan_spec_scan) {
+		relay_close(ar->rfs_chan_spec_scan);
+		ar->rfs_chan_spec_scan = NULL;
+	}
+}
+
+void ath10k_spectral_init_debug(struct ath10k *ar)
+{
+#ifdef CONFIG_ATH10K_DEBUGFS
+	ar->rfs_chan_spec_scan = relay_open("spectral_scan",
+					    ar->debug.debugfs_phy,
+					    1024, 256, &rfs_spec_scan_cb,
+					    NULL);
+	debugfs_create_file("spectral_scan_ctl",
+			    S_IRUSR | S_IWUSR,
+			    ar->debug.debugfs_phy, ar,
+			    &fops_spec_scan_ctl);
+	debugfs_create_file("spectral_count",
+			    S_IRUSR | S_IWUSR,
+			    ar->debug.debugfs_phy, ar,
+			    &fops_spectral_count);
+	debugfs_create_file("spectral_bins",
+			    S_IRUSR | S_IWUSR,
+			    ar->debug.debugfs_phy, ar,
+			    &fops_spectral_bins);
+#endif
+}
diff --git a/drivers/net/wireless/ath/ath10k/spectral.h b/drivers/net/wireless/ath/ath10k/spectral.h
new file mode 100644
index 0000000..ee10ad2
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/spectral.h
@@ -0,0 +1,64 @@ 
+/*
+ * Copyright (c) 2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef SPECTRAL_H
+#define SPECTRAL_H
+
+#include "../spectral_common.h"
+
+/**
+ * struct ath10k_spec_scan - parameters for Atheros spectral scan
+ *
+ * @count: number of scan results requested for manual mode
+ */
+struct ath10k_spec_scan {
+	u8 count;
+	u8 fft_size;
+};
+
+/* enum ath10k_spectral_mode:
+ *
+ * @SPECTRAL_DISABLED: spectral mode is disabled
+ * @SPECTRAL_BACKGROUND: hardware sends samples when it is not busy with
+ *	something else.
+ * @SPECTRAL_MANUAL: spectral scan is enabled, triggering for samples
+ *	is performed manually.
+ */
+enum ath10k_spectral_mode {
+	SPECTRAL_DISABLED = 0,
+	SPECTRAL_BACKGROUND,
+	SPECTRAL_MANUAL,
+};
+
+void ath10k_spectral_init_debug(struct ath10k *ar);
+void ath10k_spectral_deinit_debug(struct ath10k *ar);
+int ath10k_disable_spectral(struct ath10k *ar);
+
+#ifdef CONFIG_ATH10K_DEBUGFS
+int ath10k_process_fft(struct ath10k *ar,
+		       struct wmi_single_phyerr_rx_event *event,
+		       struct phyerr_fft_report *fftr, size_t bin_len, u64 tsf);
+#else
+static inline int
+ath10k_process_fft(struct ath10k *ar,
+		   struct wmi_single_phyerr_rx_event *event,
+		   struct phyerr_fft_report *fftr, size_t bin_len, u64 tsf)
+{
+	return 0;
+}
+#endif /* CONFIG_ATH10K_DEBUGFS */
+
+#endif /* SPECTRAL_H */
diff --git a/drivers/net/wireless/ath/ath10k/wmi.c b/drivers/net/wireless/ath/ath10k/wmi.c
index beac30d..1422296 100644
--- a/drivers/net/wireless/ath/ath10k/wmi.c
+++ b/drivers/net/wireless/ath/ath10k/wmi.c
@@ -1649,7 +1649,53 @@  static void ath10k_wmi_event_spectral_scan(struct ath10k *ar,
 				struct wmi_single_phyerr_rx_event *event,
 				u64 tsf)
 {
-	ath10k_dbg(ATH10K_DBG_WMI, "wmi event spectral scan\n");
+	int buf_len, tlv_len, res, i = 0;
+	struct phyerr_tlv *tlv;
+	u8 *tlv_buf;
+	struct phyerr_fft_report *fftr;
+	size_t fftr_len;
+
+	buf_len = __le32_to_cpu(event->hdr.buf_len);
+
+	while (i < buf_len) {
+		if (i + sizeof(*tlv) > buf_len) {
+			ath10k_warn("failed to parse phyerr tlv header at byte %d\n",
+				    i);
+			return;
+		}
+
+		tlv = (struct phyerr_tlv *)&event->bufp[i];
+		tlv_len = __le16_to_cpu(tlv->len);
+		tlv_buf = &event->bufp[i + sizeof(*tlv)];
+
+		if (i + sizeof(*tlv) + tlv_len > buf_len) {
+			ath10k_warn("failed to parse phyerr tlv payload at byte %d\n",
+				    i);
+			return;
+		}
+
+		switch (tlv->tag) {
+		case PHYERR_TLV_TAG_SEARCH_FFT_REPORT:
+			if (sizeof(*fftr) > tlv_len) {
+				ath10k_warn("failed to parse fft report at byte %d\n",
+					    i);
+				return;
+			}
+
+			fftr_len = tlv_len - sizeof(*fftr);
+			fftr = (struct phyerr_fft_report *)tlv_buf;
+			res = ath10k_process_fft(ar, event, fftr, fftr_len,
+						 tsf);
+			if (res < 0) {
+				ath10k_warn("failed to process fft report: %d\n",
+					    res);
+				return;
+			}
+			break;
+		}
+
+		i += sizeof(*tlv) + tlv_len;
+	}
 }
 
 static void ath10k_wmi_event_phyerr(struct ath10k *ar, struct sk_buff *skb)
@@ -3191,6 +3237,62 @@  int ath10k_wmi_vdev_install_key(struct ath10k *ar,
 				   ar->wmi.cmd->vdev_install_key_cmdid);
 }
 
+int ath10k_wmi_vdev_spectral_conf(struct ath10k *ar,
+				  const struct wmi_vdev_spectral_conf_arg *arg)
+{
+	struct wmi_vdev_spectral_conf_cmd *cmd;
+	struct sk_buff *skb;
+	u32 cmdid;
+
+	skb = ath10k_wmi_alloc_skb(sizeof(*cmd));
+	if (!skb)
+		return -ENOMEM;
+
+	cmd = (struct wmi_vdev_spectral_conf_cmd *)skb->data;
+	cmd->vdev_id              = __cpu_to_le32(arg->vdev_id);
+	cmd->scan_count           = __cpu_to_le32(arg->scan_count);
+	cmd->scan_period          = __cpu_to_le32(arg->scan_period);
+	cmd->scan_priority        = __cpu_to_le32(arg->scan_priority);
+	cmd->scan_fft_size        = __cpu_to_le32(arg->scan_fft_size);
+	cmd->scan_gc_ena          = __cpu_to_le32(arg->scan_gc_ena);
+	cmd->scan_restart_ena     = __cpu_to_le32(arg->scan_restart_ena);
+	cmd->scan_noise_floor_ref = __cpu_to_le32(arg->scan_noise_floor_ref);
+	cmd->scan_init_delay      = __cpu_to_le32(arg->scan_init_delay);
+	cmd->scan_nb_tone_thr     = __cpu_to_le32(arg->scan_nb_tone_thr);
+	cmd->scan_str_bin_thr     = __cpu_to_le32(arg->scan_str_bin_thr);
+	cmd->scan_wb_rpt_mode     = __cpu_to_le32(arg->scan_wb_rpt_mode);
+	cmd->scan_rssi_rpt_mode   = __cpu_to_le32(arg->scan_rssi_rpt_mode);
+	cmd->scan_rssi_thr        = __cpu_to_le32(arg->scan_rssi_thr);
+	cmd->scan_pwr_format      = __cpu_to_le32(arg->scan_pwr_format);
+	cmd->scan_rpt_mode        = __cpu_to_le32(arg->scan_rpt_mode);
+	cmd->scan_bin_scale       = __cpu_to_le32(arg->scan_bin_scale);
+	cmd->scan_dbm_adj         = __cpu_to_le32(arg->scan_dbm_adj);
+	cmd->scan_chn_mask        = __cpu_to_le32(arg->scan_chn_mask);
+
+	cmdid = ar->wmi.cmd->vdev_spectral_scan_configure_cmdid;
+	return ath10k_wmi_cmd_send(ar, skb, cmdid);
+}
+
+int ath10k_wmi_vdev_spectral_enable(struct ath10k *ar, u32 vdev_id, u32 trigger,
+				    u32 enable)
+{
+	struct wmi_vdev_spectral_enable_cmd *cmd;
+	struct sk_buff *skb;
+	u32 cmdid;
+
+	skb = ath10k_wmi_alloc_skb(sizeof(*cmd));
+	if (!skb)
+		return -ENOMEM;
+
+	cmd = (struct wmi_vdev_spectral_enable_cmd *)skb->data;
+	cmd->vdev_id     = __cpu_to_le32(vdev_id);
+	cmd->trigger_cmd = __cpu_to_le32(trigger);
+	cmd->enable_cmd  = __cpu_to_le32(enable);
+
+	cmdid = ar->wmi.cmd->vdev_spectral_scan_enable_cmdid;
+	return ath10k_wmi_cmd_send(ar, skb, cmdid);
+}
+
 int ath10k_wmi_peer_create(struct ath10k *ar, u32 vdev_id,
 			   const u8 peer_addr[ETH_ALEN])
 {
diff --git a/drivers/net/wireless/ath/ath10k/wmi.h b/drivers/net/wireless/ath/ath10k/wmi.h
index 4fcc96a..da5cbda 100644
--- a/drivers/net/wireless/ath/ath10k/wmi.h
+++ b/drivers/net/wireless/ath/ath10k/wmi.h
@@ -2077,6 +2077,7 @@  struct wmi_comb_phyerr_rx_event {
 #define PHYERR_TLV_SIG				0xBB
 #define PHYERR_TLV_TAG_SEARCH_FFT_REPORT	0xFB
 #define PHYERR_TLV_TAG_RADAR_PULSE_SUMMARY	0xF8
+#define PHYERR_TLV_TAG_SPECTRAL_SUMMARY_REPORT	0xF9
 
 struct phyerr_radar_report {
 	__le32 reg0; /* RADAR_REPORT_REG0_* */
@@ -3381,6 +3382,95 @@  struct wmi_vdev_simple_event {
 /* unsupported VDEV combination */
 #define WMI_INIFIED_VDEV_START_RESPONSE_NOT_SUPPORTED	0x2
 
+/* TODO: please add more comments if you have in-depth information */
+struct wmi_vdev_spectral_conf_cmd {
+	__le32 vdev_id;
+	/* number of fft samples to send (0 for infinite) */
+	__le32 scan_count;
+	__le32 scan_period;
+	__le32 scan_priority;
+	/* number of bins in the FFT: 2^(fft_size - bin_scale) */
+	__le32 scan_fft_size;
+	__le32 scan_gc_ena;
+	__le32 scan_restart_ena;
+	__le32 scan_noise_floor_ref;
+	__le32 scan_init_delay;
+	__le32 scan_nb_tone_thr;
+	__le32 scan_str_bin_thr;
+	__le32 scan_wb_rpt_mode;
+	__le32 scan_rssi_rpt_mode;
+	__le32 scan_rssi_thr;
+	__le32 scan_pwr_format;
+	/* rpt_mode: Format of FFT report to software for spectral scan
+	 * triggered FFTs:
+	 *	0: No FFT report (only spectral scan summary report)
+	 *	1: 2-dword summary of metrics for each completed FFT + spectral
+	 *	   scan	summary report
+	 *	2: 2-dword summary of metrics for each completed FFT +
+	 *	   1x- oversampled bins(in-band) per FFT + spectral scan summary
+	 *	   report
+	 *	3: 2-dword summary of metrics for each completed FFT +
+	 *	   2x- oversampled bins	(all) per FFT + spectral scan summary
+	 */
+	__le32 scan_rpt_mode;
+	__le32 scan_bin_scale;
+	__le32 scan_dbm_adj;
+	__le32 scan_chn_mask;
+} __packed;
+
+struct wmi_vdev_spectral_conf_arg {
+	u32 vdev_id;
+	u32 scan_count;
+	u32 scan_period;
+	u32 scan_priority;
+	u32 scan_fft_size;
+	u32 scan_gc_ena;
+	u32 scan_restart_ena;
+	u32 scan_noise_floor_ref;
+	u32 scan_init_delay;
+	u32 scan_nb_tone_thr;
+	u32 scan_str_bin_thr;
+	u32 scan_wb_rpt_mode;
+	u32 scan_rssi_rpt_mode;
+	u32 scan_rssi_thr;
+	u32 scan_pwr_format;
+	u32 scan_rpt_mode;
+	u32 scan_bin_scale;
+	u32 scan_dbm_adj;
+	u32 scan_chn_mask;
+};
+
+#define WMI_SPECTRAL_ENABLE_DEFAULT              0
+#define WMI_SPECTRAL_COUNT_DEFAULT               0
+#define WMI_SPECTRAL_PERIOD_DEFAULT             35
+#define WMI_SPECTRAL_PRIORITY_DEFAULT            1
+#define WMI_SPECTRAL_FFT_SIZE_DEFAULT            7
+#define WMI_SPECTRAL_GC_ENA_DEFAULT              1
+#define WMI_SPECTRAL_RESTART_ENA_DEFAULT         0
+#define WMI_SPECTRAL_NOISE_FLOOR_REF_DEFAULT   -96
+#define WMI_SPECTRAL_INIT_DELAY_DEFAULT         80
+#define WMI_SPECTRAL_NB_TONE_THR_DEFAULT        12
+#define WMI_SPECTRAL_STR_BIN_THR_DEFAULT         8
+#define WMI_SPECTRAL_WB_RPT_MODE_DEFAULT         0
+#define WMI_SPECTRAL_RSSI_RPT_MODE_DEFAULT       0
+#define WMI_SPECTRAL_RSSI_THR_DEFAULT         0xf0
+#define WMI_SPECTRAL_PWR_FORMAT_DEFAULT          0
+#define WMI_SPECTRAL_RPT_MODE_DEFAULT            2
+#define WMI_SPECTRAL_BIN_SCALE_DEFAULT           1
+#define WMI_SPECTRAL_DBM_ADJ_DEFAULT             1
+#define WMI_SPECTRAL_CHN_MASK_DEFAULT            1
+
+struct wmi_vdev_spectral_enable_cmd {
+	__le32 vdev_id;
+	__le32 trigger_cmd;
+	__le32 enable_cmd;
+} __packed;
+
+#define WMI_SPECTRAL_TRIGGER_CMD_TRIGGER  1
+#define WMI_SPECTRAL_TRIGGER_CMD_CLEAR    2
+#define WMI_SPECTRAL_ENABLE_CMD_ENABLE    1
+#define WMI_SPECTRAL_ENABLE_CMD_DISABLE   2
+
 /* Beacon processing related command and event structures */
 struct wmi_bcn_tx_hdr {
 	__le32 vdev_id;
@@ -4226,6 +4316,10 @@  int ath10k_wmi_vdev_set_param(struct ath10k *ar, u32 vdev_id,
 			      u32 param_id, u32 param_value);
 int ath10k_wmi_vdev_install_key(struct ath10k *ar,
 				const struct wmi_vdev_install_key_arg *arg);
+int ath10k_wmi_vdev_spectral_conf(struct ath10k *ar,
+				  const struct wmi_vdev_spectral_conf_arg *arg);
+int ath10k_wmi_vdev_spectral_enable(struct ath10k *ar, u32 vdev_id, u32 trigger,
+				    u32 enable);
 int ath10k_wmi_peer_create(struct ath10k *ar, u32 vdev_id,
 		    const u8 peer_addr[ETH_ALEN]);
 int ath10k_wmi_peer_delete(struct ath10k *ar, u32 vdev_id,
diff --git a/drivers/net/wireless/ath/spectral_common.h b/drivers/net/wireless/ath/spectral_common.h
index b9ab722..0827cb7 100644
--- a/drivers/net/wireless/ath/spectral_common.h
+++ b/drivers/net/wireless/ath/spectral_common.h
@@ -19,6 +19,10 @@ 
 
 #define SPECTRAL_HT20_NUM_BINS		56
 #define SPECTRAL_HT20_40_NUM_BINS		128
+/* TODO: could possibly be 512, but no samples this large
+ * could be acquired so far.
+ */
+#define SPECTRAL_ATH10K_MAX_NUM_BINS		256
 
 /* FFT sample format given to userspace via debugfs.
  *
@@ -31,6 +35,7 @@ 
 enum ath_fft_sample_type {
 	ATH_FFT_SAMPLE_HT20 = 1,
 	ATH_FFT_SAMPLE_HT20_40,
+	ATH_FFT_SAMPLE_ATH10K,
 };
 
 struct fft_sample_tlv {
@@ -85,4 +90,23 @@  struct fft_sample_ht20_40 {
 	u8 data[SPECTRAL_HT20_40_NUM_BINS];
 } __packed;
 
+struct fft_sample_ath10k {
+	struct fft_sample_tlv tlv;
+	u8 chan_width_mhz;
+	__be16 freq1;
+	__be16 freq2;
+	__be16 noise;
+	__be16 max_magnitude;
+	__be16 total_gain_db;
+	__be16 base_pwr_db;
+	__be64 tsf;
+	s8 max_index;
+	u8 rssi;
+	u8 relpwr_db;
+	u8 avgpwr_db;
+	u8 max_exp;
+
+	u8 data[0];
+} __packed;
+
 #endif /* SPECTRAL_COMMON_H */