2008-12-20 12:53:29 +03:00
/*
2009-11-08 18:39:55 +03:00
Copyright ( C ) 2004 - 2009 Ivo van Doorn < IvDoorn @ gmail . com >
2008-12-20 12:53:29 +03:00
< http : //rt2x00.serialmonkey.com>
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the
Free Software Foundation , Inc . ,
59 Temple Place - Suite 330 , Boston , MA 02111 - 1307 , USA .
*/
/*
Module : rt2x00lib
Abstract : rt2x00 generic link tuning routines .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include "rt2x00.h"
# include "rt2x00lib.h"
2008-12-20 12:54:54 +03:00
/*
* When we lack RSSI information return something less then - 80 to
* tell the driver to tune the device to maximum sensitivity .
*/
# define DEFAULT_RSSI -128
2009-08-09 01:54:51 +04:00
/*
* Helper struct and macro to work with moving / walking averages .
2008-12-20 12:54:54 +03:00
* When adding a value to the average value the following calculation
* is needed :
*
* avg_rssi = ( ( avg_rssi * 7 ) + rssi ) / 8 ;
*
* The advantage of this approach is that we only need 1 variable
* to store the average in ( No need for a count and a total ) .
* But more importantly , normal average values will over time
* move less and less towards newly added values this results
* that with link tuning , the device can have a very good RSSI
* for a few minutes but when the device is moved away from the AP
* the average will not decrease fast enough to compensate .
* The walking average compensates this and will move towards
2009-08-09 01:54:51 +04:00
* the new values correctly allowing a effective link tuning ,
* the speed of the average moving towards other values depends
* on the value for the number of samples . The higher the number
* of samples , the slower the average will move .
* We use two variables to keep track of the average value to
* compensate for the rounding errors . This can be a significant
* error ( > 5 dBm ) if the factor is too low .
2008-12-20 12:54:54 +03:00
*/
2009-08-09 01:54:51 +04:00
# define AVG_SAMPLES 8
# define AVG_FACTOR 1000
# define MOVING_AVERAGE(__avg, __val) \
( { \
struct avg_val __new ; \
__new . avg_weight = \
( __avg ) . avg_weight ? \
( ( ( ( __avg ) . avg_weight * ( ( AVG_SAMPLES ) - 1 ) ) + \
( ( __val ) * ( AVG_FACTOR ) ) ) / \
2010-11-06 17:47:09 +03:00
( AVG_SAMPLES ) ) : \
2009-08-09 01:54:51 +04:00
( ( __val ) * ( AVG_FACTOR ) ) ; \
__new . avg = __new . avg_weight / ( AVG_FACTOR ) ; \
__new ; \
} )
2008-12-20 12:54:54 +03:00
2008-12-20 12:53:29 +03:00
static int rt2x00link_antenna_get_link_rssi ( struct rt2x00_dev * rt2x00dev )
{
struct link_ant * ant = & rt2x00dev - > link . ant ;
2009-08-09 01:54:51 +04:00
if ( ant - > rssi_ant . avg & & rt2x00dev - > link . qual . rx_success )
return ant - > rssi_ant . avg ;
2008-12-20 12:53:29 +03:00
return DEFAULT_RSSI ;
}
2009-08-09 01:54:24 +04:00
static int rt2x00link_antenna_get_rssi_history ( struct rt2x00_dev * rt2x00dev )
2008-12-20 12:53:29 +03:00
{
struct link_ant * ant = & rt2x00dev - > link . ant ;
2009-08-09 01:54:24 +04:00
if ( ant - > rssi_history )
return ant - > rssi_history ;
2008-12-20 12:53:29 +03:00
return DEFAULT_RSSI ;
}
static void rt2x00link_antenna_update_rssi_history ( struct rt2x00_dev * rt2x00dev ,
int rssi )
{
struct link_ant * ant = & rt2x00dev - > link . ant ;
2009-08-09 01:54:24 +04:00
ant - > rssi_history = rssi ;
2008-12-20 12:53:29 +03:00
}
static void rt2x00link_antenna_reset ( struct rt2x00_dev * rt2x00dev )
{
2009-08-09 01:54:51 +04:00
rt2x00dev - > link . ant . rssi_ant . avg = 0 ;
rt2x00dev - > link . ant . rssi_ant . avg_weight = 0 ;
2008-12-20 12:53:29 +03:00
}
static void rt2x00lib_antenna_diversity_sample ( struct rt2x00_dev * rt2x00dev )
{
struct link_ant * ant = & rt2x00dev - > link . ant ;
struct antenna_setup new_ant ;
2009-08-09 01:54:24 +04:00
int other_antenna ;
int sample_current = rt2x00link_antenna_get_link_rssi ( rt2x00dev ) ;
int sample_other = rt2x00link_antenna_get_rssi_history ( rt2x00dev ) ;
2008-12-20 12:53:29 +03:00
memcpy ( & new_ant , & ant - > active , sizeof ( new_ant ) ) ;
/*
* We are done sampling . Now we should evaluate the results .
*/
ant - > flags & = ~ ANTENNA_MODE_SAMPLE ;
/*
* During the last period we have sampled the RSSI
2009-07-17 23:39:19 +04:00
* from both antennas . It now is time to determine
2008-12-20 12:53:29 +03:00
* which antenna demonstrated the best performance .
* When we are already on the antenna with the best
2009-08-09 01:54:24 +04:00
* performance , just create a good starting point
* for the history and we are done .
2008-12-20 12:53:29 +03:00
*/
2009-08-09 01:54:24 +04:00
if ( sample_current > = sample_other ) {
rt2x00link_antenna_update_rssi_history ( rt2x00dev ,
sample_current ) ;
2008-12-20 12:53:29 +03:00
return ;
2009-08-09 01:54:24 +04:00
}
other_antenna = ( ant - > active . rx = = ANTENNA_A ) ? ANTENNA_B : ANTENNA_A ;
2008-12-20 12:53:29 +03:00
if ( ant - > flags & ANTENNA_RX_DIVERSITY )
2009-08-09 01:54:24 +04:00
new_ant . rx = other_antenna ;
2008-12-20 12:53:29 +03:00
if ( ant - > flags & ANTENNA_TX_DIVERSITY )
2009-08-09 01:54:24 +04:00
new_ant . tx = other_antenna ;
2008-12-20 12:53:29 +03:00
2009-07-18 22:21:52 +04:00
rt2x00lib_config_antenna ( rt2x00dev , new_ant ) ;
2008-12-20 12:53:29 +03:00
}
static void rt2x00lib_antenna_diversity_eval ( struct rt2x00_dev * rt2x00dev )
{
struct link_ant * ant = & rt2x00dev - > link . ant ;
struct antenna_setup new_ant ;
int rssi_curr ;
int rssi_old ;
memcpy ( & new_ant , & ant - > active , sizeof ( new_ant ) ) ;
/*
* Get current RSSI value along with the historical value ,
* after that update the history with the current value .
*/
rssi_curr = rt2x00link_antenna_get_link_rssi ( rt2x00dev ) ;
2009-08-09 01:54:24 +04:00
rssi_old = rt2x00link_antenna_get_rssi_history ( rt2x00dev ) ;
rt2x00link_antenna_update_rssi_history ( rt2x00dev , rssi_curr ) ;
2008-12-20 12:53:29 +03:00
/*
* Legacy driver indicates that we should swap antenna ' s
* when the difference in RSSI is greater that 5. This
* also should be done when the RSSI was actually better
* then the previous sample .
* When the difference exceeds the threshold we should
* sample the rssi from the other antenna to make a valid
* comparison between the 2 antennas .
*/
if ( abs ( rssi_curr - rssi_old ) < 5 )
return ;
ant - > flags | = ANTENNA_MODE_SAMPLE ;
if ( ant - > flags & ANTENNA_RX_DIVERSITY )
new_ant . rx = ( new_ant . rx = = ANTENNA_A ) ? ANTENNA_B : ANTENNA_A ;
if ( ant - > flags & ANTENNA_TX_DIVERSITY )
new_ant . tx = ( new_ant . tx = = ANTENNA_A ) ? ANTENNA_B : ANTENNA_A ;
2009-07-18 22:21:52 +04:00
rt2x00lib_config_antenna ( rt2x00dev , new_ant ) ;
2008-12-20 12:53:29 +03:00
}
2009-08-09 01:54:24 +04:00
static bool rt2x00lib_antenna_diversity ( struct rt2x00_dev * rt2x00dev )
2008-12-20 12:53:29 +03:00
{
struct link_ant * ant = & rt2x00dev - > link . ant ;
/*
* Determine if software diversity is enabled for
* either the TX or RX antenna ( or both ) .
* Always perform this check since within the link
* tuner interval the configuration might have changed .
*/
2010-09-08 22:55:36 +04:00
ant - > flags & = ~ ANTENNA_RX_DIVERSITY ;
ant - > flags & = ~ ANTENNA_TX_DIVERSITY ;
2008-12-20 12:53:29 +03:00
if ( rt2x00dev - > default_ant . rx = = ANTENNA_SW_DIVERSITY )
2010-09-08 22:55:36 +04:00
ant - > flags | = ANTENNA_RX_DIVERSITY ;
2008-12-20 12:53:29 +03:00
if ( rt2x00dev - > default_ant . tx = = ANTENNA_SW_DIVERSITY )
2010-09-08 22:55:36 +04:00
ant - > flags | = ANTENNA_TX_DIVERSITY ;
2008-12-20 12:53:29 +03:00
if ( ! ( ant - > flags & ANTENNA_RX_DIVERSITY ) & &
! ( ant - > flags & ANTENNA_TX_DIVERSITY ) ) {
ant - > flags = 0 ;
2009-08-09 01:54:24 +04:00
return true ;
2008-12-20 12:53:29 +03:00
}
/*
* If we have only sampled the data over the last period
* we should now harvest the data . Otherwise just evaluate
* the data . The latter should only be performed once
* every 2 seconds .
*/
2009-08-09 01:54:24 +04:00
if ( ant - > flags & ANTENNA_MODE_SAMPLE ) {
2008-12-20 12:53:29 +03:00
rt2x00lib_antenna_diversity_sample ( rt2x00dev ) ;
2009-08-09 01:54:24 +04:00
return true ;
} else if ( rt2x00dev - > link . count & 1 ) {
2008-12-20 12:53:29 +03:00
rt2x00lib_antenna_diversity_eval ( rt2x00dev ) ;
2009-08-09 01:54:24 +04:00
return true ;
}
return false ;
2008-12-20 12:53:29 +03:00
}
void rt2x00link_update_stats ( struct rt2x00_dev * rt2x00dev ,
struct sk_buff * skb ,
struct rxdone_entry_desc * rxdesc )
{
2008-12-20 12:54:54 +03:00
struct link * link = & rt2x00dev - > link ;
2008-12-20 12:53:29 +03:00
struct link_qual * qual = & rt2x00dev - > link . qual ;
struct link_ant * ant = & rt2x00dev - > link . ant ;
struct ieee80211_hdr * hdr = ( struct ieee80211_hdr * ) skb - > data ;
2010-10-09 15:33:16 +04:00
/*
* No need to update the stats for ! = STA interfaces
*/
if ( ! rt2x00dev - > intf_sta_count )
return ;
2008-12-20 12:53:29 +03:00
/*
* Frame was received successfully since non - succesfull
* frames would have been dropped by the hardware .
*/
qual - > rx_success + + ;
/*
* We are only interested in quality statistics from
* beacons which came from the BSS which we are
* associated with .
*/
if ( ! ieee80211_is_beacon ( hdr - > frame_control ) | |
! ( rxdesc - > dev_flags & RXDONE_MY_BSS ) )
return ;
/*
* Update global RSSI
*/
2009-08-09 01:54:51 +04:00
link - > avg_rssi = MOVING_AVERAGE ( link - > avg_rssi , rxdesc - > rssi ) ;
2008-12-20 12:53:29 +03:00
/*
* Update antenna RSSI
*/
2009-08-09 01:54:51 +04:00
ant - > rssi_ant = MOVING_AVERAGE ( ant - > rssi_ant , rxdesc - > rssi ) ;
2008-12-20 12:53:29 +03:00
}
void rt2x00link_start_tuner ( struct rt2x00_dev * rt2x00dev )
{
2008-12-20 12:54:54 +03:00
struct link * link = & rt2x00dev - > link ;
2008-12-20 12:53:29 +03:00
/*
* Link tuning should only be performed when
2010-06-29 23:47:37 +04:00
* an active sta interface exists . AP interfaces
* don ' t need link tuning and monitor mode interfaces
* should never have to work with link tuners .
2008-12-20 12:53:29 +03:00
*/
2010-06-29 23:47:37 +04:00
if ( ! rt2x00dev - > intf_sta_count )
2008-12-20 12:53:29 +03:00
return ;
2010-07-11 14:24:47 +04:00
/**
* While scanning , link tuning is disabled . By default
* the most sensitive settings will be used to make sure
* that all beacons and probe responses will be recieved
* during the scan .
*/
if ( test_bit ( DEVICE_STATE_SCANNING , & rt2x00dev - > flags ) )
return ;
2008-12-20 12:53:29 +03:00
rt2x00link_reset_tuner ( rt2x00dev , false ) ;
2009-11-05 22:22:03 +03:00
if ( test_bit ( DEVICE_STATE_PRESENT , & rt2x00dev - > flags ) )
ieee80211_queue_delayed_work ( rt2x00dev - > hw ,
& link - > work , LINK_TUNE_INTERVAL ) ;
2008-12-20 12:53:29 +03:00
}
void rt2x00link_stop_tuner ( struct rt2x00_dev * rt2x00dev )
{
cancel_delayed_work_sync ( & rt2x00dev - > link . work ) ;
}
void rt2x00link_reset_tuner ( struct rt2x00_dev * rt2x00dev , bool antenna )
{
2008-12-20 12:54:54 +03:00
struct link_qual * qual = & rt2x00dev - > link . qual ;
2010-07-11 14:25:17 +04:00
u8 vgc_level = qual - > vgc_level_reg ;
2008-12-20 12:54:54 +03:00
2008-12-20 12:53:29 +03:00
if ( ! test_bit ( DEVICE_STATE_ENABLED_RADIO , & rt2x00dev - > flags ) )
return ;
/*
* Reset link information .
* Both the currently active vgc level as well as
* the link tuner counter should be reset . Resetting
* the counter is important for devices where the
* device should only perform link tuning during the
* first minute after being enabled .
*/
rt2x00dev - > link . count = 0 ;
2008-12-20 12:54:54 +03:00
memset ( qual , 0 , sizeof ( * qual ) ) ;
2008-12-20 12:53:29 +03:00
2010-07-11 14:25:17 +04:00
/*
* Restore the VGC level as stored in the registers ,
* the driver can use this to determine if the register
* must be updated during reset or not .
*/
qual - > vgc_level_reg = vgc_level ;
2008-12-20 12:53:29 +03:00
/*
* Reset the link tuner .
*/
2008-12-20 12:54:54 +03:00
rt2x00dev - > ops - > lib - > reset_tuner ( rt2x00dev , qual ) ;
2008-12-20 12:53:29 +03:00
if ( antenna )
rt2x00link_antenna_reset ( rt2x00dev ) ;
}
2009-03-28 22:51:41 +03:00
static void rt2x00link_reset_qual ( struct rt2x00_dev * rt2x00dev )
2009-03-01 19:42:00 +03:00
{
struct link_qual * qual = & rt2x00dev - > link . qual ;
qual - > rx_success = 0 ;
qual - > rx_failed = 0 ;
qual - > tx_success = 0 ;
qual - > tx_failed = 0 ;
}
2008-12-20 12:53:29 +03:00
static void rt2x00link_tuner ( struct work_struct * work )
{
struct rt2x00_dev * rt2x00dev =
container_of ( work , struct rt2x00_dev , link . work . work ) ;
2008-12-20 12:54:54 +03:00
struct link * link = & rt2x00dev - > link ;
2008-12-20 12:53:29 +03:00
struct link_qual * qual = & rt2x00dev - > link . qual ;
/*
* When the radio is shutting down we should
* immediately cease all link tuning .
*/
2010-07-11 14:24:47 +04:00
if ( ! test_bit ( DEVICE_STATE_ENABLED_RADIO , & rt2x00dev - > flags ) | |
test_bit ( DEVICE_STATE_SCANNING , & rt2x00dev - > flags ) )
2008-12-20 12:53:29 +03:00
return ;
/*
* Update statistics .
*/
rt2x00dev - > ops - > lib - > link_stats ( rt2x00dev , qual ) ;
rt2x00dev - > low_level_stats . dot11FCSErrorCount + = qual - > rx_failed ;
2008-12-20 12:54:54 +03:00
/*
* Update quality RSSI for link tuning ,
* when we have received some frames and we managed to
* collect the RSSI data we could use this . Otherwise we
* must fallback to the default RSSI value .
*/
2009-08-09 01:54:51 +04:00
if ( ! link - > avg_rssi . avg | | ! qual - > rx_success )
2008-12-20 12:54:54 +03:00
qual - > rssi = DEFAULT_RSSI ;
else
2009-08-09 01:54:51 +04:00
qual - > rssi = link - > avg_rssi . avg ;
2008-12-20 12:54:54 +03:00
2008-12-20 12:53:29 +03:00
/*
2010-07-11 14:24:22 +04:00
* Check if link tuning is supported by the hardware , some hardware
* do not support link tuning at all , while other devices can disable
* the feature from the EEPROM .
2008-12-20 12:53:29 +03:00
*/
2010-07-11 14:24:22 +04:00
if ( test_bit ( DRIVER_SUPPORT_LINK_TUNING , & rt2x00dev - > flags ) )
2008-12-20 12:54:54 +03:00
rt2x00dev - > ops - > lib - > link_tuner ( rt2x00dev , qual , link - > count ) ;
2008-12-20 12:53:29 +03:00
/*
* Send a signal to the led to update the led signal strength .
*/
2009-08-09 01:54:51 +04:00
rt2x00leds_led_quality ( rt2x00dev , qual - > rssi ) ;
2008-12-20 12:53:29 +03:00
/*
2009-08-09 01:54:24 +04:00
* Evaluate antenna setup , make this the last step when
* rt2x00lib_antenna_diversity made changes the quality
* statistics will be reset .
2009-03-01 19:42:00 +03:00
*/
2009-08-09 01:54:24 +04:00
if ( rt2x00lib_antenna_diversity ( rt2x00dev ) )
rt2x00link_reset_qual ( rt2x00dev ) ;
2009-03-01 19:42:00 +03:00
2008-12-20 12:53:29 +03:00
/*
* Increase tuner counter , and reschedule the next link tuner run .
*/
2008-12-20 12:54:54 +03:00
link - > count + + ;
2009-11-05 22:22:03 +03:00
if ( test_bit ( DEVICE_STATE_PRESENT , & rt2x00dev - > flags ) )
ieee80211_queue_delayed_work ( rt2x00dev - > hw ,
& link - > work , LINK_TUNE_INTERVAL ) ;
2008-12-20 12:53:29 +03:00
}
2010-07-11 14:25:46 +04:00
void rt2x00link_start_watchdog ( struct rt2x00_dev * rt2x00dev )
{
struct link * link = & rt2x00dev - > link ;
if ( ! test_bit ( DEVICE_STATE_PRESENT , & rt2x00dev - > flags ) | |
! test_bit ( DRIVER_SUPPORT_WATCHDOG , & rt2x00dev - > flags ) )
return ;
2011-01-30 15:24:05 +03:00
ieee80211_queue_delayed_work ( rt2x00dev - > hw ,
& link - > watchdog_work , WATCHDOG_INTERVAL ) ;
2010-07-11 14:25:46 +04:00
}
void rt2x00link_stop_watchdog ( struct rt2x00_dev * rt2x00dev )
{
cancel_delayed_work_sync ( & rt2x00dev - > link . watchdog_work ) ;
}
static void rt2x00link_watchdog ( struct work_struct * work )
{
struct rt2x00_dev * rt2x00dev =
container_of ( work , struct rt2x00_dev , link . watchdog_work . work ) ;
struct link * link = & rt2x00dev - > link ;
/*
* When the radio is shutting down we should
* immediately cease the watchdog monitoring .
*/
if ( ! test_bit ( DEVICE_STATE_ENABLED_RADIO , & rt2x00dev - > flags ) )
return ;
rt2x00dev - > ops - > lib - > watchdog ( rt2x00dev ) ;
if ( test_bit ( DEVICE_STATE_PRESENT , & rt2x00dev - > flags ) )
2011-01-30 15:24:05 +03:00
ieee80211_queue_delayed_work ( rt2x00dev - > hw ,
& link - > watchdog_work ,
WATCHDOG_INTERVAL ) ;
2010-07-11 14:25:46 +04:00
}
2008-12-20 12:53:29 +03:00
void rt2x00link_register ( struct rt2x00_dev * rt2x00dev )
{
2010-07-11 14:25:46 +04:00
INIT_DELAYED_WORK ( & rt2x00dev - > link . watchdog_work , rt2x00link_watchdog ) ;
2008-12-20 12:53:29 +03:00
INIT_DELAYED_WORK ( & rt2x00dev - > link . work , rt2x00link_tuner ) ;
}