2014-08-02 10:12:54 +04:00
/*
* 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 send_fft_sample ( struct ath10k * ar ,
const struct fft_sample_tlv * fft_sample_tlv )
{
int length ;
if ( ! ar - > spectral . rfs_chan_spec_scan )
return ;
length = __be16_to_cpu ( fft_sample_tlv - > length ) +
sizeof ( * fft_sample_tlv ) ;
relay_write ( ar - > spectral . 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_spectral_process_fft ( struct ath10k * ar ,
2014-09-18 17:21:25 +04:00
const struct wmi_phyerr * phyerr ,
const struct phyerr_fft_report * fftr ,
2014-08-02 10:12:54 +04:00
size_t bin_len , u64 tsf )
{
struct fft_sample_ath10k * fft_sample ;
u8 buf [ sizeof ( * fft_sample ) + SPECTRAL_ATH10K_MAX_NUM_BINS ] ;
u16 freq1 , freq2 , total_gain_db , base_pwr_db , length , peak_mag ;
2014-09-18 17:21:25 +04:00
u32 reg0 , reg1 ;
2014-08-02 10:12:54 +04:00
u8 chain_idx , * bins ;
int dc_pos ;
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 .
*/
2014-09-18 17:21:25 +04:00
switch ( phyerr - > chan_width_mhz ) {
2014-08-02 10:12:54 +04:00
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 :
2014-09-18 17:21:25 +04:00
fft_sample - > chan_width_mhz = phyerr - > chan_width_mhz ;
2014-08-02 10:12:54 +04:00
}
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 ) ;
2014-09-18 17:21:25 +04:00
fft_sample - > rssi = phyerr - > rssi_combined ;
2014-08-02 10:12:54 +04:00
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 ) ;
2014-09-18 17:21:25 +04:00
freq1 = __le16_to_cpu ( phyerr - > freq1 ) ;
freq2 = __le16_to_cpu ( phyerr - > freq2 ) ;
2014-08-02 10:12:54 +04:00
fft_sample - > freq1 = __cpu_to_be16 ( freq1 ) ;
fft_sample - > freq2 = __cpu_to_be16 ( freq2 ) ;
chain_idx = MS ( reg0 , SEARCH_FFT_REPORT_REG0_FFT_CHN_IDX ) ;
2014-09-18 17:21:25 +04:00
fft_sample - > noise = __cpu_to_be16 (
__le16_to_cpu ( phyerr - > nf_chains [ chain_idx ] ) ) ;
2014-08-02 10:12:54 +04:00
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 ;
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 ) {
2014-08-25 14:09:38 +04:00
ath10k_warn ( ar , " failed to enable spectral scan: %d \n " , res ) ;
2014-08-02 10:12:54 +04:00
return res ;
}
if ( mode = = SPECTRAL_DISABLED )
return 0 ;
if ( mode = = SPECTRAL_BACKGROUND )
count = WMI_SPECTRAL_COUNT_DEFAULT ;
else
count = max_t ( u8 , 1 , ar - > spectral . 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 - > spectral . 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 ) {
2014-08-25 14:09:38 +04:00
ath10k_warn ( ar , " failed to configure spectral scan: %d \n " , res ) ;
2014-08-02 10:12:54 +04:00
return res ;
}
return 0 ;
}
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 ) {
2014-08-25 14:09:38 +04:00
ath10k_warn ( ar , " failed to reconfigure spectral scan: %d \n " ,
2014-08-02 10:12:54 +04:00
res ) ;
}
res = ath10k_spectral_scan_trigger ( ar ) ;
if ( res < 0 ) {
2014-08-25 14:09:38 +04:00
ath10k_warn ( ar , " failed to trigger spectral scan: %d \n " ,
2014-08-02 10:12:54 +04:00
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 ,
} ;
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 - > spectral . 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 - > spectral . 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 ,
} ;
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 , bins , fft_size , bin_scale ;
mutex_lock ( & ar - > conf_mutex ) ;
fft_size = ar - > spectral . config . fft_size ;
bin_scale = WMI_SPECTRAL_BIN_SCALE_DEFAULT ;
bins = 1 < < ( fft_size - bin_scale ) ;
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 - > spectral . config . fft_size = ilog2 ( val ) ;
ar - > spectral . 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 ,
} ;
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 ,
} ;
int ath10k_spectral_start ( struct ath10k * ar )
{
struct ath10k_vif * arvif ;
lockdep_assert_held ( & ar - > conf_mutex ) ;
list_for_each_entry ( arvif , & ar - > arvifs , list )
arvif - > spectral_enabled = 0 ;
ar - > spectral . mode = SPECTRAL_DISABLED ;
ar - > spectral . config . count = WMI_SPECTRAL_COUNT_DEFAULT ;
ar - > spectral . config . fft_size = WMI_SPECTRAL_FFT_SIZE_DEFAULT ;
return 0 ;
}
int ath10k_spectral_vif_stop ( struct ath10k_vif * arvif )
{
if ( ! arvif - > spectral_enabled )
return 0 ;
return ath10k_spectral_scan_config ( arvif - > ar , SPECTRAL_DISABLED ) ;
}
int ath10k_spectral_create ( struct ath10k * ar )
{
ar - > spectral . 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 ) ;
return 0 ;
}
void ath10k_spectral_destroy ( struct ath10k * ar )
{
if ( ar - > spectral . rfs_chan_spec_scan ) {
relay_close ( ar - > spectral . rfs_chan_spec_scan ) ;
ar - > spectral . rfs_chan_spec_scan = NULL ;
}
}