rt2x00:Add VCO recalibration
authorJohn Li <chen-yang.li@mediatek.com>
Thu, 16 Feb 2012 13:40:57 +0000 (21:40 +0800)
committerJohn W. Linville <linville@tuxdriver.com>
Mon, 27 Feb 2012 19:05:44 +0000 (14:05 -0500)
Signed-off-by: John Li <chen-yang.li@mediatek.com>
Acked-by: Gertjan van Wingerde <gwingerde@gmail.com>
Acked-by: Ivo van Doorn <IvDoorn@gmail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/rt2x00/rt2800.h
drivers/net/wireless/rt2x00/rt2800lib.c
drivers/net/wireless/rt2x00/rt2800lib.h
drivers/net/wireless/rt2x00/rt2800pci.c
drivers/net/wireless/rt2x00/rt2800usb.c
drivers/net/wireless/rt2x00/rt2x00.h
drivers/net/wireless/rt2x00/rt2x00config.c
drivers/net/wireless/rt2x00/rt2x00dev.c
drivers/net/wireless/rt2x00/rt2x00lib.h
drivers/net/wireless/rt2x00/rt2x00link.c

index 06acabd02984cbea2d51d67a762aba3d47d743bd..62e697ffc6d16c744ecc59609e34e94d3283eb05 100644 (file)
  * TX_PIN_CFG:
  */
 #define TX_PIN_CFG                     0x1328
+#define TX_PIN_CFG_PA_PE_DISABLE       0xfcfffff0
 #define TX_PIN_CFG_PA_PE_A0_EN         FIELD32(0x00000001)
 #define TX_PIN_CFG_PA_PE_G0_EN         FIELD32(0x00000002)
 #define TX_PIN_CFG_PA_PE_A1_EN         FIELD32(0x00000004)
 #define TX_PIN_CFG_RFTR_POL            FIELD32(0x00020000)
 #define TX_PIN_CFG_TRSW_EN             FIELD32(0x00040000)
 #define TX_PIN_CFG_TRSW_POL            FIELD32(0x00080000)
+#define TX_PIN_CFG_PA_PE_A2_EN         FIELD32(0x01000000)
+#define TX_PIN_CFG_PA_PE_G2_EN         FIELD32(0x02000000)
+#define TX_PIN_CFG_PA_PE_A2_POL                FIELD32(0x04000000)
+#define TX_PIN_CFG_PA_PE_G2_POL                FIELD32(0x08000000)
+#define TX_PIN_CFG_LNA_PE_A2_EN                FIELD32(0x10000000)
+#define TX_PIN_CFG_LNA_PE_G2_EN                FIELD32(0x20000000)
+#define TX_PIN_CFG_LNA_PE_A2_POL       FIELD32(0x40000000)
+#define TX_PIN_CFG_LNA_PE_G2_POL       FIELD32(0x80000000)
 
 /*
  * TX_BAND_CFG: 0x1 use upper 20MHz, 0x0 use lower 20MHz
index f1df929e97a720e6f0c15d33ab2487bfcd96164a..6104a14e4fff4585de4be3bb156f17f62ed610ed 100644 (file)
@@ -2497,6 +2497,80 @@ void rt2800_gain_calibration(struct rt2x00_dev *rt2x00dev)
 }
 EXPORT_SYMBOL_GPL(rt2800_gain_calibration);
 
+void rt2800_vco_calibration(struct rt2x00_dev *rt2x00dev)
+{
+       u32     tx_pin;
+       u8      rfcsr;
+
+       /*
+        * A voltage-controlled oscillator(VCO) is an electronic oscillator
+        * designed to be controlled in oscillation frequency by a voltage
+        * input. Maybe the temperature will affect the frequency of
+        * oscillation to be shifted. The VCO calibration will be called
+        * periodically to adjust the frequency to be precision.
+       */
+
+       rt2800_register_read(rt2x00dev, TX_PIN_CFG, &tx_pin);
+       tx_pin &= TX_PIN_CFG_PA_PE_DISABLE;
+       rt2800_register_write(rt2x00dev, TX_PIN_CFG, tx_pin);
+
+       switch (rt2x00dev->chip.rf) {
+       case RF2020:
+       case RF3020:
+       case RF3021:
+       case RF3022:
+       case RF3320:
+       case RF3052:
+               rt2800_rfcsr_read(rt2x00dev, 7, &rfcsr);
+               rt2x00_set_field8(&rfcsr, RFCSR7_RF_TUNING, 1);
+               rt2800_rfcsr_write(rt2x00dev, 7, rfcsr);
+               break;
+       case RF5370:
+       case RF5372:
+       case RF5390:
+               rt2800_rfcsr_read(rt2x00dev, 3, &rfcsr);
+               rt2x00_set_field8(&rfcsr, RFCSR30_RF_CALIBRATION, 1);
+               rt2800_rfcsr_write(rt2x00dev, 3, rfcsr);
+               break;
+       default:
+               return;
+       }
+
+       mdelay(1);
+
+       rt2800_register_read(rt2x00dev, TX_PIN_CFG, &tx_pin);
+       if (rt2x00dev->rf_channel <= 14) {
+               switch (rt2x00dev->default_ant.tx_chain_num) {
+               case 3:
+                       rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_G2_EN, 1);
+                       /* fall through */
+               case 2:
+                       rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_G1_EN, 1);
+                       /* fall through */
+               case 1:
+               default:
+                       rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_G0_EN, 1);
+                       break;
+               }
+       } else {
+               switch (rt2x00dev->default_ant.tx_chain_num) {
+               case 3:
+                       rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_A2_EN, 1);
+                       /* fall through */
+               case 2:
+                       rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_A1_EN, 1);
+                       /* fall through */
+               case 1:
+               default:
+                       rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_A0_EN, 1);
+                       break;
+               }
+       }
+       rt2800_register_write(rt2x00dev, TX_PIN_CFG, tx_pin);
+
+}
+EXPORT_SYMBOL_GPL(rt2800_vco_calibration);
+
 static void rt2800_config_retry_limit(struct rt2x00_dev *rt2x00dev,
                                      struct rt2x00lib_conf *libconf)
 {
@@ -4451,6 +4525,20 @@ int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
                }
        }
 
+       switch (rt2x00dev->chip.rf) {
+       case RF2020:
+       case RF3020:
+       case RF3021:
+       case RF3022:
+       case RF3320:
+       case RF3052:
+       case RF5370:
+       case RF5372:
+       case RF5390:
+               __set_bit(CAPABILITY_VCO_RECALIBRATION, &rt2x00dev->cap_flags);
+               break;
+       }
+
        return 0;
 }
 EXPORT_SYMBOL_GPL(rt2800_probe_hw_mode);
index 8c3c281904fe7f4a08a4e9c16100920ea502e022..419e36cb06be3d393ec036ccc614414e4e3f2ddc 100644 (file)
@@ -184,6 +184,7 @@ void rt2800_reset_tuner(struct rt2x00_dev *rt2x00dev, struct link_qual *qual);
 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);
 
 int rt2800_enable_radio(struct rt2x00_dev *rt2x00dev);
 void rt2800_disable_radio(struct rt2x00_dev *rt2x00dev);
index 6ad692914ee953c1950a763b60ccdbd36af98e56..fc9acc299b106ed196a57b369d94acfca5a86d77 100644 (file)
@@ -1050,6 +1050,7 @@ static const struct rt2x00lib_ops rt2800pci_rt2x00_ops = {
        .reset_tuner            = rt2800_reset_tuner,
        .link_tuner             = rt2800_link_tuner,
        .gain_calibration       = rt2800_gain_calibration,
+       .vco_calibration        = rt2800_vco_calibration,
        .start_queue            = rt2800pci_start_queue,
        .kick_queue             = rt2800pci_kick_queue,
        .stop_queue             = rt2800pci_stop_queue,
index 0074c4e18d29cf273349a74c1827ea61b52e3aca..f997f0056d7f57197da51553d947ab38155cab83 100644 (file)
@@ -781,6 +781,7 @@ static const struct rt2x00lib_ops rt2800usb_rt2x00_ops = {
        .reset_tuner            = rt2800_reset_tuner,
        .link_tuner             = rt2800_link_tuner,
        .gain_calibration       = rt2800_gain_calibration,
+       .vco_calibration        = rt2800_vco_calibration,
        .watchdog               = rt2800usb_watchdog,
        .start_queue            = rt2800usb_start_queue,
        .kick_queue             = rt2x00usb_kick_queue,
index ed2ae6efcaa3f9dddfbf7b245d06cae345d2e291..1906a94e448f5cf718b578c091be0a8968d9f83c 100644 (file)
@@ -355,6 +355,11 @@ struct link {
         * Work structure for scheduling periodic AGC adjustments.
         */
        struct delayed_work agc_work;
+
+       /*
+        * Work structure for scheduling periodic VCO calibration.
+        */
+       struct delayed_work vco_work;
 };
 
 enum rt2x00_delayed_flags {
@@ -579,6 +584,7 @@ struct rt2x00lib_ops {
        void (*link_tuner) (struct rt2x00_dev *rt2x00dev,
                            struct link_qual *qual, const u32 count);
        void (*gain_calibration) (struct rt2x00_dev *rt2x00dev);
+       void (*vco_calibration) (struct rt2x00_dev *rt2x00dev);
 
        /*
         * Data queue handlers.
@@ -722,6 +728,7 @@ enum rt2x00_capability_flags {
        CAPABILITY_EXTERNAL_LNA_BG,
        CAPABILITY_DOUBLE_ANTENNA,
        CAPABILITY_BT_COEXIST,
+       CAPABILITY_VCO_RECALIBRATION,
 };
 
 /*
@@ -977,6 +984,11 @@ struct rt2x00_dev {
        struct tasklet_struct rxdone_tasklet;
        struct tasklet_struct autowake_tasklet;
 
+       /*
+        * Used for VCO periodic calibration.
+        */
+       int rf_channel;
+
        /*
         * Protect the interrupt mask register.
         */
index b704e5b183d0bc539e8bec6d08776774854d5479..d7c0f86c9e435bfab86fc620c5ac6d49e5433784 100644 (file)
@@ -232,6 +232,9 @@ void rt2x00lib_config(struct rt2x00_dev *rt2x00dev,
                memcpy(&libconf.channel,
                       &rt2x00dev->spec.channels_info[hw_value],
                       sizeof(libconf.channel));
+
+               /* Used for VCO periodic calibration */
+               rt2x00dev->rf_channel = libconf.rf.channel;
        }
 
        if (test_bit(REQUIRE_PS_AUTOWAKE, &rt2x00dev->cap_flags) &&
index bae5b01299ea1e1065078d984bc4b94e5086a1ad..dfe485a872f6cbaf1bb43a80e9eb6c9fb41d5155 100644 (file)
@@ -88,6 +88,8 @@ int rt2x00lib_enable_radio(struct rt2x00_dev *rt2x00dev)
        rt2x00queue_start_queues(rt2x00dev);
        rt2x00link_start_tuner(rt2x00dev);
        rt2x00link_start_agc(rt2x00dev);
+       if (test_bit(CAPABILITY_VCO_RECALIBRATION, &rt2x00dev->cap_flags))
+               rt2x00link_start_vcocal(rt2x00dev);
 
        /*
         * Start watchdog monitoring.
@@ -111,6 +113,8 @@ void rt2x00lib_disable_radio(struct rt2x00_dev *rt2x00dev)
         * Stop all queues
         */
        rt2x00link_stop_agc(rt2x00dev);
+       if (test_bit(CAPABILITY_VCO_RECALIBRATION, &rt2x00dev->cap_flags))
+               rt2x00link_stop_vcocal(rt2x00dev);
        rt2x00link_stop_tuner(rt2x00dev);
        rt2x00queue_stop_queues(rt2x00dev);
        rt2x00queue_flush_queues(rt2x00dev, true);
index 4cdf247a870d6e21dbc5557e1e1aad18fa4253c7..78bd43b8961f74f1df8c89178faa5457482f6714 100644 (file)
@@ -33,6 +33,7 @@
 #define WATCHDOG_INTERVAL      round_jiffies_relative(HZ)
 #define LINK_TUNE_INTERVAL     round_jiffies_relative(HZ)
 #define AGC_INTERVAL           round_jiffies_relative(4 * HZ)
+#define VCO_INTERVAL           round_jiffies_relative(10 * HZ) /* 10 sec */
 
 /*
  * rt2x00_rate: Per rate device information
@@ -277,12 +278,24 @@ void rt2x00link_stop_watchdog(struct rt2x00_dev *rt2x00dev);
  */
 void rt2x00link_start_agc(struct rt2x00_dev *rt2x00dev);
 
+/**
+ * rt2x00link_start_vcocal - Start periodic VCO calibration
+ * @rt2x00dev: Pointer to &struct rt2x00_dev.
+ */
+void rt2x00link_start_vcocal(struct rt2x00_dev *rt2x00dev);
+
 /**
  * rt2x00link_stop_agc - Stop periodic gain calibration
  * @rt2x00dev: Pointer to &struct rt2x00_dev.
  */
 void rt2x00link_stop_agc(struct rt2x00_dev *rt2x00dev);
 
+/**
+ * rt2x00link_stop_vcocal - Stop periodic VCO calibration
+ * @rt2x00dev: Pointer to &struct rt2x00_dev.
+ */
+void rt2x00link_stop_vcocal(struct rt2x00_dev *rt2x00dev);
+
 /**
  * rt2x00link_register - Initialize link tuning & watchdog functionality
  * @rt2x00dev: Pointer to &struct rt2x00_dev.
index ea10b0068f823f503e827f45d9e4c52dd9f8a74f..8368aab86f286ee6ff5be65b85a73940f3c24cd0 100644 (file)
@@ -447,11 +447,27 @@ void rt2x00link_start_agc(struct rt2x00_dev *rt2x00dev)
                                             AGC_INTERVAL);
 }
 
+void rt2x00link_start_vcocal(struct rt2x00_dev *rt2x00dev)
+{
+       struct link *link = &rt2x00dev->link;
+
+       if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) &&
+           rt2x00dev->ops->lib->vco_calibration)
+               ieee80211_queue_delayed_work(rt2x00dev->hw,
+                                            &link->vco_work,
+                                            VCO_INTERVAL);
+}
+
 void rt2x00link_stop_agc(struct rt2x00_dev *rt2x00dev)
 {
        cancel_delayed_work_sync(&rt2x00dev->link.agc_work);
 }
 
+void rt2x00link_stop_vcocal(struct rt2x00_dev *rt2x00dev)
+{
+       cancel_delayed_work_sync(&rt2x00dev->link.vco_work);
+}
+
 static void rt2x00link_agc(struct work_struct *work)
 {
        struct rt2x00_dev *rt2x00dev =
@@ -473,9 +489,32 @@ static void rt2x00link_agc(struct work_struct *work)
                                             AGC_INTERVAL);
 }
 
+static void rt2x00link_vcocal(struct work_struct *work)
+{
+       struct rt2x00_dev *rt2x00dev =
+           container_of(work, struct rt2x00_dev, link.vco_work.work);
+       struct link *link = &rt2x00dev->link;
+
+       /*
+        * When the radio is shutting down we should
+        * immediately cease the VCO calibration.
+        */
+       if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
+               return;
+
+       rt2x00dev->ops->lib->vco_calibration(rt2x00dev);
+
+       if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags))
+               ieee80211_queue_delayed_work(rt2x00dev->hw,
+                                            &link->vco_work,
+                                            VCO_INTERVAL);
+}
+
 void rt2x00link_register(struct rt2x00_dev *rt2x00dev)
 {
        INIT_DELAYED_WORK(&rt2x00dev->link.agc_work, rt2x00link_agc);
+       if (test_bit(CAPABILITY_VCO_RECALIBRATION, &rt2x00dev->cap_flags))
+               INIT_DELAYED_WORK(&rt2x00dev->link.vco_work, rt2x00link_vcocal);
        INIT_DELAYED_WORK(&rt2x00dev->link.watchdog_work, rt2x00link_watchdog);
        INIT_DELAYED_WORK(&rt2x00dev->link.work, rt2x00link_tuner);
 }