From: Felix Fietkau Date: Fri, 30 Jan 2009 02:52:21 +0000 (+0000) Subject: add some work-in-progress patches for ath5k stability and performance X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=5846455e82519d571d681f057b91517f23b680d7;p=openwrt%2Fstaging%2Fkaloz.git add some work-in-progress patches for ath5k stability and performance SVN-Revision: 14277 --- diff --git a/package/mac80211/patches/320-rx_dma_unmap.patch b/package/mac80211/patches/320-rx_dma_unmap.patch new file mode 100644 index 0000000000..f7cd62a7ce --- /dev/null +++ b/package/mac80211/patches/320-rx_dma_unmap.patch @@ -0,0 +1,37 @@ +When freeing rx dma descriptors, use the right buffer size. +Fixes kernel oopses on module unload on ixp4xx and most likely +other platforms as well. + +Signed-off-by: Felix Fietkau + +--- a/drivers/net/wireless/ath5k/base.c ++++ b/drivers/net/wireless/ath5k/base.c +@@ -308,6 +308,19 @@ static inline void ath5k_txbuf_free(stru + bf->skb = NULL; + } + ++static inline void ath5k_rxbuf_free(struct ath5k_softc *sc, ++ struct ath5k_buf *bf) ++{ ++ BUG_ON(!bf); ++ if (!bf->skb) ++ return; ++ pci_unmap_single(sc->pdev, bf->skbaddr, sc->rxbufsize, ++ PCI_DMA_FROMDEVICE); ++ dev_kfree_skb_any(bf->skb); ++ bf->skb = NULL; ++} ++ ++ + /* Queues setup */ + static struct ath5k_txq *ath5k_txq_setup(struct ath5k_softc *sc, + int qtype, int subtype); +@@ -1341,7 +1354,7 @@ ath5k_desc_free(struct ath5k_softc *sc, + list_for_each_entry(bf, &sc->txbuf, list) + ath5k_txbuf_free(sc, bf); + list_for_each_entry(bf, &sc->rxbuf, list) +- ath5k_txbuf_free(sc, bf); ++ ath5k_rxbuf_free(sc, bf); + + /* Free memory associated with all descriptors */ + pci_free_consistent(pdev, sc->desc_len, sc->desc, sc->desc_daddr); diff --git a/package/mac80211/patches/330-ath5k_eeprom.patch b/package/mac80211/patches/330-ath5k_eeprom.patch new file mode 100644 index 0000000000..a3a0285ae8 --- /dev/null +++ b/package/mac80211/patches/330-ath5k_eeprom.patch @@ -0,0 +1,494 @@ +Clean up the eeprom parsing code and prepare the pdgain +data for 2413, which will be required for power calibration code. +Also clean up some ugly line wrapping to make the code easier on +the eyes. + +Signed-off-by: Felix Fietkau + +--- a/drivers/net/wireless/ath5k/eeprom.c ++++ b/drivers/net/wireless/ath5k/eeprom.c +@@ -541,31 +541,30 @@ ath5k_eeprom_read_freq_list(struct ath5k + { + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + int o = *offset; +- int i = 0; ++ int i; + u8 freq1, freq2; + int ret; + u16 val; + ++ ee->ee_n_piers[mode] = 0; + while(i < max) { + AR5K_EEPROM_READ(o++, val); + +- freq1 = (val >> 8) & 0xff; +- freq2 = val & 0xff; +- +- if (freq1) { +- pc[i++].freq = ath5k_eeprom_bin2freq(ee, +- freq1, mode); +- ee->ee_n_piers[mode]++; +- } ++ freq1 = val & 0xff; ++ if (!freq1) ++ break; + +- if (freq2) { +- pc[i++].freq = ath5k_eeprom_bin2freq(ee, +- freq2, mode); +- ee->ee_n_piers[mode]++; +- } ++ pc[i++].freq = ath5k_eeprom_bin2freq(ee, ++ freq1, mode); ++ ee->ee_n_piers[mode]++; + +- if (!freq1 || !freq2) ++ freq2 = (val >> 8) & 0xff; ++ if (!freq2) + break; ++ ++ pc[i++].freq = ath5k_eeprom_bin2freq(ee, ++ freq2, mode); ++ ee->ee_n_piers[mode]++; + } + + /* return new offset */ +@@ -918,84 +917,46 @@ ath5k_cal_data_offset_2413(struct ath5k_ + * curves on eeprom. The final curve (higher power) has an extra + * point for better accuracy like RF5112. + */ ++ + static int +-ath5k_eeprom_read_pcal_info_2413(struct ath5k_hw *ah, int mode) ++ath5k_eeprom_parse_pcal_info_2413(struct ath5k_hw *ah, int mode, u32 offset, ++ struct ath5k_chan_pcal_info *chinfo) + { + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; +- struct ath5k_chan_pcal_info_rf2413 *chan_pcal_info; +- struct ath5k_chan_pcal_info *gen_chan_info; +- unsigned int i, c; +- u32 offset; ++ struct ath5k_chan_pcal_info_rf2413 *pcinfo; ++ unsigned int i; + int ret; + u16 val; +- u8 pd_gains = 0; +- +- if (ee->ee_x_gain[mode] & 0x1) pd_gains++; +- if ((ee->ee_x_gain[mode] >> 1) & 0x1) pd_gains++; +- if ((ee->ee_x_gain[mode] >> 2) & 0x1) pd_gains++; +- if ((ee->ee_x_gain[mode] >> 3) & 0x1) pd_gains++; +- ee->ee_pd_gains[mode] = pd_gains; ++ u8 pd_gains; + +- offset = ath5k_cal_data_offset_2413(ee, mode); +- ee->ee_n_piers[mode] = 0; +- switch (mode) { +- case AR5K_EEPROM_MODE_11A: +- if (!AR5K_EEPROM_HDR_11A(ee->ee_header)) +- return 0; +- +- ath5k_eeprom_init_11a_pcal_freq(ah, offset); +- offset += AR5K_EEPROM_N_5GHZ_CHAN / 2; +- gen_chan_info = ee->ee_pwr_cal_a; +- break; +- case AR5K_EEPROM_MODE_11B: +- if (!AR5K_EEPROM_HDR_11B(ee->ee_header)) +- return 0; +- +- ath5k_eeprom_init_11bg_2413(ah, mode, offset); +- offset += AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2; +- gen_chan_info = ee->ee_pwr_cal_b; +- break; +- case AR5K_EEPROM_MODE_11G: +- if (!AR5K_EEPROM_HDR_11G(ee->ee_header)) +- return 0; +- +- ath5k_eeprom_init_11bg_2413(ah, mode, offset); +- offset += AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2; +- gen_chan_info = ee->ee_pwr_cal_g; +- break; +- default: +- return -EINVAL; +- } ++ pd_gains = ee->ee_pd_gains[mode]; + + if (pd_gains == 0) + return 0; + + for (i = 0; i < ee->ee_n_piers[mode]; i++) { +- chan_pcal_info = &gen_chan_info[i].rf2413_info; ++ pcinfo = &chinfo[i].rf2413_info; + + /* + * Read pwr_i, pddac_i and the first + * 2 pd points (pwr, pddac) + */ + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pwr_i[0] = val & 0x1f; +- chan_pcal_info->pddac_i[0] = (val >> 5) & 0x7f; +- chan_pcal_info->pwr[0][0] = +- (val >> 12) & 0xf; ++ pcinfo->pwr_i[0] = val & 0x1f; ++ pcinfo->pddac_i[0] = (val >> 5) & 0x7f; ++ pcinfo->pwr[0][0] = (val >> 12) & 0xf; + + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pddac[0][0] = val & 0x3f; +- chan_pcal_info->pwr[0][1] = (val >> 6) & 0xf; +- chan_pcal_info->pddac[0][1] = +- (val >> 10) & 0x3f; ++ pcinfo->pddac[0][0] = val & 0x3f; ++ pcinfo->pwr[0][1] = (val >> 6) & 0xf; ++ pcinfo->pddac[0][1] = (val >> 10) & 0x3f; + + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pwr[0][2] = val & 0xf; +- chan_pcal_info->pddac[0][2] = +- (val >> 4) & 0x3f; ++ pcinfo->pwr[0][2] = val & 0xf; ++ pcinfo->pddac[0][2] = (val >> 4) & 0x3f; + +- chan_pcal_info->pwr[0][3] = 0; +- chan_pcal_info->pddac[0][3] = 0; ++ pcinfo->pwr[0][3] = 0; ++ pcinfo->pddac[0][3] = 0; + + if (pd_gains > 1) { + /* +@@ -1003,44 +964,36 @@ ath5k_eeprom_read_pcal_info_2413(struct + * so it only has 2 pd points. + * Continue wih pd gain 1. + */ +- chan_pcal_info->pwr_i[1] = (val >> 10) & 0x1f; ++ pcinfo->pwr_i[1] = (val >> 10) & 0x1f; + +- chan_pcal_info->pddac_i[1] = (val >> 15) & 0x1; ++ pcinfo->pddac_i[1] = (val >> 15) & 0x1; + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pddac_i[1] |= (val & 0x3F) << 1; ++ pcinfo->pddac_i[1] |= (val & 0x3F) << 1; + +- chan_pcal_info->pwr[1][0] = (val >> 6) & 0xf; +- chan_pcal_info->pddac[1][0] = +- (val >> 10) & 0x3f; ++ pcinfo->pwr[1][0] = (val >> 6) & 0xf; ++ pcinfo->pddac[1][0] = (val >> 10) & 0x3f; + + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pwr[1][1] = val & 0xf; +- chan_pcal_info->pddac[1][1] = +- (val >> 4) & 0x3f; +- chan_pcal_info->pwr[1][2] = +- (val >> 10) & 0xf; ++ pcinfo->pwr[1][1] = val & 0xf; ++ pcinfo->pddac[1][1] = (val >> 4) & 0x3f; ++ pcinfo->pwr[1][2] = (val >> 10) & 0xf; + +- chan_pcal_info->pddac[1][2] = +- (val >> 14) & 0x3; ++ pcinfo->pddac[1][2] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pddac[1][2] |= +- (val & 0xF) << 2; ++ pcinfo->pddac[1][2] |= (val & 0xF) << 2; + +- chan_pcal_info->pwr[1][3] = 0; +- chan_pcal_info->pddac[1][3] = 0; ++ pcinfo->pwr[1][3] = 0; ++ pcinfo->pddac[1][3] = 0; + } else if (pd_gains == 1) { + /* + * Pd gain 0 is the last one so + * read the extra point. + */ +- chan_pcal_info->pwr[0][3] = +- (val >> 10) & 0xf; ++ pcinfo->pwr[0][3] = (val >> 10) & 0xf; + +- chan_pcal_info->pddac[0][3] = +- (val >> 14) & 0x3; ++ pcinfo->pddac[0][3] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pddac[0][3] |= +- (val & 0xF) << 2; ++ pcinfo->pddac[0][3] |= (val & 0xF) << 2; + } + + /* +@@ -1048,105 +1001,159 @@ ath5k_eeprom_read_pcal_info_2413(struct + * as above. + */ + if (pd_gains > 2) { +- chan_pcal_info->pwr_i[2] = (val >> 4) & 0x1f; +- chan_pcal_info->pddac_i[2] = (val >> 9) & 0x7f; ++ pcinfo->pwr_i[2] = (val >> 4) & 0x1f; ++ pcinfo->pddac_i[2] = (val >> 9) & 0x7f; + + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pwr[2][0] = +- (val >> 0) & 0xf; +- chan_pcal_info->pddac[2][0] = +- (val >> 4) & 0x3f; +- chan_pcal_info->pwr[2][1] = +- (val >> 10) & 0xf; +- +- chan_pcal_info->pddac[2][1] = +- (val >> 14) & 0x3; +- AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pddac[2][1] |= +- (val & 0xF) << 2; +- +- chan_pcal_info->pwr[2][2] = +- (val >> 4) & 0xf; +- chan_pcal_info->pddac[2][2] = +- (val >> 8) & 0x3f; ++ pcinfo->pwr[2][0] = (val >> 0) & 0xf; ++ pcinfo->pddac[2][0] = (val >> 4) & 0x3f; ++ pcinfo->pwr[2][1] = (val >> 10) & 0xf; + +- chan_pcal_info->pwr[2][3] = 0; +- chan_pcal_info->pddac[2][3] = 0; ++ pcinfo->pddac[2][1] = (val >> 14) & 0x3; ++ AR5K_EEPROM_READ(offset++, val); ++ pcinfo->pddac[2][1] |= (val & 0xF) << 2; ++ ++ pcinfo->pwr[2][2] = (val >> 4) & 0xf; ++ pcinfo->pddac[2][2] = (val >> 8) & 0x3f; ++ ++ pcinfo->pwr[2][3] = 0; ++ pcinfo->pddac[2][3] = 0; + } else if (pd_gains == 2) { +- chan_pcal_info->pwr[1][3] = +- (val >> 4) & 0xf; +- chan_pcal_info->pddac[1][3] = +- (val >> 8) & 0x3f; ++ pcinfo->pwr[1][3] = (val >> 4) & 0xf; ++ pcinfo->pddac[1][3] = (val >> 8) & 0x3f; + } + + if (pd_gains > 3) { +- chan_pcal_info->pwr_i[3] = (val >> 14) & 0x3; ++ pcinfo->pwr_i[3] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pwr_i[3] |= ((val >> 0) & 0x7) << 2; ++ pcinfo->pwr_i[3] |= ((val >> 0) & 0x7) << 2; + +- chan_pcal_info->pddac_i[3] = (val >> 3) & 0x7f; +- chan_pcal_info->pwr[3][0] = +- (val >> 10) & 0xf; +- chan_pcal_info->pddac[3][0] = +- (val >> 14) & 0x3; ++ pcinfo->pddac_i[3] = (val >> 3) & 0x7f; ++ pcinfo->pwr[3][0] = (val >> 10) & 0xf; ++ pcinfo->pddac[3][0] = (val >> 14) & 0x3; + + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pddac[3][0] |= +- (val & 0xF) << 2; +- chan_pcal_info->pwr[3][1] = +- (val >> 4) & 0xf; +- chan_pcal_info->pddac[3][1] = +- (val >> 8) & 0x3f; ++ pcinfo->pddac[3][0] |= (val & 0xF) << 2; ++ pcinfo->pwr[3][1] = (val >> 4) & 0xf; ++ pcinfo->pddac[3][1] = (val >> 8) & 0x3f; + +- chan_pcal_info->pwr[3][2] = +- (val >> 14) & 0x3; ++ pcinfo->pwr[3][2] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pwr[3][2] |= +- ((val >> 0) & 0x3) << 2; ++ pcinfo->pwr[3][2] |= ((val >> 0) & 0x3) << 2; + +- chan_pcal_info->pddac[3][2] = +- (val >> 2) & 0x3f; +- chan_pcal_info->pwr[3][3] = +- (val >> 8) & 0xf; ++ pcinfo->pddac[3][2] = (val >> 2) & 0x3f; ++ pcinfo->pwr[3][3] = (val >> 8) & 0xf; + +- chan_pcal_info->pddac[3][3] = +- (val >> 12) & 0xF; ++ pcinfo->pddac[3][3] = (val >> 12) & 0xF; + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pddac[3][3] |= +- ((val >> 0) & 0x3) << 4; ++ pcinfo->pddac[3][3] |= ((val >> 0) & 0x3) << 4; + } else if (pd_gains == 3) { +- chan_pcal_info->pwr[2][3] = +- (val >> 14) & 0x3; ++ pcinfo->pwr[2][3] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); +- chan_pcal_info->pwr[2][3] |= +- ((val >> 0) & 0x3) << 2; ++ pcinfo->pwr[2][3] |= ((val >> 0) & 0x3) << 2; + +- chan_pcal_info->pddac[2][3] = +- (val >> 2) & 0x3f; ++ pcinfo->pddac[2][3] = (val >> 2) & 0x3f; + } ++ } ++ return 0; ++} ++ ++static int ++ath5k_eeprom_convert_pcal_info_2413(struct ath5k_hw *ah, int mode, ++ struct ath5k_chan_pcal_info *chinfo, ++ unsigned int *xgains) ++{ ++ struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; ++ struct ath5k_chan_pcal_info_rf2413 *pcinfo; ++ unsigned int i, j, k; + +- for (c = 0; c < pd_gains; c++) { +- /* Recreate pwr table for this channel using pwr steps */ +- chan_pcal_info->pwr[c][0] += chan_pcal_info->pwr_i[c] * 2; +- chan_pcal_info->pwr[c][1] += chan_pcal_info->pwr[c][0]; +- chan_pcal_info->pwr[c][2] += chan_pcal_info->pwr[c][1]; +- chan_pcal_info->pwr[c][3] += chan_pcal_info->pwr[c][2]; +- if (chan_pcal_info->pwr[c][3] == chan_pcal_info->pwr[c][2]) +- chan_pcal_info->pwr[c][3] = 0; +- +- /* Recreate pddac table for this channel using pddac steps */ +- chan_pcal_info->pddac[c][0] += chan_pcal_info->pddac_i[c]; +- chan_pcal_info->pddac[c][1] += chan_pcal_info->pddac[c][0]; +- chan_pcal_info->pddac[c][2] += chan_pcal_info->pddac[c][1]; +- chan_pcal_info->pddac[c][3] += chan_pcal_info->pddac[c][2]; +- if (chan_pcal_info->pddac[c][3] == chan_pcal_info->pddac[c][2]) +- chan_pcal_info->pddac[c][3] = 0; ++ /* prepare the raw values */ ++ for (i = 0; i < ee->ee_n_piers[mode]; i++) { ++ pcinfo = &chinfo[i].rf2413_info; ++ for (j = 0; j < ee->ee_pd_gains[mode]; j++) { ++ unsigned int idx = xgains[j]; ++ struct ath5k_pdgain_info *pd = &pcinfo->pdgains[idx]; ++ ++ /* one more point for the highest power (lowest gain) */ ++ if (j == ee->ee_pd_gains[mode] - 1) { ++ pd->n_vpd = AR5K_EEPROM_N_PD_POINTS; ++ } else { ++ pd->n_vpd = AR5K_EEPROM_N_PD_POINTS - 1; ++ } ++ ++ pd->vpd[0] = pcinfo->pddac_i[j]; ++ pd->pwr_t4[0] = 4 * pcinfo->pwr_i[j]; ++ for (k = 1; k < pd->n_vpd; k++) { ++ pd->pwr_t4[k] = pd->pwr_t4[k - 1] + 2 * pcinfo->pwr[j][k - 1]; ++ pd->vpd[k] = pd->vpd[k - 1] + pcinfo->pddac[j][k - 1]; ++ } + } + } + + return 0; + } + ++static int ++ath5k_eeprom_read_pcal_info_2413(struct ath5k_hw *ah, int mode) ++{ ++ struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; ++ struct ath5k_chan_pcal_info *chinfo; ++ unsigned int xgains[AR5K_EEPROM_N_PD_GAINS]; ++ u32 offset; ++ u8 pd_gains = 0; ++ int i, ret; ++ ++ memset(xgains, 0, sizeof(xgains)); ++ for (i = 0; i < AR5K_EEPROM_N_PD_GAINS; i++) { ++ int idx = AR5K_EEPROM_N_PD_GAINS - i - 1; ++ ++ if ((ee->ee_x_gain[mode] >> idx) & 0x1) ++ xgains[pd_gains++] = idx; ++ } ++ ee->ee_pd_gains[mode] = pd_gains; ++ ++ offset = ath5k_cal_data_offset_2413(ee, mode); ++ switch (mode) { ++ case AR5K_EEPROM_MODE_11A: ++ if (!AR5K_EEPROM_HDR_11A(ee->ee_header)) ++ return 0; ++ ++ ath5k_eeprom_init_11a_pcal_freq(ah, offset); ++ offset += AR5K_EEPROM_N_5GHZ_CHAN / 2; ++ chinfo = ee->ee_pwr_cal_a; ++ break; ++ case AR5K_EEPROM_MODE_11B: ++ if (!AR5K_EEPROM_HDR_11B(ee->ee_header)) ++ return 0; ++ ++ ath5k_eeprom_init_11bg_2413(ah, mode, offset); ++ offset += AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2; ++ chinfo = ee->ee_pwr_cal_b; ++ break; ++ case AR5K_EEPROM_MODE_11G: ++ if (!AR5K_EEPROM_HDR_11G(ee->ee_header)) ++ return 0; ++ ++ ath5k_eeprom_init_11bg_2413(ah, mode, offset); ++ offset += AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2; ++ chinfo = ee->ee_pwr_cal_g; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ ++ ret = ath5k_eeprom_parse_pcal_info_2413(ah, mode, offset, chinfo); ++ if (ret) ++ return ret; ++ ++ ret = ath5k_eeprom_convert_pcal_info_2413(ah, mode, chinfo, xgains); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ + /* + * Read per rate target power (this is the maximum tx power + * supported by the card). This info is used when setting +@@ -1264,6 +1271,7 @@ ath5k_eeprom_read_pcal_info(struct ath5k + else + read_pcal = ath5k_eeprom_read_pcal_info_5111; + ++ + for (mode = AR5K_EEPROM_MODE_11A; mode <= AR5K_EEPROM_MODE_11G; mode++) { + err = read_pcal(ah, mode); + if (err) +--- a/drivers/net/wireless/ath5k/eeprom.h ++++ b/drivers/net/wireless/ath5k/eeprom.h +@@ -265,15 +265,27 @@ struct ath5k_chan_pcal_info_rf5112 { + u8 pcdac_x3[AR5K_EEPROM_N_XPD3_POINTS]; + }; + ++ ++struct ath5k_pdgain_info { ++ u16 n_vpd; ++ u16 vpd[AR5K_EEPROM_N_PD_POINTS]; ++ s16 pwr_t4[AR5K_EEPROM_N_PD_POINTS]; ++}; ++ + struct ath5k_chan_pcal_info_rf2413 { ++ /* --- EEPROM VALUES --- */ + /* Starting pwr/pddac values */ +- s8 pwr_i[AR5K_EEPROM_N_PD_GAINS]; +- u8 pddac_i[AR5K_EEPROM_N_PD_GAINS]; ++ s8 pwr_i[AR5K_EEPROM_N_PD_GAINS]; ++ u8 pddac_i[AR5K_EEPROM_N_PD_GAINS]; + /* (pwr,pddac) points */ +- s8 pwr[AR5K_EEPROM_N_PD_GAINS] +- [AR5K_EEPROM_N_PD_POINTS]; +- u8 pddac[AR5K_EEPROM_N_PD_GAINS] +- [AR5K_EEPROM_N_PD_POINTS]; ++ s8 pwr[AR5K_EEPROM_N_PD_GAINS] ++ [AR5K_EEPROM_N_PD_POINTS]; ++ u8 pddac[AR5K_EEPROM_N_PD_GAINS] ++ [AR5K_EEPROM_N_PD_POINTS]; ++ ++ /* --- RAW VALUES --- */ ++ struct ath5k_pdgain_info pdgains ++ [AR5K_EEPROM_N_PD_GAINS]; + }; + + struct ath5k_chan_pcal_info { 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 + +--- a/drivers/net/wireless/ath5k/phy.c ++++ b/drivers/net/wireless/ath5k/phy.c +@@ -4,6 +4,7 @@ + * Copyright (c) 2004-2007 Reyk Floeter + * Copyright (c) 2006-2007 Nick Kossifidis + * Copyright (c) 2007-2008 Jiri Slaby ++ * Copyright (c) 2008-2009 Felix Fietkau + * + * 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)); +