ath5k: Add tx power calibration support
authorNick Kossifidis <mick@madwifi-project.org>
Sun, 15 Mar 2009 20:20:35 +0000 (22:20 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Sat, 28 Mar 2009 00:12:56 +0000 (20:12 -0400)
* Add tx power calibration support
* Add a few tx power limits
* Hardcode default power to 12.5dB
* Disable TPC for now

v2: Address Jiri's comments

Signed-off-by: Nick Kossifidis <mickflemm@gmail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/ath5k/ath5k.h
drivers/net/wireless/ath5k/attach.c
drivers/net/wireless/ath5k/base.c
drivers/net/wireless/ath5k/desc.c
drivers/net/wireless/ath5k/phy.c
drivers/net/wireless/ath5k/reg.h
drivers/net/wireless/ath5k/reset.c

index 0dc2c7321c8b69c6a45f79b607459f17b2ff47c7..0b616e72fe051436a59b822b51d486f834a9f343 100644 (file)
 #define AR5K_TUNE_CWMAX_11B                    1023
 #define AR5K_TUNE_CWMAX_XR                     7
 #define AR5K_TUNE_NOISE_FLOOR                  -72
-#define AR5K_TUNE_MAX_TXPOWER                  60
-#define AR5K_TUNE_DEFAULT_TXPOWER              30
-#define AR5K_TUNE_TPC_TXPOWER                  true
+#define AR5K_TUNE_MAX_TXPOWER                  63
+#define AR5K_TUNE_DEFAULT_TXPOWER              25
+#define AR5K_TUNE_TPC_TXPOWER                  false
 #define AR5K_TUNE_ANT_DIVERSITY                        true
 #define AR5K_TUNE_HWTXTRIES                    4
 
@@ -551,11 +551,11 @@ enum ath5k_pkt_type {
  */
 #define AR5K_TXPOWER_OFDM(_r, _v)      (                       \
        ((0 & 1) << ((_v) + 6)) |                               \
-       (((ah->ah_txpower.txp_rates[(_r)]) & 0x3f) << (_v))     \
+       (((ah->ah_txpower.txp_rates_power_table[(_r)]) & 0x3f) << (_v)) \
 )
 
 #define AR5K_TXPOWER_CCK(_r, _v)       (                       \
-       (ah->ah_txpower.txp_rates[(_r)] & 0x3f) << (_v) \
+       (ah->ah_txpower.txp_rates_power_table[(_r)] & 0x3f) << (_v)     \
 )
 
 /*
@@ -1085,13 +1085,25 @@ struct ath5k_hw {
        struct ath5k_gain       ah_gain;
        u8                      ah_offset[AR5K_MAX_RF_BANKS];
 
+
        struct {
-               u16             txp_pcdac[AR5K_EEPROM_POWER_TABLE_SIZE];
-               u16             txp_rates[AR5K_MAX_RATES];
-               s16             txp_min;
-               s16             txp_max;
+               /* Temporary tables used for interpolation */
+               u8              tmpL[AR5K_EEPROM_N_PD_GAINS]
+                                       [AR5K_EEPROM_POWER_TABLE_SIZE];
+               u8              tmpR[AR5K_EEPROM_N_PD_GAINS]
+                                       [AR5K_EEPROM_POWER_TABLE_SIZE];
+               u8              txp_pd_table[AR5K_EEPROM_POWER_TABLE_SIZE * 2];
+               u16             txp_rates_power_table[AR5K_MAX_RATES];
+               u8              txp_min_idx;
                bool            txp_tpc;
+               /* Values in 0.25dB units */
+               s16             txp_min_pwr;
+               s16             txp_max_pwr;
+               s16             txp_offset;
                s16             txp_ofdm;
+               /* Values in dB units */
+               s16             txp_cck_ofdm_pwr_delta;
+               s16             txp_cck_ofdm_gainf_delta;
        } ah_txpower;
 
        struct {
@@ -1161,6 +1173,7 @@ extern void ath5k_hw_update_mib_counters(struct ath5k_hw *ah, struct ieee80211_l
 
 /* EEPROM access functions */
 extern int ath5k_eeprom_init(struct ath5k_hw *ah);
+extern void ath5k_eeprom_detach(struct ath5k_hw *ah);
 extern int ath5k_eeprom_read_mac(struct ath5k_hw *ah, u8 *mac);
 extern bool ath5k_eeprom_is_hb63(struct ath5k_hw *ah);
 
@@ -1256,8 +1269,8 @@ extern void ath5k_hw_set_def_antenna(struct ath5k_hw *ah, unsigned int ant);
 extern unsigned int ath5k_hw_get_def_antenna(struct ath5k_hw *ah);
 extern int ath5k_hw_phy_disable(struct ath5k_hw *ah);
 /* TX power setup */
-extern int ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel, unsigned int txpower);
-extern int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, unsigned int power);
+extern int ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel, u8 ee_mode, u8 txpower);
+extern int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, u8 ee_mode, u8 txpower);
 
 /*
  * Functions used internaly
index 656cb9dc833b8690d99170b0e9ca35accb17098a..70d376c63aac4d4ba746dc899333bbcee606bff1 100644 (file)
@@ -341,6 +341,8 @@ void ath5k_hw_detach(struct ath5k_hw *ah)
        if (ah->ah_rf_banks != NULL)
                kfree(ah->ah_rf_banks);
 
+       ath5k_eeprom_detach(ah);
+
        /* assume interrupts are down */
        kfree(ah);
 }
index ef9825fa4ec85ff60f43dfe00d9bc23a50772e49..f28b86c5d346dd967e53cb3c3d06fed91184220b 100644 (file)
@@ -1209,6 +1209,9 @@ ath5k_txbuf_setup(struct ath5k_softc *sc, struct ath5k_buf *bf)
 
        pktlen = skb->len;
 
+       /* FIXME: If we are in g mode and rate is a CCK rate
+        * subtract ah->ah_txpower.txp_cck_ofdm_pwr_delta
+        * from tx power (value is in dB units already) */
        if (info->control.hw_key) {
                keyidx = info->control.hw_key->hw_key_idx;
                pktlen += info->control.hw_key->icv_len;
@@ -2037,6 +2040,9 @@ ath5k_beacon_setup(struct ath5k_softc *sc, struct ath5k_buf *bf)
                        antenna = sc->bsent & 4 ? 2 : 1;
        }
 
+       /* FIXME: If we are in g mode and rate is a CCK rate
+        * subtract ah->ah_txpower.txp_cck_ofdm_pwr_delta
+        * from tx power (value is in dB units already) */
        ds->ds_data = bf->skbaddr;
        ret = ah->ah_setup_tx_desc(ah, ds, skb->len,
                        ieee80211_get_hdrlen_from_skb(skb),
@@ -2601,12 +2607,6 @@ ath5k_reset(struct ath5k_softc *sc, bool stop, bool change_channel)
                goto err;
        }
 
-       /*
-        * This is needed only to setup initial state
-        * but it's best done after a reset.
-        */
-       ath5k_hw_set_txpower_limit(sc->ah, 0);
-
        ret = ath5k_rx_start(sc);
        if (ret) {
                ATH5K_ERR(sc, "can't start recv logic\n");
index b40a9287a39a8a3be2fd8888c2f5dd8fde484511..dc30a2b70a6b41861e9471df551dd1ba4fb62b4e 100644 (file)
@@ -194,6 +194,10 @@ static int ath5k_hw_setup_4word_tx_desc(struct ath5k_hw *ah,
                return -EINVAL;
        }
 
+       tx_power += ah->ah_txpower.txp_offset;
+       if (tx_power > AR5K_TUNE_MAX_TXPOWER)
+               tx_power = AR5K_TUNE_MAX_TXPOWER;
+
        /* Clear descriptor */
        memset(&desc->ud.ds_tx5212, 0, sizeof(struct ath5k_hw_5212_tx_desc));
 
index 81f5bebc48b13d2a40074664af148e4f162ef1d2..9e2faae5ae942c81d5a857d2b9c528138370cbfa 100644 (file)
@@ -4,6 +4,7 @@
  * Copyright (c) 2004-2007 Reyk Floeter <reyk@openbsd.org>
  * Copyright (c) 2006-2009 Nick Kossifidis <mickflemm@gmail.com>
  * Copyright (c) 2007-2008 Jiri Slaby <jirislaby@gmail.com>
+ * Copyright (c) 2008-2009 Felix Fietkau <nbd@openwrt.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -183,7 +184,9 @@ static void ath5k_hw_request_rfgain_probe(struct ath5k_hw *ah)
        if (ah->ah_gain.g_state != AR5K_RFGAIN_ACTIVE)
                return;
 
-       ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txpower.txp_max,
+       /* Send the packet with 2dB below max power as
+        * patent doc suggest */
+       ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txpower.txp_max_pwr - 4,
                        AR5K_PHY_PAPD_PROBE_TXPOWER) |
                        AR5K_PHY_PAPD_PROBE_TX_NEXT, AR5K_PHY_PAPD_PROBE);
 
@@ -1433,93 +1436,1120 @@ unsigned int ath5k_hw_get_def_antenna(struct ath5k_hw *ah)
        return false; /*XXX: What do we return for 5210 ?*/
 }
 
+
+/****************\
+* TX power setup *
+\****************/
+
+/*
+ * Helper functions
+ */
+
+/*
+ * Do linear interpolation between two given (x, y) points
+ */
+static s16
+ath5k_get_interpolated_value(s16 target, s16 x_left, s16 x_right,
+                                       s16 y_left, s16 y_right)
+{
+       s16 ratio, result;
+
+       /* Avoid divide by zero and skip interpolation
+        * if we have the same point */
+       if ((x_left == x_right) || (y_left == y_right))
+               return y_left;
+
+       /*
+        * Since we use ints and not fps, we need to scale up in
+        * order to get a sane ratio value (or else we 'll eg. get
+        * always 1 instead of 1.25, 1.75 etc). We scale up by 100
+        * to have some accuracy both for 0.5 and 0.25 steps.
+        */
+       ratio = ((100 * y_right - 100 * y_left)/(x_right - x_left));
+
+       /* Now scale down to be in range */
+       result = y_left + (ratio * (target - x_left) / 100);
+
+       return result;
+}
+
+/*
+ * Find vertical boundary (min pwr) for the linear PCDAC curve.
+ *
+ * Since we have the top of the curve and we draw the line below
+ * until we reach 1 (1 pcdac step) we need to know which point
+ * (x value) that is so that we don't go below y axis and have negative
+ * pcdac values when creating the curve, or fill the table with zeroes.
+ */
+static s16
+ath5k_get_linear_pcdac_min(const u8 *stepL, const u8 *stepR,
+                               const s16 *pwrL, const s16 *pwrR)
+{
+       s8 tmp;
+       s16 min_pwrL, min_pwrR;
+       s16 pwr_i = pwrL[0];
+
+       do {
+               pwr_i--;
+               tmp = (s8) ath5k_get_interpolated_value(pwr_i,
+                                               pwrL[0], pwrL[1],
+                                               stepL[0], stepL[1]);
+
+       } while (tmp > 1);
+
+       min_pwrL = pwr_i;
+
+       pwr_i = pwrR[0];
+       do {
+               pwr_i--;
+               tmp = (s8) ath5k_get_interpolated_value(pwr_i,
+                                               pwrR[0], pwrR[1],
+                                               stepR[0], stepR[1]);
+
+       } while (tmp > 1);
+
+       min_pwrR = pwr_i;
+
+       /* Keep the right boundary so that it works for both curves */
+       return max(min_pwrL, min_pwrR);
+}
+
+/*
+ * Interpolate (pwr,vpd) points to create a Power to PDADC or a
+ * Power to PCDAC curve.
+ *
+ * Each curve has power on x axis (in 0.5dB units) and PCDAC/PDADC
+ * steps (offsets) on y axis. Power can go up to 31.5dB and max
+ * PCDAC/PDADC step for each curve is 64 but we can write more than
+ * one curves on hw so we can go up to 128 (which is the max step we
+ * can write on the final table).
+ *
+ * We write y values (PCDAC/PDADC steps) on hw.
+ */
+static void
+ath5k_create_power_curve(s16 pmin, s16 pmax,
+                       const s16 *pwr, const u8 *vpd,
+                       u8 num_points,
+                       u8 *vpd_table, u8 type)
+{
+       u8 idx[2] = { 0, 1 };
+       s16 pwr_i = 2*pmin;
+       int i;
+
+       if (num_points < 2)
+               return;
+
+       /* We want the whole line, so adjust boundaries
+        * to cover the entire power range. Note that
+        * power values are already 0.25dB so no need
+        * to multiply pwr_i by 2 */
+       if (type == AR5K_PWRTABLE_LINEAR_PCDAC) {
+               pwr_i = pmin;
+               pmin = 0;
+               pmax = 63;
+       }
+
+       /* Find surrounding turning points (TPs)
+        * and interpolate between them */
+       for (i = 0; (i <= (u16) (pmax - pmin)) &&
+       (i < AR5K_EEPROM_POWER_TABLE_SIZE); i++) {
+
+               /* We passed the right TP, move to the next set of TPs
+                * if we pass the last TP, extrapolate above using the last
+                * two TPs for ratio */
+               if ((pwr_i > pwr[idx[1]]) && (idx[1] < num_points - 1)) {
+                       idx[0]++;
+                       idx[1]++;
+               }
+
+               vpd_table[i] = (u8) ath5k_get_interpolated_value(pwr_i,
+                                               pwr[idx[0]], pwr[idx[1]],
+                                               vpd[idx[0]], vpd[idx[1]]);
+
+               /* Increase by 0.5dB
+                * (0.25 dB units) */
+               pwr_i += 2;
+       }
+}
+
+/*
+ * Get the surrounding per-channel power calibration piers
+ * for a given frequency so that we can interpolate between
+ * them and come up with an apropriate dataset for our current
+ * channel.
+ */
+static void
+ath5k_get_chan_pcal_surrounding_piers(struct ath5k_hw *ah,
+                       struct ieee80211_channel *channel,
+                       struct ath5k_chan_pcal_info **pcinfo_l,
+                       struct ath5k_chan_pcal_info **pcinfo_r)
+{
+       struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
+       struct ath5k_chan_pcal_info *pcinfo;
+       u8 idx_l, idx_r;
+       u8 mode, max, i;
+       u32 target = channel->center_freq;
+
+       idx_l = 0;
+       idx_r = 0;
+
+       if (!(channel->hw_value & CHANNEL_OFDM)) {
+               pcinfo = ee->ee_pwr_cal_b;
+               mode = AR5K_EEPROM_MODE_11B;
+       } else if (channel->hw_value & CHANNEL_2GHZ) {
+               pcinfo = ee->ee_pwr_cal_g;
+               mode = AR5K_EEPROM_MODE_11G;
+       } else {
+               pcinfo = ee->ee_pwr_cal_a;
+               mode = AR5K_EEPROM_MODE_11A;
+       }
+       max = ee->ee_n_piers[mode] - 1;
+
+       /* Frequency is below our calibrated
+        * range. Use the lowest power curve
+        * we have */
+       if (target < pcinfo[0].freq) {
+               idx_l = idx_r = 0;
+               goto done;
+       }
+
+       /* Frequency is above our calibrated
+        * range. Use the highest power curve
+        * we have */
+       if (target > pcinfo[max].freq) {
+               idx_l = idx_r = max;
+               goto done;
+       }
+
+       /* Frequency is inside our calibrated
+        * channel range. Pick the surrounding
+        * calibration piers so that we can
+        * interpolate */
+       for (i = 0; i <= max; i++) {
+
+               /* Frequency matches one of our calibration
+                * piers, no need to interpolate, just use
+                * that calibration pier */
+               if (pcinfo[i].freq == target) {
+                       idx_l = idx_r = i;
+                       goto done;
+               }
+
+               /* We found a calibration pier that's above
+                * frequency, use this pier and the previous
+                * one to interpolate */
+               if (target < pcinfo[i].freq) {
+                       idx_r = i;
+                       idx_l = idx_r - 1;
+                       goto done;
+               }
+       }
+
+done:
+       *pcinfo_l = &pcinfo[idx_l];
+       *pcinfo_r = &pcinfo[idx_r];
+
+       return;
+}
+
+/*
+ * Get the surrounding per-rate power calibration data
+ * for a given frequency and interpolate between power
+ * values to set max target power supported by hw for
+ * each rate.
+ */
+static void
+ath5k_get_rate_pcal_data(struct ath5k_hw *ah,
+                       struct ieee80211_channel *channel,
+                       struct ath5k_rate_pcal_info *rates)
+{
+       struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
+       struct ath5k_rate_pcal_info *rpinfo;
+       u8 idx_l, idx_r;
+       u8 mode, max, i;
+       u32 target = channel->center_freq;
+
+       idx_l = 0;
+       idx_r = 0;
+
+       if (!(channel->hw_value & CHANNEL_OFDM)) {
+               rpinfo = ee->ee_rate_tpwr_b;
+               mode = AR5K_EEPROM_MODE_11B;
+       } else if (channel->hw_value & CHANNEL_2GHZ) {
+               rpinfo = ee->ee_rate_tpwr_g;
+               mode = AR5K_EEPROM_MODE_11G;
+       } else {
+               rpinfo = ee->ee_rate_tpwr_a;
+               mode = AR5K_EEPROM_MODE_11A;
+       }
+       max = ee->ee_rate_target_pwr_num[mode] - 1;
+
+       /* Get the surrounding calibration
+        * piers - same as above */
+       if (target < rpinfo[0].freq) {
+               idx_l = idx_r = 0;
+               goto done;
+       }
+
+       if (target > rpinfo[max].freq) {
+               idx_l = idx_r = max;
+               goto done;
+       }
+
+       for (i = 0; i <= max; i++) {
+
+               if (rpinfo[i].freq == target) {
+                       idx_l = idx_r = i;
+                       goto done;
+               }
+
+               if (target < rpinfo[i].freq) {
+                       idx_r = i;
+                       idx_l = idx_r - 1;
+                       goto done;
+               }
+       }
+
+done:
+       /* Now interpolate power value, based on the frequency */
+       rates->freq = target;
+
+       rates->target_power_6to24 =
+               ath5k_get_interpolated_value(target, rpinfo[idx_l].freq,
+                                       rpinfo[idx_r].freq,
+                                       rpinfo[idx_l].target_power_6to24,
+                                       rpinfo[idx_r].target_power_6to24);
+
+       rates->target_power_36 =
+               ath5k_get_interpolated_value(target, rpinfo[idx_l].freq,
+                                       rpinfo[idx_r].freq,
+                                       rpinfo[idx_l].target_power_36,
+                                       rpinfo[idx_r].target_power_36);
+
+       rates->target_power_48 =
+               ath5k_get_interpolated_value(target, rpinfo[idx_l].freq,
+                                       rpinfo[idx_r].freq,
+                                       rpinfo[idx_l].target_power_48,
+                                       rpinfo[idx_r].target_power_48);
+
+       rates->target_power_54 =
+               ath5k_get_interpolated_value(target, rpinfo[idx_l].freq,
+                                       rpinfo[idx_r].freq,
+                                       rpinfo[idx_l].target_power_54,
+                                       rpinfo[idx_r].target_power_54);
+}
+
+/*
+ * Get the max edge power for this channel if
+ * we have such data from EEPROM's Conformance Test
+ * Limits (CTL), and limit max power if needed.
+ *
+ * FIXME: Only works for world regulatory domains
+ */
+static void
+ath5k_get_max_ctl_power(struct ath5k_hw *ah,
+                       struct ieee80211_channel *channel)
+{
+       struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
+       struct ath5k_edge_power *rep = ee->ee_ctl_pwr;
+       u8 *ctl_val = ee->ee_ctl;
+       s16 max_chan_pwr = ah->ah_txpower.txp_max_pwr / 4;
+       s16 edge_pwr = 0;
+       u8 rep_idx;
+       u8 i, ctl_mode;
+       u8 ctl_idx = 0xFF;
+       u32 target = channel->center_freq;
+
+       /* Find out a CTL for our mode that's not mapped
+        * on a specific reg domain.
+        *
+        * TODO: Map our current reg domain to one of the 3 available
+        * reg domain ids so that we can support more CTLs. */
+       switch (channel->hw_value & CHANNEL_MODES) {
+       case CHANNEL_A:
+               ctl_mode = AR5K_CTL_11A | AR5K_CTL_NO_REGDOMAIN;
+               break;
+       case CHANNEL_G:
+               ctl_mode = AR5K_CTL_11G | AR5K_CTL_NO_REGDOMAIN;
+               break;
+       case CHANNEL_B:
+               ctl_mode = AR5K_CTL_11B | AR5K_CTL_NO_REGDOMAIN;
+               break;
+       case CHANNEL_T:
+               ctl_mode = AR5K_CTL_TURBO | AR5K_CTL_NO_REGDOMAIN;
+               break;
+       case CHANNEL_TG:
+               ctl_mode = AR5K_CTL_TURBOG | AR5K_CTL_NO_REGDOMAIN;
+               break;
+       case CHANNEL_XR:
+               /* Fall through */
+       default:
+               return;
+       }
+
+       for (i = 0; i < ee->ee_ctls; i++) {
+               if (ctl_val[i] == ctl_mode) {
+                       ctl_idx = i;
+                       break;
+               }
+       }
+
+       /* If we have a CTL dataset available grab it and find the
+        * edge power for our frequency */
+       if (ctl_idx == 0xFF)
+               return;
+
+       /* Edge powers are sorted by frequency from lower
+        * to higher. Each CTL corresponds to 8 edge power
+        * measurements. */
+       rep_idx = ctl_idx * AR5K_EEPROM_N_EDGES;
+
+       /* Don't do boundaries check because we
+        * might have more that one bands defined
+        * for this mode */
+
+       /* Get the edge power that's closer to our
+        * frequency */
+       for (i = 0; i < AR5K_EEPROM_N_EDGES; i++) {
+               rep_idx += i;
+               if (target <= rep[rep_idx].freq)
+                       edge_pwr = (s16) rep[rep_idx].edge;
+       }
+
+       if (edge_pwr)
+               ah->ah_txpower.txp_max_pwr = 4*min(edge_pwr, max_chan_pwr);
+}
+
+
+/*
+ * Power to PCDAC table functions
+ */
+
 /*
- * TX power setup
+ * Fill Power to PCDAC table on RF5111
+ *
+ * No further processing is needed for RF5111, the only thing we have to
+ * do is fill the values below and above calibration range since eeprom data
+ * may not cover the entire PCDAC table.
  */
+static void
+ath5k_fill_pwr_to_pcdac_table(struct ath5k_hw *ah, s16* table_min,
+                                                       s16 *table_max)
+{
+       u8      *pcdac_out = ah->ah_txpower.txp_pd_table;
+       u8      *pcdac_tmp = ah->ah_txpower.tmpL[0];
+       u8      pcdac_0, pcdac_n, pcdac_i, pwr_idx, i;
+       s16     min_pwr, max_pwr;
+
+       /* Get table boundaries */
+       min_pwr = table_min[0];
+       pcdac_0 = pcdac_tmp[0];
+
+       max_pwr = table_max[0];
+       pcdac_n = pcdac_tmp[table_max[0] - table_min[0]];
+
+       /* Extrapolate below minimum using pcdac_0 */
+       pcdac_i = 0;
+       for (i = 0; i < min_pwr; i++)
+               pcdac_out[pcdac_i++] = pcdac_0;
+
+       /* Copy values from pcdac_tmp */
+       pwr_idx = min_pwr;
+       for (i = 0 ; pwr_idx <= max_pwr &&
+       pcdac_i < AR5K_EEPROM_POWER_TABLE_SIZE; i++) {
+               pcdac_out[pcdac_i++] = pcdac_tmp[i];
+               pwr_idx++;
+       }
+
+       /* Extrapolate above maximum */
+       while (pcdac_i < AR5K_EEPROM_POWER_TABLE_SIZE)
+               pcdac_out[pcdac_i++] = pcdac_n;
+
+}
 
 /*
- * Initialize the tx power table (not fully implemented)
+ * Combine available XPD Curves and fill Linear Power to PCDAC table
+ * on RF5112
+ *
+ * RFX112 can have up to 2 curves (one for low txpower range and one for
+ * higher txpower range). We need to put them both on pcdac_out and place
+ * them in the correct location. In case we only have one curve available
+ * just fit it on pcdac_out (it's supposed to cover the entire range of
+ * available pwr levels since it's always the higher power curve). Extrapolate
+ * below and above final table if needed.
  */
-static void ath5k_txpower_table(struct ath5k_hw *ah,
-               struct ieee80211_channel *channel, s16 max_power)
+static void
+ath5k_combine_linear_pcdac_curves(struct ath5k_hw *ah, s16* table_min,
+                                               s16 *table_max, u8 pdcurves)
 {
-       unsigned int i, min, max, n;
-       u16 txpower, *rates;
-
-       rates = ah->ah_txpower.txp_rates;
-
-       txpower = AR5K_TUNE_DEFAULT_TXPOWER * 2;
-       if (max_power > txpower)
-               txpower = max_power > AR5K_TUNE_MAX_TXPOWER ?
-                   AR5K_TUNE_MAX_TXPOWER : max_power;
-
-       for (i = 0; i < AR5K_MAX_RATES; i++)
-               rates[i] = txpower;
-
-       /* XXX setup target powers by rate */
-
-       ah->ah_txpower.txp_min = rates[7];
-       ah->ah_txpower.txp_max = rates[0];
-       ah->ah_txpower.txp_ofdm = rates[0];
-
-       /* Calculate the power table */
-       n = ARRAY_SIZE(ah->ah_txpower.txp_pcdac);
-       min = AR5K_EEPROM_PCDAC_START;
-       max = AR5K_EEPROM_PCDAC_STOP;
-       for (i = 0; i < n; i += AR5K_EEPROM_PCDAC_STEP)
-               ah->ah_txpower.txp_pcdac[i] =
-#ifdef notyet
-               min + ((i * (max - min)) / n);
-#else
-               min;
+       u8      *pcdac_out = ah->ah_txpower.txp_pd_table;
+       u8      *pcdac_low_pwr;
+       u8      *pcdac_high_pwr;
+       u8      *pcdac_tmp;
+       u8      pwr;
+       s16     max_pwr_idx;
+       s16     min_pwr_idx;
+       s16     mid_pwr_idx = 0;
+       /* Edge flag turs on the 7nth bit on the PCDAC
+        * to delcare the higher power curve (force values
+        * to be greater than 64). If we only have one curve
+        * we don't need to set this, if we have 2 curves and
+        * fill the table backwards this can also be used to
+        * switch from higher power curve to lower power curve */
+       u8      edge_flag;
+       int     i;
+
+       /* When we have only one curve available
+        * that's the higher power curve. If we have
+        * two curves the first is the high power curve
+        * and the next is the low power curve. */
+       if (pdcurves > 1) {
+               pcdac_low_pwr = ah->ah_txpower.tmpL[1];
+               pcdac_high_pwr = ah->ah_txpower.tmpL[0];
+               mid_pwr_idx = table_max[1] - table_min[1] - 1;
+               max_pwr_idx = (table_max[0] - table_min[0]) / 2;
+
+               /* If table size goes beyond 31.5dB, keep the
+                * upper 31.5dB range when setting tx power.
+                * Note: 126 = 31.5 dB in quarter dB steps */
+               if (table_max[0] - table_min[1] > 126)
+                       min_pwr_idx = table_max[0] - 126;
+               else
+                       min_pwr_idx = table_min[1];
+
+               /* Since we fill table backwards
+                * start from high power curve */
+               pcdac_tmp = pcdac_high_pwr;
+
+               edge_flag = 0x40;
+#if 0
+               /* If both min and max power limits are in lower
+                * power curve's range, only use the low power curve.
+                * TODO: min/max levels are related to target
+                * power values requested from driver/user
+                * XXX: Is this really needed ? */
+               if (min_pwr < table_max[1] &&
+               max_pwr < table_max[1]) {
+                       edge_flag = 0;
+                       pcdac_tmp = pcdac_low_pwr;
+                       max_pwr_idx = (table_max[1] - table_min[1])/2;
+               }
 #endif
+       } else {
+               pcdac_low_pwr = ah->ah_txpower.tmpL[1]; /* Zeroed */
+               pcdac_high_pwr = ah->ah_txpower.tmpL[0];
+               min_pwr_idx = table_min[0];
+               max_pwr_idx = (table_max[0] - table_min[0]) / 2;
+               pcdac_tmp = pcdac_high_pwr;
+               edge_flag = 0;
+       }
+
+       /* This is used when setting tx power*/
+       ah->ah_txpower.txp_min_idx = min_pwr_idx/2;
+
+       /* Fill Power to PCDAC table backwards */
+       pwr = max_pwr_idx;
+       for (i = 63; i >= 0; i--) {
+               /* Entering lower power range, reset
+                * edge flag and set pcdac_tmp to lower
+                * power curve.*/
+               if (edge_flag == 0x40 &&
+               (2*pwr <= (table_max[1] - table_min[0]) || pwr == 0)) {
+                       edge_flag = 0x00;
+                       pcdac_tmp = pcdac_low_pwr;
+                       pwr = mid_pwr_idx/2;
+               }
+
+               /* Don't go below 1, extrapolate below if we have
+                * already swithced to the lower power curve -or
+                * we only have one curve and edge_flag is zero
+                * anyway */
+               if (pcdac_tmp[pwr] < 1 && (edge_flag == 0x00)) {
+                       while (i >= 0) {
+                               pcdac_out[i] = pcdac_out[i + 1];
+                               i--;
+                       }
+                       break;
+               }
+
+               pcdac_out[i] = pcdac_tmp[pwr] | edge_flag;
+
+               /* Extrapolate above if pcdac is greater than
+                * 126 -this can happen because we OR pcdac_out
+                * value with edge_flag on high power curve */
+               if (pcdac_out[i] > 126)
+                       pcdac_out[i] = 126;
+
+               /* Decrease by a 0.5dB step */
+               pwr--;
+       }
 }
 
+/* Write PCDAC values on hw */
+static void
+ath5k_setup_pcdac_table(struct ath5k_hw *ah)
+{
+       u8      *pcdac_out = ah->ah_txpower.txp_pd_table;
+       int     i;
+
+       /*
+        * Write TX power values
+        */
+       for (i = 0; i < (AR5K_EEPROM_POWER_TABLE_SIZE / 2); i++) {
+               ath5k_hw_reg_write(ah,
+                       (((pcdac_out[2*i + 0] << 8 | 0xff) & 0xffff) << 0) |
+                       (((pcdac_out[2*i + 1] << 8 | 0xff) & 0xffff) << 16),
+                       AR5K_PHY_PCDAC_TXPOWER(i));
+       }
+}
+
+
 /*
- * Set transmition power
+ * Power to PDADC table functions
  */
-int /*O.K. - txpower_table is unimplemented so this doesn't work*/
-ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel,
-               unsigned int txpower)
+
+/*
+ * Set the gain boundaries and create final Power to PDADC table
+ *
+ * We can have up to 4 pd curves, we need to do a simmilar process
+ * as we do for RF5112. This time we don't have an edge_flag but we
+ * set the gain boundaries on a separate register.
+ */
+static void
+ath5k_combine_pwr_to_pdadc_curves(struct ath5k_hw *ah,
+                       s16 *pwr_min, s16 *pwr_max, u8 pdcurves)
 {
-       bool tpc = ah->ah_txpower.txp_tpc;
-       unsigned int i;
+       u8 gain_boundaries[AR5K_EEPROM_N_PD_GAINS];
+       u8 *pdadc_out = ah->ah_txpower.txp_pd_table;
+       u8 *pdadc_tmp;
+       s16 pdadc_0;
+       u8 pdadc_i, pdadc_n, pwr_step, pdg, max_idx, table_size;
+       u8 pd_gain_overlap;
+
+       /* Note: Register value is initialized on initvals
+        * there is no feedback from hw.
+        * XXX: What about pd_gain_overlap from EEPROM ? */
+       pd_gain_overlap = (u8) ath5k_hw_reg_read(ah, AR5K_PHY_TPC_RG5) &
+               AR5K_PHY_TPC_RG5_PD_GAIN_OVERLAP;
+
+       /* Create final PDADC table */
+       for (pdg = 0, pdadc_i = 0; pdg < pdcurves; pdg++) {
+               pdadc_tmp = ah->ah_txpower.tmpL[pdg];
+
+               if (pdg == pdcurves - 1)
+                       /* 2 dB boundary stretch for last
+                        * (higher power) curve */
+                       gain_boundaries[pdg] = pwr_max[pdg] + 4;
+               else
+                       /* Set gain boundary in the middle
+                        * between this curve and the next one */
+                       gain_boundaries[pdg] =
+                               (pwr_max[pdg] + pwr_min[pdg + 1]) / 2;
+
+               /* Sanity check in case our 2 db stretch got out of
+                * range. */
+               if (gain_boundaries[pdg] > AR5K_TUNE_MAX_TXPOWER)
+                       gain_boundaries[pdg] = AR5K_TUNE_MAX_TXPOWER;
+
+               /* For the first curve (lower power)
+                * start from 0 dB */
+               if (pdg == 0)
+                       pdadc_0 = 0;
+               else
+                       /* For the other curves use the gain overlap */
+                       pdadc_0 = (gain_boundaries[pdg - 1] - pwr_min[pdg]) -
+                                                       pd_gain_overlap;
 
-       ATH5K_TRACE(ah->ah_sc);
-       if (txpower > AR5K_TUNE_MAX_TXPOWER) {
-               ATH5K_ERR(ah->ah_sc, "invalid tx power: %u\n", txpower);
-               return -EINVAL;
+               /* Force each power step to be at least 0.5 dB */
+               if ((pdadc_tmp[1] - pdadc_tmp[0]) > 1)
+                       pwr_step = pdadc_tmp[1] - pdadc_tmp[0];
+               else
+                       pwr_step = 1;
+
+               /* If pdadc_0 is negative, we need to extrapolate
+                * below this pdgain by a number of pwr_steps */
+               while ((pdadc_0 < 0) && (pdadc_i < 128)) {
+                       s16 tmp = pdadc_tmp[0] + pdadc_0 * pwr_step;
+                       pdadc_out[pdadc_i++] = (tmp < 0) ? 0 : (u8) tmp;
+                       pdadc_0++;
+               }
+
+               /* Set last pwr level, using gain boundaries */
+               pdadc_n = gain_boundaries[pdg] + pd_gain_overlap - pwr_min[pdg];
+               /* Limit it to be inside pwr range */
+               table_size = pwr_max[pdg] - pwr_min[pdg];
+               max_idx = (pdadc_n < table_size) ? pdadc_n : table_size;
+
+               /* Fill pdadc_out table */
+               while (pdadc_0 < max_idx)
+                       pdadc_out[pdadc_i++] = pdadc_tmp[pdadc_0++];
+
+               /* Need to extrapolate above this pdgain? */
+               if (pdadc_n <= max_idx)
+                       continue;
+
+               /* Force each power step to be at least 0.5 dB */
+               if ((pdadc_tmp[table_size - 1] - pdadc_tmp[table_size - 2]) > 1)
+                       pwr_step = pdadc_tmp[table_size - 1] -
+                                               pdadc_tmp[table_size - 2];
+               else
+                       pwr_step = 1;
+
+               /* Extrapolate above */
+               while ((pdadc_0 < (s16) pdadc_n) &&
+               (pdadc_i < AR5K_EEPROM_POWER_TABLE_SIZE * 2)) {
+                       s16 tmp = pdadc_tmp[table_size - 1] +
+                                       (pdadc_0 - max_idx) * pwr_step;
+                       pdadc_out[pdadc_i++] = (tmp > 127) ? 127 : (u8) tmp;
+                       pdadc_0++;
+               }
        }
 
+       while (pdg < AR5K_EEPROM_N_PD_GAINS) {
+               gain_boundaries[pdg] = gain_boundaries[pdg - 1];
+               pdg++;
+       }
+
+       while (pdadc_i < AR5K_EEPROM_POWER_TABLE_SIZE * 2) {
+               pdadc_out[pdadc_i] = pdadc_out[pdadc_i - 1];
+               pdadc_i++;
+       }
+
+       /* Set gain boundaries */
+       ath5k_hw_reg_write(ah,
+               AR5K_REG_SM(pd_gain_overlap,
+                       AR5K_PHY_TPC_RG5_PD_GAIN_OVERLAP) |
+               AR5K_REG_SM(gain_boundaries[0],
+                       AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_1) |
+               AR5K_REG_SM(gain_boundaries[1],
+                       AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_2) |
+               AR5K_REG_SM(gain_boundaries[2],
+                       AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_3) |
+               AR5K_REG_SM(gain_boundaries[3],
+                       AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_4),
+               AR5K_PHY_TPC_RG5);
+
+       /* Used for setting rate power table */
+       ah->ah_txpower.txp_min_idx = pwr_min[0];
+
+}
+
+/* Write PDADC values on hw */
+static void
+ath5k_setup_pwr_to_pdadc_table(struct ath5k_hw *ah,
+                       u8 pdcurves, u8 *pdg_to_idx)
+{
+       u8 *pdadc_out = ah->ah_txpower.txp_pd_table;
+       u32 reg;
+       u8 i;
+
+       /* Select the right pdgain curves */
+
+       /* Clear current settings */
+       reg = ath5k_hw_reg_read(ah, AR5K_PHY_TPC_RG1);
+       reg &= ~(AR5K_PHY_TPC_RG1_PDGAIN_1 |
+               AR5K_PHY_TPC_RG1_PDGAIN_2 |
+               AR5K_PHY_TPC_RG1_PDGAIN_3 |
+               AR5K_PHY_TPC_RG1_NUM_PD_GAIN);
+
        /*
-        * RF2413 for some reason can't
-        * transmit anything if we call
-        * this funtion, so we skip it
-        * until we fix txpower.
+        * Use pd_gains curve from eeprom
         *
-        * XXX: Assume same for RF2425
-        * to be safe.
+        * This overrides the default setting from initvals
+        * in case some vendors (e.g. Zcomax) don't use the default
+        * curves. If we don't honor their settings we 'll get a
+        * 5dB (1 * gain overlap ?) drop.
         */
-       if ((ah->ah_radio == AR5K_RF2413) || (ah->ah_radio == AR5K_RF2425))
-               return 0;
+       reg |= AR5K_REG_SM(pdcurves, AR5K_PHY_TPC_RG1_NUM_PD_GAIN);
 
-       /* Reset TX power values */
-       memset(&ah->ah_txpower, 0, sizeof(ah->ah_txpower));
-       ah->ah_txpower.txp_tpc = tpc;
-
-       /* Initialize TX power table */
-       ath5k_txpower_table(ah, channel, txpower);
+       switch (pdcurves) {
+       case 3:
+               reg |= AR5K_REG_SM(pdg_to_idx[2], AR5K_PHY_TPC_RG1_PDGAIN_3);
+               /* Fall through */
+       case 2:
+               reg |= AR5K_REG_SM(pdg_to_idx[1], AR5K_PHY_TPC_RG1_PDGAIN_2);
+               /* Fall through */
+       case 1:
+               reg |= AR5K_REG_SM(pdg_to_idx[0], AR5K_PHY_TPC_RG1_PDGAIN_1);
+               break;
+       }
+       ath5k_hw_reg_write(ah, reg, AR5K_PHY_TPC_RG1);
 
        /*
         * Write TX power values
         */
        for (i = 0; i < (AR5K_EEPROM_POWER_TABLE_SIZE / 2); i++) {
                ath5k_hw_reg_write(ah,
-                       ((((ah->ah_txpower.txp_pcdac[(i << 1) + 1] << 8) | 0xff) & 0xffff) << 16) |
-                       (((ah->ah_txpower.txp_pcdac[(i << 1)    ] << 8) | 0xff) & 0xffff),
-                       AR5K_PHY_PCDAC_TXPOWER(i));
+                       ((pdadc_out[4*i + 0] & 0xff) << 0) |
+                       ((pdadc_out[4*i + 1] & 0xff) << 8) |
+                       ((pdadc_out[4*i + 2] & 0xff) << 16) |
+                       ((pdadc_out[4*i + 3] & 0xff) << 24),
+                       AR5K_PHY_PDADC_TXPOWER(i));
+       }
+}
+
+
+/*
+ * Common code for PCDAC/PDADC tables
+ */
+
+/*
+ * This is the main function that uses all of the above
+ * to set PCDAC/PDADC table on hw for the current channel.
+ * This table is used for tx power calibration on the basband,
+ * without it we get weird tx power levels and in some cases
+ * distorted spectral mask
+ */
+static int
+ath5k_setup_channel_powertable(struct ath5k_hw *ah,
+                       struct ieee80211_channel *channel,
+                       u8 ee_mode, u8 type)
+{
+       struct ath5k_pdgain_info *pdg_L, *pdg_R;
+       struct ath5k_chan_pcal_info *pcinfo_L;
+       struct ath5k_chan_pcal_info *pcinfo_R;
+       struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
+       u8 *pdg_curve_to_idx = ee->ee_pdc_to_idx[ee_mode];
+       s16 table_min[AR5K_EEPROM_N_PD_GAINS];
+       s16 table_max[AR5K_EEPROM_N_PD_GAINS];
+       u8 *tmpL;
+       u8 *tmpR;
+       u32 target = channel->center_freq;
+       int pdg, i;
+
+       /* Get surounding freq piers for this channel */
+       ath5k_get_chan_pcal_surrounding_piers(ah, channel,
+                                               &pcinfo_L,
+                                               &pcinfo_R);
+
+       /* Loop over pd gain curves on
+        * surounding freq piers by index */
+       for (pdg = 0; pdg < ee->ee_pd_gains[ee_mode]; pdg++) {
+
+               /* Fill curves in reverse order
+                * from lower power (max gain)
+                * to higher power. Use curve -> idx
+                * backmaping we did on eeprom init */
+               u8 idx = pdg_curve_to_idx[pdg];
+
+               /* Grab the needed curves by index */
+               pdg_L = &pcinfo_L->pd_curves[idx];
+               pdg_R = &pcinfo_R->pd_curves[idx];
+
+               /* Initialize the temp tables */
+               tmpL = ah->ah_txpower.tmpL[pdg];
+               tmpR = ah->ah_txpower.tmpR[pdg];
+
+               /* Set curve's x boundaries and create
+                * curves so that they cover the same
+                * range (if we don't do that one table
+                * will have values on some range and the
+                * other one won't have any so interpolation
+                * will fail) */
+               table_min[pdg] = min(pdg_L->pd_pwr[0],
+                                       pdg_R->pd_pwr[0]) / 2;
+
+               table_max[pdg] = max(pdg_L->pd_pwr[pdg_L->pd_points - 1],
+                               pdg_R->pd_pwr[pdg_R->pd_points - 1]) / 2;
+
+               /* Now create the curves on surrounding channels
+                * and interpolate if needed to get the final
+                * curve for this gain on this channel */
+               switch (type) {
+               case AR5K_PWRTABLE_LINEAR_PCDAC:
+                       /* Override min/max so that we don't loose
+                        * accuracy (don't divide by 2) */
+                       table_min[pdg] = min(pdg_L->pd_pwr[0],
+                                               pdg_R->pd_pwr[0]);
+
+                       table_max[pdg] =
+                               max(pdg_L->pd_pwr[pdg_L->pd_points - 1],
+                                       pdg_R->pd_pwr[pdg_R->pd_points - 1]);
+
+                       /* Override minimum so that we don't get
+                        * out of bounds while extrapolating
+                        * below. Don't do this when we have 2
+                        * curves and we are on the high power curve
+                        * because table_min is ok in this case */
+                       if (!(ee->ee_pd_gains[ee_mode] > 1 && pdg == 0)) {
+
+                               table_min[pdg] =
+                                       ath5k_get_linear_pcdac_min(pdg_L->pd_step,
+                                                               pdg_R->pd_step,
+                                                               pdg_L->pd_pwr,
+                                                               pdg_R->pd_pwr);
+
+                               /* Don't go too low because we will
+                                * miss the upper part of the curve.
+                                * Note: 126 = 31.5dB (max power supported)
+                                * in 0.25dB units */
+                               if (table_max[pdg] - table_min[pdg] > 126)
+                                       table_min[pdg] = table_max[pdg] - 126;
+                       }
+
+                       /* Fall through */
+               case AR5K_PWRTABLE_PWR_TO_PCDAC:
+               case AR5K_PWRTABLE_PWR_TO_PDADC:
+
+                       ath5k_create_power_curve(table_min[pdg],
+                                               table_max[pdg],
+                                               pdg_L->pd_pwr,
+                                               pdg_L->pd_step,
+                                               pdg_L->pd_points, tmpL, type);
+
+                       /* We are in a calibration
+                        * pier, no need to interpolate
+                        * between freq piers */
+                       if (pcinfo_L == pcinfo_R)
+                               continue;
+
+                       ath5k_create_power_curve(table_min[pdg],
+                                               table_max[pdg],
+                                               pdg_R->pd_pwr,
+                                               pdg_R->pd_step,
+                                               pdg_R->pd_points, tmpR, type);
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               /* Interpolate between curves
+                * of surounding freq piers to
+                * get the final curve for this
+                * pd gain. Re-use tmpL for interpolation
+                * output */
+               for (i = 0; (i < (u16) (table_max[pdg] - table_min[pdg])) &&
+               (i < AR5K_EEPROM_POWER_TABLE_SIZE); i++) {
+                       tmpL[i] = (u8) ath5k_get_interpolated_value(target,
+                                                       (s16) pcinfo_L->freq,
+                                                       (s16) pcinfo_R->freq,
+                                                       (s16) tmpL[i],
+                                                       (s16) tmpR[i]);
+               }
        }
 
+       /* Now we have a set of curves for this
+        * channel on tmpL (x range is table_max - table_min
+        * and y values are tmpL[pdg][]) sorted in the same
+        * order as EEPROM (because we've used the backmaping).
+        * So for RF5112 it's from higher power to lower power
+        * and for RF2413 it's from lower power to higher power.
+        * For RF5111 we only have one curve. */
+
+       /* Fill min and max power levels for this
+        * channel by interpolating the values on
+        * surounding channels to complete the dataset */
+       ah->ah_txpower.txp_min_pwr = ath5k_get_interpolated_value(target,
+                                       (s16) pcinfo_L->freq,
+                                       (s16) pcinfo_R->freq,
+                                       pcinfo_L->min_pwr, pcinfo_R->min_pwr);
+
+       ah->ah_txpower.txp_max_pwr = ath5k_get_interpolated_value(target,
+                                       (s16) pcinfo_L->freq,
+                                       (s16) pcinfo_R->freq,
+                                       pcinfo_L->max_pwr, pcinfo_R->max_pwr);
+
+       /* We are ready to go, fill PCDAC/PDADC
+        * table and write settings on hardware */
+       switch (type) {
+       case AR5K_PWRTABLE_LINEAR_PCDAC:
+               /* For RF5112 we can have one or two curves
+                * and each curve covers a certain power lvl
+                * range so we need to do some more processing */
+               ath5k_combine_linear_pcdac_curves(ah, table_min, table_max,
+                                               ee->ee_pd_gains[ee_mode]);
+
+               /* Set txp.offset so that we can
+                * match max power value with max
+                * table index */
+               ah->ah_txpower.txp_offset = 64 - (table_max[0] / 2);
+
+               /* Write settings on hw */
+               ath5k_setup_pcdac_table(ah);
+               break;
+       case AR5K_PWRTABLE_PWR_TO_PCDAC:
+               /* We are done for RF5111 since it has only
+                * one curve, just fit the curve on the table */
+               ath5k_fill_pwr_to_pcdac_table(ah, table_min, table_max);
+
+               /* No rate powertable adjustment for RF5111 */
+               ah->ah_txpower.txp_min_idx = 0;
+               ah->ah_txpower.txp_offset = 0;
+
+               /* Write settings on hw */
+               ath5k_setup_pcdac_table(ah);
+               break;
+       case AR5K_PWRTABLE_PWR_TO_PDADC:
+               /* Set PDADC boundaries and fill
+                * final PDADC table */
+               ath5k_combine_pwr_to_pdadc_curves(ah, table_min, table_max,
+                                               ee->ee_pd_gains[ee_mode]);
+
+               /* Write settings on hw */
+               ath5k_setup_pwr_to_pdadc_table(ah, pdg, pdg_curve_to_idx);
+
+               /* Set txp.offset, note that table_min
+                * can be negative */
+               ah->ah_txpower.txp_offset = table_min[0];
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+
+/*
+ * Per-rate tx power setting
+ *
+ * This is the code that sets the desired tx power (below
+ * maximum) on hw for each rate (we also have TPC that sets
+ * power per packet). We do that by providing an index on the
+ * PCDAC/PDADC table we set up.
+ */
+
+/*
+ * Set rate power table
+ *
+ * For now we only limit txpower based on maximum tx power
+ * supported by hw (what's inside rate_info). We need to limit
+ * this even more, based on regulatory domain etc.
+ *
+ * Rate power table contains indices to PCDAC/PDADC table (0.5dB steps)
+ * and is indexed as follows:
+ * rates[0] - rates[7] -> OFDM rates
+ * rates[8] - rates[14] -> CCK rates
+ * rates[15] -> XR rates (they all have the same power)
+ */
+static void
+ath5k_setup_rate_powertable(struct ath5k_hw *ah, u16 max_pwr,
+                       struct ath5k_rate_pcal_info *rate_info,
+                       u8 ee_mode)
+{
+       unsigned int i;
+       u16 *rates;
+
+       /* max_pwr is power level we got from driver/user in 0.5dB
+        * units, switch to 0.25dB units so we can compare */
+       max_pwr *= 2;
+       max_pwr = min(max_pwr, (u16) ah->ah_txpower.txp_max_pwr) / 2;
+
+       /* apply rate limits */
+       rates = ah->ah_txpower.txp_rates_power_table;
+
+       /* OFDM rates 6 to 24Mb/s */
+       for (i = 0; i < 5; i++)
+               rates[i] = min(max_pwr, rate_info->target_power_6to24);
+
+       /* Rest OFDM rates */
+       rates[5] = min(rates[0], rate_info->target_power_36);
+       rates[6] = min(rates[0], rate_info->target_power_48);
+       rates[7] = min(rates[0], rate_info->target_power_54);
+
+       /* CCK rates */
+       /* 1L */
+       rates[8] = min(rates[0], rate_info->target_power_6to24);
+       /* 2L */
+       rates[9] = min(rates[0], rate_info->target_power_36);
+       /* 2S */
+       rates[10] = min(rates[0], rate_info->target_power_36);
+       /* 5L */
+       rates[11] = min(rates[0], rate_info->target_power_48);
+       /* 5S */
+       rates[12] = min(rates[0], rate_info->target_power_48);
+       /* 11L */
+       rates[13] = min(rates[0], rate_info->target_power_54);
+       /* 11S */
+       rates[14] = min(rates[0], rate_info->target_power_54);
+
+       /* XR rates */
+       rates[15] = min(rates[0], rate_info->target_power_6to24);
+
+       /* CCK rates have different peak to average ratio
+        * so we have to tweak their power so that gainf
+        * correction works ok. For this we use OFDM to
+        * CCK delta from eeprom */
+       if ((ee_mode == AR5K_EEPROM_MODE_11G) &&
+       (ah->ah_phy_revision < AR5K_SREV_PHY_5212A))
+               for (i = 8; i <= 15; i++)
+                       rates[i] -= ah->ah_txpower.txp_cck_ofdm_gainf_delta;
+
+       ah->ah_txpower.txp_min_pwr = rates[7];
+       ah->ah_txpower.txp_max_pwr = rates[0];
+       ah->ah_txpower.txp_ofdm = rates[7];
+}
+
+
+/*
+ * Set transmition power
+ */
+int
+ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel,
+               u8 ee_mode, u8 txpower)
+{
+       struct ath5k_rate_pcal_info rate_info;
+       u8 type;
+       int ret;
+
+       ATH5K_TRACE(ah->ah_sc);
+       if (txpower > AR5K_TUNE_MAX_TXPOWER) {
+               ATH5K_ERR(ah->ah_sc, "invalid tx power: %u\n", txpower);
+               return -EINVAL;
+       }
+       if (txpower == 0)
+               txpower = AR5K_TUNE_DEFAULT_TXPOWER;
+
+       /* Reset TX power values */
+       memset(&ah->ah_txpower, 0, sizeof(ah->ah_txpower));
+       ah->ah_txpower.txp_tpc = AR5K_TUNE_TPC_TXPOWER;
+       ah->ah_txpower.txp_min_pwr = 0;
+       ah->ah_txpower.txp_max_pwr = AR5K_TUNE_MAX_TXPOWER;
+
+       /* Initialize TX power table */
+       switch (ah->ah_radio) {
+       case AR5K_RF5111:
+               type = AR5K_PWRTABLE_PWR_TO_PCDAC;
+               break;
+       case AR5K_RF5112:
+               type = AR5K_PWRTABLE_LINEAR_PCDAC;
+               break;
+       case AR5K_RF2413:
+       case AR5K_RF5413:
+       case AR5K_RF2316:
+       case AR5K_RF2317:
+       case AR5K_RF2425:
+               type = AR5K_PWRTABLE_PWR_TO_PDADC;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /* FIXME: Only on channel/mode change */
+       ret = ath5k_setup_channel_powertable(ah, channel, ee_mode, type);
+       if (ret)
+               return ret;
+
+       /* Limit max power if we have a CTL available */
+       ath5k_get_max_ctl_power(ah, channel);
+
+       /* FIXME: Tx power limit for this regdomain
+        * XXX: Mac80211/CRDA will do that anyway ? */
+
+       /* FIXME: Antenna reduction stuff */
+
+       /* FIXME: Limit power on turbo modes */
+
+       /* FIXME: TPC scale reduction */
+
+       /* Get surounding channels for per-rate power table
+        * calibration */
+       ath5k_get_rate_pcal_data(ah, channel, &rate_info);
+
+       /* Setup rate power table */
+       ath5k_setup_rate_powertable(ah, txpower, &rate_info, ee_mode);
+
+       /* Write rate power table on hw */
        ath5k_hw_reg_write(ah, AR5K_TXPOWER_OFDM(3, 24) |
                AR5K_TXPOWER_OFDM(2, 16) | AR5K_TXPOWER_OFDM(1, 8) |
                AR5K_TXPOWER_OFDM(0, 0), AR5K_PHY_TXPOWER_RATE1);
@@ -1536,26 +2566,34 @@ ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel,
                AR5K_TXPOWER_CCK(13, 16) | AR5K_TXPOWER_CCK(12, 8) |
                AR5K_TXPOWER_CCK(11, 0), AR5K_PHY_TXPOWER_RATE4);
 
-       if (ah->ah_txpower.txp_tpc)
+       /* FIXME: TPC support */
+       if (ah->ah_txpower.txp_tpc) {
                ath5k_hw_reg_write(ah, AR5K_PHY_TXPOWER_RATE_MAX_TPC_ENABLE |
                        AR5K_TUNE_MAX_TXPOWER, AR5K_PHY_TXPOWER_RATE_MAX);
-       else
+
+               ath5k_hw_reg_write(ah,
+                       AR5K_REG_MS(AR5K_TUNE_MAX_TXPOWER, AR5K_TPC_ACK) |
+                       AR5K_REG_MS(AR5K_TUNE_MAX_TXPOWER, AR5K_TPC_CTS) |
+                       AR5K_REG_MS(AR5K_TUNE_MAX_TXPOWER, AR5K_TPC_CHIRP),
+                       AR5K_TPC);
+       } else {
                ath5k_hw_reg_write(ah, AR5K_PHY_TXPOWER_RATE_MAX |
                        AR5K_TUNE_MAX_TXPOWER, AR5K_PHY_TXPOWER_RATE_MAX);
+       }
 
        return 0;
 }
 
-int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, unsigned int power)
+int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, u8 mode, u8 txpower)
 {
        /*Just a try M.F.*/
        struct ieee80211_channel *channel = &ah->ah_current_channel;
 
        ATH5K_TRACE(ah->ah_sc);
        ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_TXPOWER,
-               "changing txpower to %d\n", power);
+               "changing txpower to %d\n", txpower);
 
-       return ath5k_hw_txpower(ah, channel, power);
+       return ath5k_hw_txpower(ah, channel, mode, txpower);
 }
 
 #undef _ATH5K_PHY
index 2dc008e102268baf945fe2e8b064c99c5e7e7233..7070d1543cdc54359107b72ffe7032dbe16cf327 100644 (file)
 
 /*===5212 Specific PCU registers===*/
 
+/*
+ * Transmit power control register
+ */
+#define AR5K_TPC                       0x80e8
+#define AR5K_TPC_ACK                   0x0000003f      /* ack frames */
+#define AR5K_TPC_ACK_S                 0
+#define AR5K_TPC_CTS                   0x00003f00      /* cts frames */
+#define AR5K_TPC_CTS_S                 8
+#define AR5K_TPC_CHIRP                 0x003f0000      /* chirp frames */
+#define AR5K_TPC_CHIRP_S               16
+#define AR5K_TPC_DOPPLER               0x0f000000      /* doppler chirp span */
+#define AR5K_TPC_DOPPLER_S             24
+
 /*
  * XR (eXtended Range) mode register
  */
 #define        AR5K_PHY_TPC_RG1                0xa258
 #define        AR5K_PHY_TPC_RG1_NUM_PD_GAIN    0x0000c000
 #define        AR5K_PHY_TPC_RG1_NUM_PD_GAIN_S  14
+#define AR5K_PHY_TPC_RG1_PDGAIN_1      0x00030000
+#define AR5K_PHY_TPC_RG1_PDGAIN_1_S    16
+#define AR5K_PHY_TPC_RG1_PDGAIN_2      0x000c0000
+#define AR5K_PHY_TPC_RG1_PDGAIN_2_S    18
+#define AR5K_PHY_TPC_RG1_PDGAIN_3      0x00300000
+#define AR5K_PHY_TPC_RG1_PDGAIN_3_S    20
 
 #define        AR5K_PHY_TPC_RG5                        0xa26C
 #define        AR5K_PHY_TPC_RG5_PD_GAIN_OVERLAP        0x0000000F
index 685dc213edae01e8772b7f2fad6d7d612e3546ca..7a17d31b2fd9f762a2bd8870639e7e0ed8b3723c 100644 (file)
@@ -664,29 +664,35 @@ static void ath5k_hw_commit_eeprom_settings(struct ath5k_hw *ah,
                struct ieee80211_channel *channel, u8 *ant, u8 ee_mode)
 {
        struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom;
+       s16 cck_ofdm_pwr_delta;
 
-       /* Set CCK to OFDM power delta */
-       if (ah->ah_phy_revision >= AR5K_SREV_PHY_5212A) {
-               int16_t cck_ofdm_pwr_delta;
-
-               /* Adjust power delta for channel 14 */
-               if (channel->center_freq == 2484)
-                       cck_ofdm_pwr_delta =
-                               ((ee->ee_cck_ofdm_power_delta -
-                               ee->ee_scaled_cck_delta) * 2) / 10;
-               else
-                       cck_ofdm_pwr_delta =
-                               (ee->ee_cck_ofdm_power_delta * 2) / 10;
+       /* Adjust power delta for channel 14 */
+       if (channel->center_freq == 2484)
+               cck_ofdm_pwr_delta =
+                       ((ee->ee_cck_ofdm_power_delta -
+                       ee->ee_scaled_cck_delta) * 2) / 10;
+       else
+               cck_ofdm_pwr_delta =
+                       (ee->ee_cck_ofdm_power_delta * 2) / 10;
 
+       /* Set CCK to OFDM power delta on tx power
+        * adjustment register */
+       if (ah->ah_phy_revision >= AR5K_SREV_PHY_5212A) {
                if (channel->hw_value == CHANNEL_G)
                        ath5k_hw_reg_write(ah,
-                       AR5K_REG_SM((ee->ee_cck_ofdm_power_delta * -1),
+                       AR5K_REG_SM((ee->ee_cck_ofdm_gain_delta * -1),
                                AR5K_PHY_TX_PWR_ADJ_CCK_GAIN_DELTA) |
                        AR5K_REG_SM((cck_ofdm_pwr_delta * -1),
                                AR5K_PHY_TX_PWR_ADJ_CCK_PCDAC_INDEX),
                                AR5K_PHY_TX_PWR_ADJ);
                else
                        ath5k_hw_reg_write(ah, 0, AR5K_PHY_TX_PWR_ADJ);
+       } else {
+               /* For older revs we scale power on sw during tx power
+                * setup */
+               ah->ah_txpower.txp_cck_ofdm_pwr_delta = cck_ofdm_pwr_delta;
+               ah->ah_txpower.txp_cck_ofdm_gainf_delta =
+                                               ee->ee_cck_ofdm_gain_delta;
        }
 
        /* Set antenna idle switch table */
@@ -994,7 +1000,8 @@ int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode,
                /*
                 * Set TX power (FIXME)
                 */
-               ret = ath5k_hw_txpower(ah, channel, AR5K_TUNE_DEFAULT_TXPOWER);
+               ret = ath5k_hw_txpower(ah, channel, ee_mode,
+                                       AR5K_TUNE_DEFAULT_TXPOWER);
                if (ret)
                        return ret;