diff options
Diffstat (limited to 'package/mac80211/patches/340-ath5k_txpower_2413.patch')
-rw-r--r-- | package/mac80211/patches/340-ath5k_txpower_2413.patch | 672 |
1 files changed, 672 insertions, 0 deletions
diff --git a/package/mac80211/patches/340-ath5k_txpower_2413.patch b/package/mac80211/patches/340-ath5k_txpower_2413.patch new file mode 100644 index 0000000000..7373d35530 --- /dev/null +++ b/package/mac80211/patches/340-ath5k_txpower_2413.patch @@ -0,0 +1,672 @@ +Implement the power curve interpolation, which is required for +proper tx on 2413 and newer RF designs. + +Signed-off-by: Felix Fietkau <nbd@openwrt.org> + +--- a/drivers/net/wireless/ath5k/phy.c ++++ b/drivers/net/wireless/ath5k/phy.c +@@ -4,6 +4,7 @@ + * Copyright (c) 2004-2007 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006-2007 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 +@@ -2383,31 +2384,449 @@ unsigned int ath5k_hw_get_def_antenna(st + */ + + /* +- * Initialize the tx power table (not fully implemented) ++ * find the lower and upper index of the values in the table surrounding the target value + */ +-static void ath5k_txpower_table(struct ath5k_hw *ah, +- struct ieee80211_channel *channel, s16 max_power) ++static void ++ath5k_get_table_index(const u16 *tbl, unsigned int tbl_sz, u16 target, ++ unsigned int idx[2]) + { +- unsigned int i, min, max, n; +- u16 txpower, *rates; ++ const u16 *ti; + +- rates = ah->ah_txpower.txp_rates; ++ if (target < tbl[0]) { ++ idx[0] = idx[1] = 0; ++ return; ++ } ++ ++ if (target > tbl[tbl_sz - 1]) { ++ idx[0] = idx[1] = tbl_sz - 1; ++ return; ++ } ++ ++ /* look for the surrounding values */ ++ for (ti = tbl; ti < &tbl[tbl_sz - 1]; ti++) { ++ ++ /* if the value is equal to the target, set lo = hi = index */ ++ if (*ti == target) { ++ idx[0] = idx[1] = ti - tbl; ++ return; ++ } ++ ++ /* if the target is between the current value and the next one, ++ * set lo = cur, hi = lo + 1 */ ++ if (target < ti[1]) { ++ idx[0] = ti - tbl; ++ idx[1] = idx[0] + 1; ++ return; ++ } ++ } ++} ++ ++/* find the lower and upper frequency info */ ++static void ++ath5k_get_freq_tables(struct ath5k_hw *ah, struct ieee80211_channel *channel, ++ struct ath5k_chan_pcal_info **pcinfo_l, ++ struct ath5k_chan_pcal_info **pcinfo_r, ++ struct ath5k_rate_pcal_info *rates) ++{ ++ struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; ++ struct ath5k_chan_pcal_info *pcinfo; ++ unsigned int idx_l, idx_r; ++ int mode, max, i; ++ unsigned int target = channel->center_freq; ++ struct ath5k_rate_pcal_info *rpinfo; ++ ++ if (!(channel->hw_value & CHANNEL_OFDM)) { ++ pcinfo = ee->ee_pwr_cal_b; ++ rpinfo = ee->ee_rate_tpwr_b; ++ mode = AR5K_EEPROM_MODE_11B; ++ } else if (channel->hw_value & CHANNEL_2GHZ) { ++ pcinfo = ee->ee_pwr_cal_g; ++ rpinfo = ee->ee_rate_tpwr_g; ++ mode = AR5K_EEPROM_MODE_11G; ++ } else { ++ pcinfo = ee->ee_pwr_cal_a; ++ rpinfo = ee->ee_rate_tpwr_a; ++ mode = AR5K_EEPROM_MODE_11A; ++ } ++ max = ee->ee_n_piers[mode] - 1; ++ ++ if (target < pcinfo[0].freq) { ++ idx_l = idx_r = 0; ++ goto done; ++ } ++ ++ if (target > pcinfo[max].freq) { ++ idx_l = idx_r = max; ++ goto done; ++ } ++ ++ /* look for the surrounding values */ ++ for (i = 0; i <= max; i++) { ++ ++ /* if the value is equal to the target, set lo = hi = index */ ++ if (pcinfo[i].freq == target) { ++ idx_l = idx_r = i; ++ goto done; ++ } ++ ++ /* if the target is between the current value and the next one, ++ * set lo = cur, hi = lo + 1 */ ++ if (target < pcinfo[i].freq) { ++ idx_l = i; ++ idx_r = idx_l + 1; ++ goto done; ++ } ++ } ++ ++done: ++ *pcinfo_l = &pcinfo[idx_l]; ++ *pcinfo_r = &pcinfo[idx_r]; ++ ++ if (!rates) ++ return; ++ ++ /* rate info minimum values */ ++ rates->freq = channel->center_freq; ++ rates->target_power_6to24 = ++ min(rpinfo[idx_l].target_power_6to24, ++ rpinfo[idx_r].target_power_6to24); ++ rates->target_power_36 = ++ min(rpinfo[idx_l].target_power_36, ++ rpinfo[idx_r].target_power_36); ++ rates->target_power_48 = ++ min(rpinfo[idx_l].target_power_48, ++ rpinfo[idx_r].target_power_48); ++ rates->target_power_54 = ++ min(rpinfo[idx_l].target_power_54, ++ rpinfo[idx_r].target_power_54); ++} ++ ++ ++/* Fill the VPD table for all indices between pmin and pmax */ ++static void ++ath5k_fill_vpdtable(s16 pmin, s16 pmax, const s16 *pwr, ++ const u16 *vpd, unsigned int intercepts, ++ u16 vpdtable[AR5K_EEPROM_POWER_TABLE_SIZE]) ++{ ++ unsigned int idx[2] = { 0, 0 }; ++ s16 cur_pwr = 2 * pmin; ++ int i; ++ ++ if (intercepts < 2) ++ return; ++ ++ for(i = 0; i <= (pmax - pmin); i++) { ++ ath5k_get_table_index(pwr, intercepts, cur_pwr, idx); ++ ++ if (!idx[1]) ++ idx[1] = 1; ++ ++ if (idx[0] == intercepts - 1) ++ idx[0] = intercepts - 2; ++ ++ if (pwr[idx[0]] == pwr[idx[1]]) ++ vpdtable[i] = vpd[idx[0]]; ++ else ++ vpdtable[i] = (((cur_pwr - pwr[idx[0]]) * vpd[idx[1]] + ++ (pwr[idx[1]] - cur_pwr) * vpd[idx[0]]) / ++ (pwr[idx[1]] - pwr[idx[0]])); ++ ++ cur_pwr += 2; ++ } ++} ++ ++static inline s16 ++ath5k_interpolate_signed(u16 ref, u16 ref_l, u16 ref_r, s16 val_l, s16 val_r) ++{ ++ if (ref_l == ref_r) ++ return val_l; ++ ++ return ((ref - ref_l)*val_r + (ref_r - ref)*val_l) / (ref_r - ref_l); ++} ++ ++static inline s16 ++ath5k_get_min_power_2413(struct ath5k_chan_pcal_info *pcinfo) ++{ ++ struct ath5k_pdgain_info *pd; ++ int i; ++ ++ /* backwards - highest pdgain == lowest power */ ++ for (i = AR5K_EEPROM_N_PD_GAINS - 1; i >= 0; i--) { ++ pd = &pcinfo->rf2413_info.pdgains[i]; ++ if (!pd->n_vpd) ++ continue; ++ ++ return pd->pwr_t4[0]; ++ } ++ return 0; ++} ++ ++static inline s16 ++ath5k_get_max_power_2413(struct ath5k_chan_pcal_info *pcinfo) ++{ ++ struct ath5k_pdgain_info *pd; ++ int i; ++ ++ /* forwards: lowest pdgain == highest power */ ++ for (i = 0; i < AR5K_EEPROM_N_PD_GAINS; i++) { ++ pd = &pcinfo->rf2413_info.pdgains[i]; ++ if (!pd->n_vpd) ++ continue; ++ ++ return pd->pwr_t4[pd->n_vpd]; ++ } ++ return 0; ++} ++ ++ ++ ++static int ++ath5k_txpower_table_2413(struct ath5k_hw *ah, struct ieee80211_channel *ch, ++ struct ath5k_chan_pcal_info *pcinfo_l, ++ struct ath5k_chan_pcal_info *pcinfo_r) ++{ ++ struct ath5k_pdgain_info *pd_l, *pd_r; ++ u16 gain_boundaries[4]; ++ u16 *xpd = ah->ah_txpower.txp_xpd; ++ int n_xpd = 0; ++ s16 pmin_t2[AR5K_EEPROM_N_PD_GAINS]; ++ s16 pmax_t2[AR5K_EEPROM_N_PD_GAINS]; ++ u16 *pdadc_out = ah->ah_txpower.txp_pcdac; ++ unsigned int gain_overlap; ++ unsigned int vpd_size, target_idx, max_idx; ++ unsigned int n_pdadc = 0; ++ u16 vpd_step; ++ u16 *pcdacL; ++ u16 *pcdacR; ++ int i, j, s; ++ u32 reg; ++ s16 ch_pmin, ch_pmax; ++ ++ gain_overlap = ath5k_hw_reg_read(ah, AR5K_PHY_TPC_RG5) & ++ AR5K_PHY_TPC_RG5_PD_GAIN_OVERLAP; ++ ++ /* loop backwards over pdgains (highest pdgain == lowest power) */ ++ for (i = AR5K_EEPROM_N_PD_GAINS - 1; i >= 0; i--) { ++ pd_l = &pcinfo_l->rf2413_info.pdgains[i]; ++ pd_r = &pcinfo_r->rf2413_info.pdgains[i]; ++ pcdacL = ah->ah_txpower.txp_rfdata.rf2413.pcdacL[n_xpd]; ++ pcdacR = ah->ah_txpower.txp_rfdata.rf2413.pcdacR[n_xpd]; ++ ++ if (!pd_l->n_vpd) ++ continue; ++ ++ xpd[n_xpd] = i; ++ ++ pmin_t2[n_xpd] = min(pd_l->pwr_t4[0], pd_r->pwr_t4[0]) / 2; ++ pmax_t2[n_xpd] = min(pd_l->pwr_t4[pd_l->n_vpd - 1], ++ pd_r->pwr_t4[pd_r->n_vpd - 1]) / 2; ++ ++ if ((u16) (pmax_t2[n_xpd] - pmin_t2[n_xpd]) > 64) ++ continue; ++ ++ /* fill vpd tables for left and right frequency info */ ++ ath5k_fill_vpdtable(pmin_t2[n_xpd], pmax_t2[n_xpd], ++ pd_l->pwr_t4, pd_l->vpd, pd_l->n_vpd, pcdacL); ++ ++ /* check if interpolation is necessary */ ++ if (pcinfo_l == pcinfo_r) ++ continue; ++ ++ ath5k_fill_vpdtable(pmin_t2[n_xpd], pmax_t2[n_xpd], ++ pd_r->pwr_t4, pd_r->vpd, pd_r->n_vpd, pcdacR); ++ ++ /* interpolate pcdac values, ++ * reuse pcdacL table for interpolation output */ ++ for (j = 0; j < (u16) (pmax_t2[n_xpd] - pmin_t2[n_xpd]); j++) { ++ pcdacL[j] = ath5k_interpolate_signed(ch->center_freq, ++ pcinfo_l->freq, pcinfo_r->freq, ++ (s16) pcdacL[j], (s16) pcdacR[j]); ++ } ++ n_xpd++; ++ } ++ ++ if (!n_xpd) ++ return 0; ++ ++ /* create final table */ ++ for (i = 0, n_pdadc = 0; i < n_xpd; i++) { ++ pcdacL = ah->ah_txpower.txp_rfdata.rf2413.pcdacL[i]; ++ ++ if (i == n_xpd - 1) { ++ /* 2 db boundary stretch */ ++ gain_boundaries[i] = pmax_t2[i] + 4; ++ } else { ++ gain_boundaries[i] = (pmax_t2[i] + pmin_t2[i + 1]) / 2; ++ } ++ ++ if (gain_boundaries[i] > AR5K_TUNE_MAX_TXPOWER) ++ gain_boundaries[i] = AR5K_TUNE_MAX_TXPOWER; ++ ++ /* find starting index */ ++ if (i == 0) ++ s = 0; ++ else ++ s = (gain_boundaries[i - 1] - pmin_t2[i]) - ++ gain_overlap; ++ ++ if (pcdacL[1] > pcdacL[0]) ++ vpd_step = pcdacL[1] - pcdacL[0]; ++ else ++ vpd_step = 1; ++ ++ /* if s is below 0, we need to extrapolate below this pdgain */ ++ while ((s < 0) && (n_pdadc < 128)) { ++ s16 tmp = pcdacL[0] + s * vpd_step; ++ pdadc_out[n_pdadc++] = (u16) ((tmp < 0) ? 0 : tmp); ++ s++; ++ } ++ ++ vpd_size = pmax_t2[i] - pmin_t2[i]; ++ target_idx = gain_boundaries[i] + gain_overlap - pmin_t2[i]; ++ max_idx = (target_idx < vpd_size) ? target_idx : vpd_size; ++ ++ while ((s < (s16) max_idx) && (n_pdadc < 128)) ++ pdadc_out[n_pdadc++] = pcdacL[s++]; ++ ++ /* need to extrapolate above this pdgain? */ ++ if (target_idx <= max_idx) ++ continue; ++ ++ if (pcdacL[vpd_size - 1] > pcdacL[vpd_size - 2]) ++ vpd_step = pcdacL[vpd_size - 1] - pcdacL[vpd_size - 2]; ++ else ++ vpd_step = 1; ++ ++ while ((s < (s16) target_idx) && (n_pdadc < 128)) { ++ int tmp = pcdacL[vpd_size - 1] + ++ (s - max_idx) * vpd_step; ++ pdadc_out[n_pdadc++] = (tmp > 127) ? 127 : tmp; ++ s++; ++ } ++ } ++ ++ while (i < AR5K_EEPROM_N_PD_GAINS) { ++ gain_boundaries[i] = gain_boundaries[i - 1]; ++ i++; ++ } ++ ++ while (n_pdadc < 128) { ++ pdadc_out[n_pdadc] = pdadc_out[n_pdadc - 1]; ++ n_pdadc++; ++ } ++ ++ /* select the right xpdgain curves */ ++ 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); ++ reg |= AR5K_REG_SM(n_xpd, AR5K_PHY_TPC_RG1_NUM_PD_GAIN); ++ switch(n_xpd) { ++ case 3: ++ reg |= AR5K_REG_SM(xpd[2], AR5K_PHY_TPC_RG1_PDGAIN_3); ++ /* fall through */ ++ case 2: ++ reg |= AR5K_REG_SM(xpd[1], AR5K_PHY_TPC_RG1_PDGAIN_2); ++ /* fall through */ ++ case 1: ++ reg |= AR5K_REG_SM(xpd[0], AR5K_PHY_TPC_RG1_PDGAIN_1); ++ break; ++ } ++ ath5k_hw_reg_write(ah, reg, AR5K_PHY_TPC_RG1); + +- txpower = AR5K_TUNE_DEFAULT_TXPOWER * 2; +- if (max_power > txpower) +- txpower = max_power > AR5K_TUNE_MAX_TXPOWER ? +- AR5K_TUNE_MAX_TXPOWER : max_power; ++ /* ++ * Write TX power values ++ */ ++ reg = AR5K_PHY_PCDAC_TXPOWER_BASE_2413; ++ for (i = 0; i < (AR5K_EEPROM_POWER_TABLE_SIZE / 2); i++) { ++ ath5k_hw_reg_write(ah, ++ ((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), reg); ++ reg += 4; ++ } ++ ++ ath5k_hw_reg_write(ah, ++ AR5K_REG_SM(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); ++ ++ ah->ah_txpower.txp_offset = pmin_t2[0]; ++ ++ /* look up power boundaries for this channel */ ++ ch_pmin = ath5k_get_min_power_2413(pcinfo_l); ++ ch_pmax = ath5k_get_max_power_2413(pcinfo_l); ++ ++ if (pcinfo_l != pcinfo_r) { ++ s16 pwr_r; ++ ++ pwr_r = ath5k_get_min_power_2413(pcinfo_r); ++ ch_pmin = ath5k_interpolate_signed(ch->center_freq, ++ pcinfo_l->freq, pcinfo_r->freq, ++ ch_pmin, pwr_r); ++ ++ pwr_r = ath5k_get_max_power_2413(pcinfo_r); ++ ch_pmax = ath5k_interpolate_signed(ch->center_freq, ++ pcinfo_l->freq, pcinfo_r->freq, ++ ch_pmax, pwr_r); ++ } ++ ah->ah_txpower.txp_min = ch_pmin; ++ ah->ah_txpower.txp_max = ch_pmax; + +- for (i = 0; i < AR5K_MAX_RATES; i++) +- rates[i] = txpower; ++ return 0; ++} + +- /* XXX setup target powers by rate */ ++static void ++ath5k_setup_rate_table(struct ath5k_hw *ah, u16 max_pwr, ++ struct ath5k_rate_pcal_info *rate_info) ++{ ++ unsigned int i; ++ u16 *rates; ++ ++ max_pwr *= 2; ++ max_pwr = min(max_pwr, (u16) ah->ah_txpower.txp_max); + ++ /* apply rate limits */ ++ rates = ah->ah_txpower.txp_rates; ++ for (i = 0; i < 5; i++) { ++ rates[i] = min(max_pwr, rate_info->target_power_6to24); ++ } ++ 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); ++ rates[8] = min(rates[0], rate_info->target_power_6to24); ++ rates[9] = min(rates[0], rate_info->target_power_36); ++ rates[10] = min(rates[0], rate_info->target_power_36); ++ rates[11] = min(rates[0], rate_info->target_power_48); ++ rates[12] = min(rates[0], rate_info->target_power_48); ++ rates[13] = min(rates[0], rate_info->target_power_54); ++ rates[14] = min(rates[0], rate_info->target_power_54); ++ ++ ah->ah_txpower.txp_tpc = max_pwr; + ah->ah_txpower.txp_min = rates[7]; +- ah->ah_txpower.txp_max = rates[0]; +- ah->ah_txpower.txp_ofdm = rates[0]; ++ ah->ah_txpower.txp_max = min(ah->ah_txpower.txp_max, ++ (s16) rate_info->target_power_36); ++ ah->ah_txpower.txp_ofdm = ah->ah_txpower.txp_max; ++} ++ ++static int ++ath5k_txpower_table(struct ath5k_hw *ah, struct ieee80211_channel *ch, ++ struct ath5k_chan_pcal_info *pcinfo_l, ++ struct ath5k_chan_pcal_info *pcinfo_r, ++ u16 max_pwr) ++{ ++ unsigned int i, min, max, n; + +- /* Calculate the power table */ + n = ARRAY_SIZE(ah->ah_txpower.txp_pcdac); + min = AR5K_EEPROM_PCDAC_START; + max = AR5K_EEPROM_PCDAC_STOP; +@@ -2418,51 +2837,64 @@ static void ath5k_txpower_table(struct a + #else + min; + #endif ++ ++ /* ++ * 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)); ++ } ++ return 0; + } + ++ + /* + * Set transmition power + */ +-int /*O.K. - txpower_table is unimplemented so this doesn't work*/ ++int + ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel, + unsigned int txpower) + { ++ struct ath5k_chan_pcal_info *pcinfo_l, *pcinfo_r; ++ struct ath5k_rate_pcal_info rate_info; + bool tpc = ah->ah_txpower.txp_tpc; +- unsigned int i; + + ATH5K_TRACE(ah->ah_sc); + if (txpower > AR5K_TUNE_MAX_TXPOWER) { + ATH5K_ERR(ah->ah_sc, "invalid tx power: %u\n", txpower); + return -EINVAL; + } +- +- /* +- * RF2413 for some reason can't +- * transmit anything if we call +- * this funtion, so we skip it +- * until we fix txpower. +- * +- * XXX: Assume same for RF2425 +- * to be safe. +- */ +- if ((ah->ah_radio == AR5K_RF2413) || (ah->ah_radio == AR5K_RF2425)) +- return 0; ++ if (txpower == 0) ++ txpower = AR5K_TUNE_MAX_TXPOWER; + + /* Reset TX power values */ + memset(&ah->ah_txpower, 0, sizeof(ah->ah_txpower)); + ah->ah_txpower.txp_tpc = tpc; ++ ah->ah_txpower.txp_min = 0; ++ ah->ah_txpower.txp_max = AR5K_TUNE_MAX_TXPOWER; + +- /* Initialize TX power table */ +- ath5k_txpower_table(ah, channel, txpower); ++ /* find matching frequency info */ ++ ath5k_get_freq_tables(ah, channel, &pcinfo_l, &pcinfo_r, &rate_info); ++ ath5k_setup_rate_table(ah, txpower, &rate_info); + +- /* +- * 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)); ++ /* Initialize TX power table */ ++ switch(ah->ah_radio) { ++ case AR5K_RF2413: ++ case AR5K_RF5413: ++ ath5k_txpower_table_2413(ah, channel, pcinfo_l, pcinfo_r); ++ break; ++ case AR5K_RF2425: ++ /* unimplemented */ ++ return 0; ++ default: ++ /* Default power table */ ++ ath5k_txpower_table(ah, channel, pcinfo_l, pcinfo_r, txpower); ++ break; + } + + ath5k_hw_reg_write(ah, AR5K_TXPOWER_OFDM(3, 24) | +@@ -2481,12 +2913,19 @@ ath5k_hw_txpower(struct ath5k_hw *ah, st + 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) ++ 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; + } +--- a/drivers/net/wireless/ath5k/ath5k.h ++++ b/drivers/net/wireless/ath5k/ath5k.h +@@ -207,7 +207,7 @@ + #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_MAX_TXPOWER 63 + #define AR5K_TUNE_DEFAULT_TXPOWER 30 + #define AR5K_TUNE_TPC_TXPOWER true + #define AR5K_TUNE_ANT_DIVERSITY true +@@ -1115,11 +1115,23 @@ struct ath5k_hw { + struct ath5k_gain ah_gain; + u32 ah_offset[AR5K_MAX_RF_BANKS]; + ++ + struct { +- u16 txp_pcdac[AR5K_EEPROM_POWER_TABLE_SIZE]; ++ union { ++ struct { ++ /* Temporary PCDAC tables for interpolation */ ++ u16 pcdacL[AR5K_EEPROM_N_PD_GAINS] ++ [AR5K_EEPROM_POWER_TABLE_SIZE]; ++ u16 pcdacR[AR5K_EEPROM_N_PD_GAINS] ++ [AR5K_EEPROM_POWER_TABLE_SIZE]; ++ } rf2413; ++ } txp_rfdata; ++ u16 txp_xpd[AR5K_EEPROM_N_XPD_PER_CHANNEL]; ++ u16 txp_pcdac[AR5K_EEPROM_POWER_TABLE_SIZE * 2]; + u16 txp_rates[AR5K_MAX_RATES]; + s16 txp_min; + s16 txp_max; ++ s16 txp_offset; + bool txp_tpc; + s16 txp_ofdm; + } ah_txpower; +--- a/drivers/net/wireless/ath5k/reg.h ++++ b/drivers/net/wireless/ath5k/reg.h +@@ -1549,6 +1549,15 @@ + + + /*===5212 Specific PCU registers===*/ ++#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 +@@ -2578,6 +2587,12 @@ + #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 +--- a/drivers/net/wireless/ath5k/desc.c ++++ b/drivers/net/wireless/ath5k/desc.c +@@ -194,6 +194,10 @@ static int ath5k_hw_setup_4word_tx_desc( + 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)); + |