From: Stanislaw Gruszka <stf_xl@wp.pl>
To: Ho Jie Feng <hjf3108@gmail.com>
Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH wireless-next 2/8] wifi: rt2800: calibrate the crystal of RT3593 with the beacons
Date: Sat, 31 Oct 2026 11:52:17 +0100 [thread overview]
Message-ID: <20261031105217.GB31143@wp.pl> (raw)
In-Reply-To: <20261029134752.1299130-3-hjf3108@gmail.com>
On Thu, Oct 29, 2026 at 09:47:43PM +0800, Ho Jie Feng wrote:
> On a Netgear WNDA4100 (RT3593), as the adapter warms up, the
> frequency offset drifts enough to affect the link quality.
> In certain cases it can cause the link speed to drop
> to 1Mbit/s or below.
>
> Add a frequency calibration step function to RT3593 to use
> the frequency offset obtained from the beacon frames of the AP
> for tuning.
Presumably this is based on vendor driver? Could you point
the sources i.e. a link for some Github repo where the
correspond code is?
Regards
Stanislaw
> Test results:
> before after
> TX 27.7 28.5
> RX 31.6 69.7
>
> Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
> ---
> drivers/net/wireless/ralink/rt2x00/rt2800.h | 1 +
> .../net/wireless/ralink/rt2x00/rt2800lib.c | 101 ++++++++++++++++++
> .../net/wireless/ralink/rt2x00/rt2800lib.h | 4 +
> .../net/wireless/ralink/rt2x00/rt2800usb.c | 1 +
> drivers/net/wireless/ralink/rt2x00/rt2x00.h | 6 ++
> .../net/wireless/ralink/rt2x00/rt2x00lib.h | 2 +
> .../net/wireless/ralink/rt2x00/rt2x00link.c | 9 ++
> 7 files changed, 124 insertions(+)
>
> diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800.h b/drivers/net/wireless/ralink/rt2x00/rt2800.h
> index 8930589b4967d..b9f6de3a462e3 100644
> --- a/drivers/net/wireless/ralink/rt2x00/rt2800.h
> +++ b/drivers/net/wireless/ralink/rt2x00/rt2800.h
> @@ -3163,6 +3163,7 @@ enum rt2800_eeprom_word {
> */
> #define RXWI_W3_SNR0 FIELD32(0x000000ff)
> #define RXWI_W3_SNR1 FIELD32(0x0000ff00)
> +#define RXWI_W3_FOFFSET FIELD32(0xff000000)
>
> /*
> * Macros for converting txpower from EEPROM to mac80211 value
> diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
> index 2e2e382b62475..e7b6edf485099 100644
> --- a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
> +++ b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
> @@ -897,6 +897,28 @@ static int rt2800_agc_to_rssi(struct rt2x00_dev *rt2x00dev, u32 rxwi_w2)
> return (int)max(rssi0, rssi2);
> }
>
> +static void rt2800_rx_freq_offset(struct queue_entry *entry,
> + struct rxdone_entry_desc *rxdesc)
> +{
> + struct rt2800_drv_data *drv_data = entry->queue->rt2x00dev->drv_data;
> + __le32 *rxwi = (__le32 *) entry->skb->data;
> + struct ieee80211_hdr *hdr =
> + (void *)(entry->skb->data + entry->queue->winfo_size);
> + u32 word;
> +
> + if (!(rxdesc->dev_flags & RXDONE_MY_BSS) ||
> + (rxdesc->flags & RX_FLAG_FAILED_FCS_CRC) ||
> + !ieee80211_is_beacon(hdr->frame_control))
> + return;
> +
> + word = rt2x00_desc_read(rxwi, 3);
> +
> + spin_lock_bh(&drv_data->bcn_lock);
> + drv_data->bcn_freq_offset = rt2x00_get_field32(word, RXWI_W3_FOFFSET);
> + drv_data->bcn_rate_mode = rxdesc->rate_mode;
> + spin_unlock_bh(&drv_data->bcn_lock);
> +}
> +
> void rt2800_process_rxwi(struct queue_entry *entry,
> struct rxdone_entry_desc *rxdesc)
> {
> @@ -935,6 +957,14 @@ void rt2800_process_rxwi(struct queue_entry *entry,
> * Convert descriptor AGC value to RSSI value.
> */
> rxdesc->rssi = rt2800_agc_to_rssi(entry->queue->rt2x00dev, word);
> +
> + /*
> + * The frequency offset of the beacons is used for the frequency
> + * calibration.
> + */
> + if (rt2x00_rt(entry->queue->rt2x00dev, RT3593))
> + rt2800_rx_freq_offset(entry, rxdesc);
> +
> /*
> * Remove RXWI descriptor from start of the buffer.
> */
> @@ -2443,6 +2473,7 @@ static inline bool rt2800_clk_is_20mhz(struct rt2x00_dev *rt2x00dev)
> }
>
> #define FREQ_OFFSET_BOUND 0x5f
> +#define FREQ_OFFSET_INVALID -128
>
> static void rt2800_freq_cal_mode1(struct rt2x00_dev *rt2x00dev)
> {
> @@ -5633,6 +5664,73 @@ void rt2800_vco_calibration(struct rt2x00_dev *rt2x00dev)
> }
> EXPORT_SYMBOL_GPL(rt2800_vco_calibration);
>
> +void rt2800_freq_calibration(struct rt2x00_dev *rt2x00dev)
> +{
> + struct rt2800_drv_data *drv_data = rt2x00dev->drv_data;
> + struct link *link = &rt2x00dev->link;
> + u8 activate_threshold, deactivate_threshold;
> + u8 freq_offset, rate_mode;
> + u16 eeprom;
> + s8 offset;
> +
> + if (!rt2x00_rt(rt2x00dev, RT3593))
> + return;
> +
> + if (!rt2x00dev->intf_associated) {
> + eeprom = rt2800_eeprom_read(rt2x00dev, EEPROM_FREQ);
> + rt2x00dev->freq_offset = rt2x00_get_field16(eeprom,
> + EEPROM_FREQ_OFFSET);
> + link->freq_cal_adjusting = false;
> + return;
> + }
> +
> + spin_lock_bh(&drv_data->bcn_lock);
> + offset = drv_data->bcn_freq_offset;
> + rate_mode = drv_data->bcn_rate_mode;
> + drv_data->bcn_freq_offset = FREQ_OFFSET_INVALID;
> + spin_unlock_bh(&drv_data->bcn_lock);
> +
> + /* No beacons received */
> + if (offset == FREQ_OFFSET_INVALID)
> + return;
> +
> + if (rate_mode == RATE_MODE_CCK) {
> + activate_threshold = 4;
> + deactivate_threshold = 2;
> + } else {
> + activate_threshold = 20;
> + deactivate_threshold = 10;
> + }
> +
> + /* Hysteresis */
> + if (abs(offset) >= activate_threshold)
> + link->freq_cal_adjusting = true;
> + else if (abs(offset) <= deactivate_threshold)
> + link->freq_cal_adjusting = false;
> +
> + if (!link->freq_cal_adjusting)
> + return;
> +
> + freq_offset = rt2x00_get_field8(rt2x00dev->freq_offset, RFCSR17_CODE);
> + freq_offset = min_t(u8, freq_offset, FREQ_OFFSET_BOUND);
> +
> + if (offset > 0) {
> + if (freq_offset > 0)
> + freq_offset--;
> + else
> + link->freq_cal_adjusting = false;
> + } else {
> + if (freq_offset < FREQ_OFFSET_BOUND)
> + freq_offset++;
> + else
> + link->freq_cal_adjusting = false;
> + }
> +
> + rt2x00dev->freq_offset = freq_offset;
> + rt2800_freq_cal_mode1(rt2x00dev);
> +}
> +EXPORT_SYMBOL_GPL(rt2800_freq_calibration);
> +
> static void rt2800_config_retry_limit(struct rt2x00_dev *rt2x00dev,
> struct rt2x00lib_conf *libconf)
> {
> @@ -12032,6 +12130,7 @@ static int rt2800_probe_rt(struct rt2x00_dev *rt2x00dev)
>
> int rt2800_probe_hw(struct rt2x00_dev *rt2x00dev)
> {
> + struct rt2800_drv_data *drv_data = rt2x00dev->drv_data;
> int retval;
> u32 reg;
>
> @@ -12105,6 +12204,8 @@ int rt2800_probe_hw(struct rt2x00_dev *rt2x00dev)
> */
> rt2x00dev->rssi_offset = DEFAULT_RSSI_OFFSET;
>
> + spin_lock_init(&drv_data->bcn_lock);
> +
> return 0;
> }
> EXPORT_SYMBOL_GPL(rt2800_probe_hw);
> diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800lib.h b/drivers/net/wireless/ralink/rt2x00/rt2800lib.h
> index a3c3a751f57e3..9a04a0fba113f 100644
> --- a/drivers/net/wireless/ralink/rt2x00/rt2800lib.h
> +++ b/drivers/net/wireless/ralink/rt2x00/rt2800lib.h
> @@ -45,6 +45,9 @@ struct rt2800_drv_data {
> unsigned int ampdu_factor_cnt[4];
> DECLARE_BITMAP(sta_ids, STA_IDS_SIZE);
> struct ieee80211_sta *wcid_to_sta[STA_IDS_SIZE];
> + spinlock_t bcn_lock; /* protects bcn_freq_offset, bcn_rate_mode */
> + s8 bcn_freq_offset;
> + u8 bcn_rate_mode;
> };
>
> struct rt2800_ops {
> @@ -241,6 +244,7 @@ void rt2800_link_tuner(struct rt2x00_dev *rt2x00dev, struct link_qual *qual,
> const u32 count);
> void rt2800_gain_calibration(struct rt2x00_dev *rt2x00dev);
> void rt2800_vco_calibration(struct rt2x00_dev *rt2x00dev);
> +void rt2800_freq_calibration(struct rt2x00_dev *rt2x00dev);
>
> int rt2800_enable_radio(struct rt2x00_dev *rt2x00dev);
> void rt2800_disable_radio(struct rt2x00_dev *rt2x00dev);
> diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800usb.c b/drivers/net/wireless/ralink/rt2x00/rt2800usb.c
> index 15de3db124298..387c07e2251b7 100644
> --- a/drivers/net/wireless/ralink/rt2x00/rt2800usb.c
> +++ b/drivers/net/wireless/ralink/rt2x00/rt2800usb.c
> @@ -693,6 +693,7 @@ static const struct rt2x00lib_ops rt2800usb_rt2x00_ops = {
> .link_tuner = rt2800_link_tuner,
> .gain_calibration = rt2800_gain_calibration,
> .vco_calibration = rt2800_vco_calibration,
> + .freq_calibration = rt2800_freq_calibration,
> .watchdog = rt2800_watchdog,
> .start_queue = rt2800usb_start_queue,
> .kick_queue = rt2x00usb_kick_queue,
> diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00.h b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
> index 7d313e86d3f20..a80508db32d9c 100644
> --- a/drivers/net/wireless/ralink/rt2x00/rt2x00.h
> +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
> @@ -319,6 +319,11 @@ struct link {
> */
> struct ewma_rssi avg_rssi;
>
> + /*
> + * Controls interval of frequency calibration
> + */
> + bool freq_cal_adjusting;
> +
> /*
> * Work structure for scheduling periodic link tuning.
> */
> @@ -559,6 +564,7 @@ struct rt2x00lib_ops {
> struct link_qual *qual, const u32 count);
> void (*gain_calibration) (struct rt2x00_dev *rt2x00dev);
> void (*vco_calibration) (struct rt2x00_dev *rt2x00dev);
> + void (*freq_calibration) (struct rt2x00_dev *rt2x00dev);
>
> /*
> * Data queue handlers.
> diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00lib.h b/drivers/net/wireless/ralink/rt2x00/rt2x00lib.h
> index 2f1385baa7ae9..a6f516ebdb2db 100644
> --- a/drivers/net/wireless/ralink/rt2x00/rt2x00lib.h
> +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00lib.h
> @@ -22,6 +22,8 @@
> #define LINK_TUNE_INTERVAL round_jiffies_relative(LINK_TUNE_SECONDS * HZ)
> #define AGC_SECONDS 4
> #define VCO_SECONDS 10
> +#define FREQ_SECONDS 10
> +#define FREQ_ADJUST_SECONDS 1
>
> /*
> * rt2x00_rate: Per rate device information
> diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00link.c b/drivers/net/wireless/ralink/rt2x00/rt2x00link.c
> index fb23d409fba87..2dc4edc12834f 100644
> --- a/drivers/net/wireless/ralink/rt2x00/rt2x00link.c
> +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00link.c
> @@ -343,6 +343,7 @@ static void rt2x00link_tuner(struct work_struct *work)
> struct rt2x00_dev *rt2x00dev =
> container_of(work, struct rt2x00_dev, link.work.work);
> struct link *link = &rt2x00dev->link;
> + unsigned int freq_interval;
>
> /*
> * When the radio is shutting down we should
> @@ -362,6 +363,14 @@ static void rt2x00link_tuner(struct work_struct *work)
> (link->count % (AGC_SECONDS / LINK_TUNE_SECONDS)) == 0)
> rt2x00dev->ops->lib->gain_calibration(rt2x00dev);
>
> + freq_interval = link->freq_cal_adjusting ?
> + FREQ_ADJUST_SECONDS : FREQ_SECONDS;
> +
> + /* VCO calibration should be done after frequency calibration */
> + if (rt2x00dev->ops->lib->freq_calibration &&
> + (link->count % (freq_interval / LINK_TUNE_SECONDS)) == 0)
> + rt2x00dev->ops->lib->freq_calibration(rt2x00dev);
> +
> if (rt2x00dev->ops->lib->vco_calibration &&
> rt2x00_has_cap_vco_recalibration(rt2x00dev) &&
> (link->count % (VCO_SECONDS / LINK_TUNE_SECONDS)) == 0)
> --
> 2.53.0
>
next prev parent reply other threads:[~2026-10-31 10:52 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-29 13:47 [PATCH wireless-next 0/8] wifi: rt2x00: improve the throughput of rt2800usb Ho Jie Feng
2026-10-29 13:47 ` [PATCH wireless-next 1/8] wifi: rt2x00: start the link tuner when an interface is added Ho Jie Feng
2026-10-31 10:51 ` Stanislaw Gruszka
2026-10-29 13:47 ` [PATCH wireless-next 2/8] wifi: rt2800: calibrate the crystal of RT3593 with the beacons Ho Jie Feng
2026-10-31 10:52 ` Stanislaw Gruszka [this message]
2026-10-31 16:49 ` Jie Feng
2026-10-29 13:47 ` [PATCH wireless-next 3/8] wifi: rt2800: use the 3SS fallback table of RT3883 on RT3593 Ho Jie Feng
2026-10-29 13:47 ` [PATCH wireless-next 4/8] wifi: rt2800: report the MCS16 to MCS8 fallback of RT3593 in TX status Ho Jie Feng
2026-10-31 10:54 ` Stanislaw Gruszka
2026-10-29 13:47 ` [PATCH wireless-next 5/8] wifi: rt2800: don't report an A-MPDU length to minstrel_ht Ho Jie Feng
2026-10-29 13:47 ` [PATCH wireless-next 6/8] wifi: rt2800: set the mac80211 fast-xmit and aggregation flags Ho Jie Feng
2026-10-30 9:17 ` Stanislaw Gruszka
2026-10-30 11:33 ` Jie Feng
2026-10-31 11:06 ` Stanislaw Gruszka
2026-10-31 17:18 ` Jie Feng
2026-10-31 17:23 ` Johannes Berg
2026-10-29 13:47 ` [PATCH wireless-next 7/8] wifi: rt2800usb: enable RX bulk aggregation Ho Jie Feng
2026-10-29 13:47 ` [PATCH wireless-next 8/8] wifi: rt2x00usb: use a threaded NAPI with GRO for RX Ho Jie Feng
2026-10-30 9:10 ` [PATCH wireless-next 0/8] wifi: rt2x00: improve the throughput of rt2800usb Stanislaw Gruszka
2026-10-30 11:30 ` Jie Feng
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261031105217.GB31143@wp.pl \
--to=stf_xl@wp.pl \
--cc=hjf3108@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-wireless@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox