Commit e93d083f authored by Simon Wunderlich's avatar Simon Wunderlich Committed by John W. Linville

ath9k: add spectral scan feature

Adds the spectral scan feature for ath9k. AR92xx and AR93xx chips
are supported for now. 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:

echo chanscan > /sys/kernel/debug/ieee80211/phy0/ath9k/spectral_scan_ctl
iw dev wlan0 scan
cat /sys/kernel/debug/ieee80211/phy0/ath9k/spectral_scan0 > samples
echo disable > /sys/kernel/debug/ieee80211/phy0/ath9k/spectral_scan_ctl

This feature is still experimental.

The special "chanscan" mode is used to perform spectral scan while
mac80211 is scanning for channels. To allow this,
sw_scan_start/complete() ops have been added.

The patch contains code snippets and information from Zefir Kurtisi and
information provided by Adrian Chadd and Felix Fietkau.
Signed-off-by: default avatarSimon Wunderlich <siwu@hrz.tu-chemnitz.de>
Signed-off-by: default avatarMathias Kretschmer <mathias.kretschmer@fokus.fraunhofer.de>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 1a26cda8
...@@ -555,6 +555,67 @@ static void ar9002_hw_antdiv_comb_conf_set(struct ath_hw *ah, ...@@ -555,6 +555,67 @@ static void ar9002_hw_antdiv_comb_conf_set(struct ath_hw *ah,
REG_WRITE(ah, AR_PHY_MULTICHAIN_GAIN_CTL, regval); REG_WRITE(ah, AR_PHY_MULTICHAIN_GAIN_CTL, regval);
} }
void ar9002_hw_spectral_scan_config(struct ath_hw *ah,
struct ath_spec_scan *param)
{
u8 count;
if (!param->enabled) {
REG_CLR_BIT(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_ENABLE);
return;
}
REG_SET_BIT(ah, AR_PHY_RADAR_0, AR_PHY_RADAR_0_FFT_ENA);
REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN, AR_PHY_SPECTRAL_SCAN_ENABLE);
if (param->short_repeat)
REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_SHORT_REPEAT);
else
REG_CLR_BIT(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_SHORT_REPEAT);
/* on AR92xx, the highest bit of count will make the the chip send
* spectral samples endlessly. Check if this really was intended,
* and fix otherwise.
*/
count = param->count;
if (param->endless)
count = 0;
else if (count & 0x80)
count = 0x7f;
REG_RMW_FIELD(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_COUNT, count);
REG_RMW_FIELD(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_PERIOD, param->period);
REG_RMW_FIELD(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_FFT_PERIOD, param->fft_period);
return;
}
static void ar9002_hw_spectral_scan_trigger(struct ath_hw *ah)
{
REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN, AR_PHY_SPECTRAL_SCAN_ENABLE);
/* Activate spectral scan */
REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_ACTIVE);
}
static void ar9002_hw_spectral_scan_wait(struct ath_hw *ah)
{
struct ath_common *common = ath9k_hw_common(ah);
/* Poll for spectral scan complete */
if (!ath9k_hw_wait(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_ACTIVE,
0, AH_WAIT_TIMEOUT)) {
ath_err(common, "spectral scan wait failed\n");
return;
}
}
void ar9002_hw_attach_phy_ops(struct ath_hw *ah) void ar9002_hw_attach_phy_ops(struct ath_hw *ah)
{ {
struct ath_hw_private_ops *priv_ops = ath9k_hw_private_ops(ah); struct ath_hw_private_ops *priv_ops = ath9k_hw_private_ops(ah);
...@@ -569,6 +630,9 @@ void ar9002_hw_attach_phy_ops(struct ath_hw *ah) ...@@ -569,6 +630,9 @@ void ar9002_hw_attach_phy_ops(struct ath_hw *ah)
ops->antdiv_comb_conf_get = ar9002_hw_antdiv_comb_conf_get; ops->antdiv_comb_conf_get = ar9002_hw_antdiv_comb_conf_get;
ops->antdiv_comb_conf_set = ar9002_hw_antdiv_comb_conf_set; ops->antdiv_comb_conf_set = ar9002_hw_antdiv_comb_conf_set;
ops->spectral_scan_config = ar9002_hw_spectral_scan_config;
ops->spectral_scan_trigger = ar9002_hw_spectral_scan_trigger;
ops->spectral_scan_wait = ar9002_hw_spectral_scan_wait;
ar9002_hw_set_nf_limits(ah); ar9002_hw_set_nf_limits(ah);
} }
...@@ -1453,6 +1453,67 @@ static int ar9003_hw_fast_chan_change(struct ath_hw *ah, ...@@ -1453,6 +1453,67 @@ static int ar9003_hw_fast_chan_change(struct ath_hw *ah,
return 0; return 0;
} }
static void ar9003_hw_spectral_scan_config(struct ath_hw *ah,
struct ath_spec_scan *param)
{
u8 count;
if (!param->enabled) {
REG_CLR_BIT(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_ENABLE);
return;
}
REG_SET_BIT(ah, AR_PHY_RADAR_0, AR_PHY_RADAR_0_FFT_ENA);
REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN, AR_PHY_SPECTRAL_SCAN_ENABLE);
/* on AR93xx and newer, count = 0 will make the the chip send
* spectral samples endlessly. Check if this really was intended,
* and fix otherwise.
*/
count = param->count;
if (param->endless)
count = 0;
else if (param->count == 0)
count = 1;
if (param->short_repeat)
REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_SHORT_REPEAT);
else
REG_CLR_BIT(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_SHORT_REPEAT);
REG_RMW_FIELD(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_COUNT, count);
REG_RMW_FIELD(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_PERIOD, param->period);
REG_RMW_FIELD(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_FFT_PERIOD, param->fft_period);
return;
}
static void ar9003_hw_spectral_scan_trigger(struct ath_hw *ah)
{
/* Activate spectral scan */
REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_ACTIVE);
}
static void ar9003_hw_spectral_scan_wait(struct ath_hw *ah)
{
struct ath_common *common = ath9k_hw_common(ah);
/* Poll for spectral scan complete */
if (!ath9k_hw_wait(ah, AR_PHY_SPECTRAL_SCAN,
AR_PHY_SPECTRAL_SCAN_ACTIVE,
0, AH_WAIT_TIMEOUT)) {
ath_err(common, "spectral scan wait failed\n");
return;
}
}
void ar9003_hw_attach_phy_ops(struct ath_hw *ah) void ar9003_hw_attach_phy_ops(struct ath_hw *ah)
{ {
struct ath_hw_private_ops *priv_ops = ath9k_hw_private_ops(ah); struct ath_hw_private_ops *priv_ops = ath9k_hw_private_ops(ah);
...@@ -1486,6 +1547,9 @@ void ar9003_hw_attach_phy_ops(struct ath_hw *ah) ...@@ -1486,6 +1547,9 @@ void ar9003_hw_attach_phy_ops(struct ath_hw *ah)
ops->antdiv_comb_conf_get = ar9003_hw_antdiv_comb_conf_get; ops->antdiv_comb_conf_get = ar9003_hw_antdiv_comb_conf_get;
ops->antdiv_comb_conf_set = ar9003_hw_antdiv_comb_conf_set; ops->antdiv_comb_conf_set = ar9003_hw_antdiv_comb_conf_set;
ops->antctrl_shared_chain_lnadiv = ar9003_hw_antctrl_shared_chain_lnadiv; ops->antctrl_shared_chain_lnadiv = ar9003_hw_antctrl_shared_chain_lnadiv;
ops->spectral_scan_config = ar9003_hw_spectral_scan_config;
ops->spectral_scan_trigger = ar9003_hw_spectral_scan_trigger;
ops->spectral_scan_wait = ar9003_hw_spectral_scan_wait;
ar9003_hw_set_nf_limits(ah); ar9003_hw_set_nf_limits(ah);
ar9003_hw_set_radar_conf(ah); ar9003_hw_set_radar_conf(ah);
......
...@@ -670,6 +670,23 @@ struct ath9k_vif_iter_data { ...@@ -670,6 +670,23 @@ struct ath9k_vif_iter_data {
int nadhocs; /* number of adhoc vifs */ int nadhocs; /* number of adhoc vifs */
}; };
/* enum 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.
* @SPECTRAL_CHANSCAN: Like manual, but also triggered when changing channels
* during a channel scan.
*/
enum spectral_mode {
SPECTRAL_DISABLED = 0,
SPECTRAL_BACKGROUND,
SPECTRAL_MANUAL,
SPECTRAL_CHANSCAN,
};
struct ath_softc { struct ath_softc {
struct ieee80211_hw *hw; struct ieee80211_hw *hw;
struct device *dev; struct device *dev;
...@@ -738,6 +755,10 @@ struct ath_softc { ...@@ -738,6 +755,10 @@ struct ath_softc {
u8 ant_tx, ant_rx; u8 ant_tx, ant_rx;
struct dfs_pattern_detector *dfs_detector; struct dfs_pattern_detector *dfs_detector;
u32 wow_enabled; u32 wow_enabled;
/* relay(fs) channel for spectral scan */
struct rchan *rfs_chan_spec_scan;
enum spectral_mode spectral_mode;
int scanning;
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
atomic_t wow_got_bmiss_intr; atomic_t wow_got_bmiss_intr;
...@@ -746,6 +767,133 @@ struct ath_softc { ...@@ -746,6 +767,133 @@ struct ath_softc {
#endif #endif
}; };
#define SPECTRAL_SCAN_BITMASK 0x10
/* Radar info packet format, used for DFS and spectral formats. */
struct ath_radar_info {
u8 pulse_length_pri;
u8 pulse_length_ext;
u8 pulse_bw_info;
} __packed;
/* The HT20 spectral data has 4 bytes of additional information at it's end.
*
* [7:0]: all bins {max_magnitude[1:0], bitmap_weight[5:0]}
* [7:0]: all bins max_magnitude[9:2]
* [7:0]: all bins {max_index[5:0], max_magnitude[11:10]}
* [3:0]: max_exp (shift amount to size max bin to 8-bit unsigned)
*/
struct ath_ht20_mag_info {
u8 all_bins[3];
u8 max_exp;
} __packed;
#define SPECTRAL_HT20_NUM_BINS 56
/* WARNING: don't actually use this struct! MAC may vary the amount of
* data by -1/+2. This struct is for reference only.
*/
struct ath_ht20_fft_packet {
u8 data[SPECTRAL_HT20_NUM_BINS];
struct ath_ht20_mag_info mag_info;
struct ath_radar_info radar_info;
} __packed;
#define SPECTRAL_HT20_TOTAL_DATA_LEN (sizeof(struct ath_ht20_fft_packet))
/* Dynamic 20/40 mode:
*
* [7:0]: lower bins {max_magnitude[1:0], bitmap_weight[5:0]}
* [7:0]: lower bins max_magnitude[9:2]
* [7:0]: lower bins {max_index[5:0], max_magnitude[11:10]}
* [7:0]: upper bins {max_magnitude[1:0], bitmap_weight[5:0]}
* [7:0]: upper bins max_magnitude[9:2]
* [7:0]: upper bins {max_index[5:0], max_magnitude[11:10]}
* [3:0]: max_exp (shift amount to size max bin to 8-bit unsigned)
*/
struct ath_ht20_40_mag_info {
u8 lower_bins[3];
u8 upper_bins[3];
u8 max_exp;
} __packed;
#define SPECTRAL_HT20_40_NUM_BINS 128
/* WARNING: don't actually use this struct! MAC may vary the amount of
* data. This struct is for reference only.
*/
struct ath_ht20_40_fft_packet {
u8 data[SPECTRAL_HT20_40_NUM_BINS];
struct ath_ht20_40_mag_info mag_info;
struct ath_radar_info radar_info;
} __packed;
#define SPECTRAL_HT20_40_TOTAL_DATA_LEN (sizeof(struct ath_ht20_40_fft_packet))
/* grabs the max magnitude from the all/upper/lower bins */
static inline u16 spectral_max_magnitude(u8 *bins)
{
return (bins[0] & 0xc0) >> 6 |
(bins[1] & 0xff) << 2 |
(bins[2] & 0x03) << 10;
}
/* return the max magnitude from the all/upper/lower bins */
static inline u8 spectral_max_index(u8 *bins)
{
s8 m = (bins[2] & 0xfc) >> 2;
/* TODO: this still doesn't always report the right values ... */
if (m > 32)
m |= 0xe0;
else
m &= ~0xe0;
return m + 29;
}
/* return the bitmap weight from the all/upper/lower bins */
static inline u8 spectral_bitmap_weight(u8 *bins)
{
return bins[0] & 0x3f;
}
/* FFT sample format given to userspace via debugfs.
*
* Please keep the type/length at the front position and change
* other fields after adding another sample type
*
* TODO: this might need rework when switching to nl80211-based
* interface.
*/
enum ath_fft_sample_type {
ATH_FFT_SAMPLE_HT20 = 0,
};
struct fft_sample_tlv {
u8 type; /* see ath_fft_sample */
u16 length;
/* type dependent data follows */
} __packed;
struct fft_sample_ht20 {
struct fft_sample_tlv tlv;
u8 __alignment;
u16 freq;
s8 rssi;
s8 noise;
u16 max_magnitude;
u8 max_index;
u8 bitmap_weight;
u64 tsf;
u16 data[SPECTRAL_HT20_NUM_BINS];
} __packed;
void ath9k_tasklet(unsigned long data); void ath9k_tasklet(unsigned long data);
int ath_cabq_update(struct ath_softc *); int ath_cabq_update(struct ath_softc *);
...@@ -768,6 +916,10 @@ void ath9k_set_hw_capab(struct ath_softc *sc, struct ieee80211_hw *hw); ...@@ -768,6 +916,10 @@ void ath9k_set_hw_capab(struct ath_softc *sc, struct ieee80211_hw *hw);
void ath9k_reload_chainmask_settings(struct ath_softc *sc); void ath9k_reload_chainmask_settings(struct ath_softc *sc);
bool ath9k_uses_beacons(int type); bool ath9k_uses_beacons(int type);
void ath9k_spectral_scan_trigger(struct ieee80211_hw *hw);
int ath9k_spectral_scan_config(struct ieee80211_hw *hw,
enum spectral_mode spectral_mode);
#ifdef CONFIG_ATH9K_PCI #ifdef CONFIG_ATH9K_PCI
int ath_pci_init(void); int ath_pci_init(void);
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/relay.h>
#include <asm/unaligned.h> #include <asm/unaligned.h>
#include "ath9k.h" #include "ath9k.h"
...@@ -966,6 +967,112 @@ static const struct file_operations fops_recv = { ...@@ -966,6 +967,112 @@ static const struct file_operations fops_recv = {
.llseek = default_llseek, .llseek = default_llseek,
}; };
static ssize_t read_file_spec_scan_ctl(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath_softc *sc = file->private_data;
char *mode = "";
unsigned int len;
switch (sc->spectral_mode) {
case SPECTRAL_DISABLED:
mode = "disable";
break;
case SPECTRAL_BACKGROUND:
mode = "background";
break;
case SPECTRAL_CHANSCAN:
mode = "chanscan";
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 ath_softc *sc = file->private_data;
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
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 (strncmp("trigger", buf, 7) == 0) {
ath9k_spectral_scan_trigger(sc->hw);
} else if (strncmp("background", buf, 9) == 0) {
ath9k_spectral_scan_config(sc->hw, SPECTRAL_BACKGROUND);
ath_dbg(common, CONFIG, "spectral scan: background mode enabled\n");
} else if (strncmp("chanscan", buf, 8) == 0) {
ath9k_spectral_scan_config(sc->hw, SPECTRAL_CHANSCAN);
ath_dbg(common, CONFIG, "spectral scan: channel scan mode enabled\n");
} else if (strncmp("manual", buf, 6) == 0) {
ath9k_spectral_scan_config(sc->hw, SPECTRAL_MANUAL);
ath_dbg(common, CONFIG, "spectral scan: manual mode enabled\n");
} else if (strncmp("disable", buf, 7) == 0) {
ath9k_spectral_scan_config(sc->hw, SPECTRAL_DISABLED);
ath_dbg(common, CONFIG, "spectral scan: disabled\n");
} else {
return -EINVAL;
}
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,
};
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;
}
void ath_debug_send_fft_sample(struct ath_softc *sc,
struct fft_sample_tlv *fft_sample_tlv)
{
if (!sc->rfs_chan_spec_scan)
return;
relay_write(sc->rfs_chan_spec_scan, fft_sample_tlv,
fft_sample_tlv->length + sizeof(*fft_sample_tlv));
}
static struct rchan_callbacks rfs_spec_scan_cb = {
.create_buf_file = create_buf_file_handler,
.remove_buf_file = remove_buf_file_handler,
};
static ssize_t read_file_regidx(struct file *file, char __user *user_buf, static ssize_t read_file_regidx(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos) size_t count, loff_t *ppos)
{ {
...@@ -1780,6 +1887,14 @@ int ath9k_init_debug(struct ath_hw *ah) ...@@ -1780,6 +1887,14 @@ int ath9k_init_debug(struct ath_hw *ah)
&fops_base_eeprom); &fops_base_eeprom);
debugfs_create_file("modal_eeprom", S_IRUSR, sc->debug.debugfs_phy, sc, debugfs_create_file("modal_eeprom", S_IRUSR, sc->debug.debugfs_phy, sc,
&fops_modal_eeprom); &fops_modal_eeprom);
sc->rfs_chan_spec_scan = relay_open("spectral_scan",
sc->debug.debugfs_phy,
262144, 4, &rfs_spec_scan_cb,
NULL);
debugfs_create_file("spectral_scan_ctl", S_IRUSR | S_IWUSR,
sc->debug.debugfs_phy, sc,
&fops_spec_scan_ctl);
#ifdef CONFIG_ATH9K_MAC_DEBUG #ifdef CONFIG_ATH9K_MAC_DEBUG
debugfs_create_file("samples", S_IRUSR, sc->debug.debugfs_phy, sc, debugfs_create_file("samples", S_IRUSR, sc->debug.debugfs_phy, sc,
&fops_samps); &fops_samps);
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
struct ath_txq; struct ath_txq;
struct ath_buf; struct ath_buf;
struct fft_sample_tlv;
#ifdef CONFIG_ATH9K_DEBUGFS #ifdef CONFIG_ATH9K_DEBUGFS
#define TX_STAT_INC(q, c) sc->debug.stats.txstats[q].c++ #define TX_STAT_INC(q, c) sc->debug.stats.txstats[q].c++
...@@ -323,6 +324,10 @@ void ath9k_sta_remove_debugfs(struct ieee80211_hw *hw, ...@@ -323,6 +324,10 @@ void ath9k_sta_remove_debugfs(struct ieee80211_hw *hw,
struct ieee80211_vif *vif, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, struct ieee80211_sta *sta,
struct dentry *dir); struct dentry *dir);
void ath_debug_send_fft_sample(struct ath_softc *sc,
struct fft_sample_tlv *fft_sample);
#else #else
#define RX_STAT_INC(c) /* NOP */ #define RX_STAT_INC(c) /* NOP */
......
...@@ -657,6 +657,37 @@ struct ath_hw_private_ops { ...@@ -657,6 +657,37 @@ struct ath_hw_private_ops {
void (*ani_cache_ini_regs)(struct ath_hw *ah); void (*ani_cache_ini_regs)(struct ath_hw *ah);
}; };
/**
* struct ath_spec_scan - parameters for Atheros spectral scan
*
* @enabled: enable/disable spectral scan
* @short_repeat: controls whether the chip is in spectral scan mode
* for 4 usec (enabled) or 204 usec (disabled)
* @count: number of scan results requested. There are special meanings
* in some chip revisions:
* AR92xx: highest bit set (>=128) for endless mode
* (spectral scan won't stopped until explicitly disabled)
* AR9300 and newer: 0 for endless mode
* @endless: true if endless mode is intended. Otherwise, count value is
* corrected to the next possible value.
* @period: time duration between successive spectral scan entry points
* (period*256*Tclk). Tclk = ath_common->clockrate
* @fft_period: PHY passes FFT frames to MAC every (fft_period+1)*4uS
*
* Note: Tclk = 40MHz or 44MHz depending upon operating mode.
* Typically it's 44MHz in 2/5GHz on later chips, but there's
* a "fast clock" check for this in 5GHz.
*
*/
struct ath_spec_scan {
bool enabled;
bool short_repeat;
bool endless;
u8 count;
u8 period;
u8 fft_period;
};
/** /**
* struct ath_hw_ops - callbacks used by hardware code and driver code * struct ath_hw_ops - callbacks used by hardware code and driver code
* *
...@@ -665,6 +696,10 @@ struct ath_hw_private_ops { ...@@ -665,6 +696,10 @@ struct ath_hw_private_ops {
* *
* @config_pci_powersave: * @config_pci_powersave:
* @calibrate: periodic calibration for NF, ANI, IQ, ADC gain, ADC-DC * @calibrate: periodic calibration for NF, ANI, IQ, ADC gain, ADC-DC
*
* @spectral_scan_config: set parameters for spectral scan and enable/disable it
* @spectral_scan_trigger: trigger a spectral scan run
* @spectral_scan_wait: wait for a spectral scan run to finish
*/ */
struct ath_hw_ops { struct ath_hw_ops {
void (*config_pci_powersave)(struct ath_hw *ah, void (*config_pci_powersave)(struct ath_hw *ah,
...@@ -685,6 +720,10 @@ struct ath_hw_ops { ...@@ -685,6 +720,10 @@ struct ath_hw_ops {
void (*antdiv_comb_conf_set)(struct ath_hw *ah, void (*antdiv_comb_conf_set)(struct ath_hw *ah,
struct ath_hw_antcomb_conf *antconf); struct ath_hw_antcomb_conf *antconf);
void (*antctrl_shared_chain_lnadiv)(struct ath_hw *hw, bool enable); void (*antctrl_shared_chain_lnadiv)(struct ath_hw *hw, bool enable);
void (*spectral_scan_config)(struct ath_hw *ah,
struct ath_spec_scan *param);
void (*spectral_scan_trigger)(struct ath_hw *ah);
void (*spectral_scan_wait)(struct ath_hw *ah);
}; };
struct ath_nf_limits { struct ath_nf_limits {
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/ath9k_platform.h> #include <linux/ath9k_platform.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/relay.h>
#include "ath9k.h" #include "ath9k.h"
...@@ -916,6 +917,11 @@ static void ath9k_deinit_softc(struct ath_softc *sc) ...@@ -916,6 +917,11 @@ static void ath9k_deinit_softc(struct ath_softc *sc)
sc->dfs_detector->exit(sc->dfs_detector); sc->dfs_detector->exit(sc->dfs_detector);
ath9k_eeprom_release(sc); ath9k_eeprom_release(sc);
if (sc->rfs_chan_spec_scan) {
relay_close(sc->rfs_chan_spec_scan);
sc->rfs_chan_spec_scan = NULL;
}
} }
void ath9k_deinit_device(struct ath_softc *sc) void ath9k_deinit_device(struct ath_softc *sc)
......
...@@ -226,7 +226,8 @@ enum ath9k_phyerr { ...@@ -226,7 +226,8 @@ enum ath9k_phyerr {
ATH9K_PHYERR_HT_LENGTH_ILLEGAL = 35, ATH9K_PHYERR_HT_LENGTH_ILLEGAL = 35,
ATH9K_PHYERR_HT_RATE_ILLEGAL = 36, ATH9K_PHYERR_HT_RATE_ILLEGAL = 36,
ATH9K_PHYERR_MAX = 37, ATH9K_PHYERR_SPECTRAL = 38,
ATH9K_PHYERR_MAX = 39,
}; };
struct ath_desc { struct ath_desc {
......
...@@ -1075,6 +1075,86 @@ static void ath9k_disable_ps(struct ath_softc *sc) ...@@ -1075,6 +1075,86 @@ static void ath9k_disable_ps(struct ath_softc *sc)
ath_dbg(common, PS, "PowerSave disabled\n"); ath_dbg(common, PS, "PowerSave disabled\n");
} }
void ath9k_spectral_scan_trigger(struct ieee80211_hw *hw)
{
struct ath_softc *sc = hw->priv;
struct ath_hw *ah = sc->sc_ah;
struct ath_common *common = ath9k_hw_common(ah);
u32 rxfilter;
if (!ath9k_hw_ops(ah)->spectral_scan_trigger) {
ath_err(common, "spectrum analyzer not implemented on this hardware\n");
return;
}
ath9k_ps_wakeup(sc);
rxfilter = ath9k_hw_getrxfilter(ah);
ath9k_hw_setrxfilter(ah, rxfilter |
ATH9K_RX_FILTER_PHYRADAR |
ATH9K_RX_FILTER_PHYERR);
/* TODO: usually this should not be neccesary, but for some reason
* (or in some mode?) the trigger must be called after the
* configuration, otherwise the register will have its values reset
* (on my ar9220 to value 0x01002310)
*/
ath9k_spectral_scan_config(hw, sc->spectral_mode);
ath9k_hw_ops(ah)->spectral_scan_trigger(ah);
ath9k_ps_restore(sc);
}
int ath9k_spectral_scan_config(struct ieee80211_hw *hw,
enum spectral_mode spectral_mode)
{
struct ath_softc *sc = hw->priv;
struct ath_hw *ah = sc->sc_ah;
struct ath_common *common = ath9k_hw_common(ah);
struct ath_spec_scan param;
if (!ath9k_hw_ops(ah)->spectral_scan_trigger) {
ath_err(common, "spectrum analyzer not implemented on this hardware\n");
return -1;
}
/* NOTE: this will generate a few samples ...
*
* TODO: review default parameters, and/or define an interface to set
* them.
*/
param.enabled = 1;
param.short_repeat = true;
param.count = 8;
param.endless = false;
param.period = 0xFF;
param.fft_period = 0xF;
switch (spectral_mode) {
case SPECTRAL_DISABLED:
param.enabled = 0;
break;
case SPECTRAL_BACKGROUND:
/* send endless samples.
* TODO: is this really useful for "background"?
*/
param.endless = 1;
break;
case SPECTRAL_CHANSCAN:
break;
case SPECTRAL_MANUAL:
break;
default:
return -1;
}
ath9k_ps_wakeup(sc);
ath9k_hw_ops(ah)->spectral_scan_config(ah, &param);
ath9k_ps_restore(sc);
sc->spectral_mode = spectral_mode;
return 0;
}
static int ath9k_config(struct ieee80211_hw *hw, u32 changed) static int ath9k_config(struct ieee80211_hw *hw, u32 changed)
{ {
struct ath_softc *sc = hw->priv; struct ath_softc *sc = hw->priv;
...@@ -1188,6 +1268,11 @@ static int ath9k_config(struct ieee80211_hw *hw, u32 changed) ...@@ -1188,6 +1268,11 @@ static int ath9k_config(struct ieee80211_hw *hw, u32 changed)
*/ */
if (old_pos >= 0) if (old_pos >= 0)
ath_update_survey_nf(sc, old_pos); ath_update_survey_nf(sc, old_pos);
/* perform spectral scan if requested. */
if (sc->scanning && sc->spectral_mode == SPECTRAL_CHANSCAN)
ath9k_spectral_scan_trigger(hw);
} }
if (changed & IEEE80211_CONF_CHANGE_POWER) { if (changed & IEEE80211_CONF_CHANGE_POWER) {
...@@ -2240,6 +2325,19 @@ static void ath9k_set_wakeup(struct ieee80211_hw *hw, bool enabled) ...@@ -2240,6 +2325,19 @@ static void ath9k_set_wakeup(struct ieee80211_hw *hw, bool enabled)
} }
#endif #endif
static void ath9k_sw_scan_start(struct ieee80211_hw *hw)
{
struct ath_softc *sc = hw->priv;
sc->scanning = 1;
}
static void ath9k_sw_scan_complete(struct ieee80211_hw *hw)
{
struct ath_softc *sc = hw->priv;
sc->scanning = 0;
}
struct ieee80211_ops ath9k_ops = { struct ieee80211_ops ath9k_ops = {
.tx = ath9k_tx, .tx = ath9k_tx,
...@@ -2286,4 +2384,6 @@ struct ieee80211_ops ath9k_ops = { ...@@ -2286,4 +2384,6 @@ struct ieee80211_ops ath9k_ops = {
.sta_add_debugfs = ath9k_sta_add_debugfs, .sta_add_debugfs = ath9k_sta_add_debugfs,
.sta_remove_debugfs = ath9k_sta_remove_debugfs, .sta_remove_debugfs = ath9k_sta_remove_debugfs,
#endif #endif
.sw_scan_start = ath9k_sw_scan_start,
.sw_scan_complete = ath9k_sw_scan_complete,
}; };
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
#include <linux/relay.h>
#include "ath9k.h" #include "ath9k.h"
#include "ar9003_mac.h" #include "ar9003_mac.h"
...@@ -1025,6 +1026,108 @@ static void ath9k_rx_skb_postprocess(struct ath_common *common, ...@@ -1025,6 +1026,108 @@ static void ath9k_rx_skb_postprocess(struct ath_common *common,
rxs->flag &= ~RX_FLAG_DECRYPTED; rxs->flag &= ~RX_FLAG_DECRYPTED;
} }
static s8 fix_rssi_inv_only(u8 rssi_val)
{
if (rssi_val == 128)
rssi_val = 0;
return (s8) rssi_val;
}
static void ath_process_fft(struct ath_softc *sc, struct ieee80211_hdr *hdr,
struct ath_rx_status *rs, u64 tsf)
{
#ifdef CONFIG_ATH_DEBUG
struct ath_hw *ah = sc->sc_ah;
u8 bins[SPECTRAL_HT20_NUM_BINS];
u8 *vdata = (u8 *)hdr;
struct fft_sample_ht20 fft_sample;
struct ath_radar_info *radar_info;
struct ath_ht20_mag_info *mag_info;
int len = rs->rs_datalen;
int i, dc_pos;
/* AR9280 and before report via ATH9K_PHYERR_RADAR, AR93xx and newer
* via ATH9K_PHYERR_SPECTRAL. Haven't seen ATH9K_PHYERR_FALSE_RADAR_EXT
* yet, but this is supposed to be possible as well.
*/
if (rs->rs_phyerr != ATH9K_PHYERR_RADAR &&
rs->rs_phyerr != ATH9K_PHYERR_FALSE_RADAR_EXT &&
rs->rs_phyerr != ATH9K_PHYERR_SPECTRAL)
return;
/* Variation in the data length is possible and will be fixed later.
* Note that we only support HT20 for now.
*
* TODO: add HT20_40 support as well.
*/
if ((len > SPECTRAL_HT20_TOTAL_DATA_LEN + 2) ||
(len < SPECTRAL_HT20_TOTAL_DATA_LEN - 1))
return;
/* check if spectral scan bit is set. This does not have to be checked
* if received through a SPECTRAL phy error, but shouldn't hurt.
*/
radar_info = ((struct ath_radar_info *)&vdata[len]) - 1;
if (!(radar_info->pulse_bw_info & SPECTRAL_SCAN_BITMASK))
return;
fft_sample.tlv.type = ATH_FFT_SAMPLE_HT20;
fft_sample.tlv.length = sizeof(fft_sample) - sizeof(fft_sample.tlv);
fft_sample.freq = ah->curchan->chan->center_freq;
fft_sample.rssi = fix_rssi_inv_only(rs->rs_rssi_ctl0);
fft_sample.noise = ah->noise;
switch (len - SPECTRAL_HT20_TOTAL_DATA_LEN) {
case 0:
/* length correct, nothing to do. */
memcpy(bins, vdata, SPECTRAL_HT20_NUM_BINS);
break;
case -1:
/* first byte missing, duplicate it. */
memcpy(&bins[1], vdata, SPECTRAL_HT20_NUM_BINS - 1);
bins[0] = vdata[0];
break;
case 2:
/* MAC added 2 extra bytes at bin 30 and 32, remove them. */
memcpy(bins, vdata, 30);
bins[30] = vdata[31];
memcpy(&bins[31], &vdata[33], SPECTRAL_HT20_NUM_BINS - 31);
break;
case 1:
/* MAC added 2 extra bytes AND first byte is missing. */
bins[0] = vdata[0];
memcpy(&bins[0], vdata, 30);
bins[31] = vdata[31];
memcpy(&bins[32], &vdata[33], SPECTRAL_HT20_NUM_BINS - 32);
break;
default:
return;
}
/* DC value (value in the middle) is the blind spot of the spectral
* sample and invalid, interpolate it.
*/
dc_pos = SPECTRAL_HT20_NUM_BINS / 2;
bins[dc_pos] = (bins[dc_pos + 1] + bins[dc_pos - 1]) / 2;
/* mag data is at the end of the frame, in front of radar_info */
mag_info = ((struct ath_ht20_mag_info *)radar_info) - 1;
/* Apply exponent and grab further auxiliary information. */
for (i = 0; i < SPECTRAL_HT20_NUM_BINS; i++)
fft_sample.data[i] = bins[i] << mag_info->max_exp;
fft_sample.max_magnitude = spectral_max_magnitude(mag_info->all_bins);
fft_sample.max_index = spectral_max_index(mag_info->all_bins);
fft_sample.bitmap_weight = spectral_bitmap_weight(mag_info->all_bins);
fft_sample.tsf = tsf;
ath_debug_send_fft_sample(sc, &fft_sample.tlv);
#endif
}
int ath_rx_tasklet(struct ath_softc *sc, int flush, bool hp) int ath_rx_tasklet(struct ath_softc *sc, int flush, bool hp)
{ {
struct ath_buf *bf; struct ath_buf *bf;
...@@ -1122,6 +1225,9 @@ int ath_rx_tasklet(struct ath_softc *sc, int flush, bool hp) ...@@ -1122,6 +1225,9 @@ int ath_rx_tasklet(struct ath_softc *sc, int flush, bool hp)
unlikely(tsf_lower - rs.rs_tstamp > 0x10000000)) unlikely(tsf_lower - rs.rs_tstamp > 0x10000000))
rxs->mactime += 0x100000000ULL; rxs->mactime += 0x100000000ULL;
if ((rs.rs_status & ATH9K_RXERR_PHY))
ath_process_fft(sc, hdr, &rs, rxs->mactime);
retval = ath9k_rx_skb_preprocess(common, hw, hdr, &rs, retval = ath9k_rx_skb_preprocess(common, hw, hdr, &rs,
rxs, &decrypt_error); rxs, &decrypt_error);
if (retval) if (retval)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment