svn commit: r219393 - in head/sys: conf dev/ath/ath_hal/ar5416 dev/ath/ath_hal/ar9002 modules/ath

Adrian Chadd adrian at FreeBSD.org
Tue Mar 8 07:00:00 UTC 2011


Author: adrian
Date: Tue Mar  8 06:59:59 2011
New Revision: 219393
URL: http://svn.freebsd.org/changeset/base/219393

Log:
  Implement open-loop TX power control (OLC) for Merlin (AR9280) and
  generally tidy up the TX power programming code.
  
  Enforce that the TX power offset for Merlin is -5 dBm, rather than
  any other value programmable in the EEPROM. This requires some
  further code to be ported over from ath9k, so until that is done
  and tested, fail to attach NICs whose TX power offset isn't -5
  dBm.
  
  This improves both legacy and HT transmission on my merlin board.
  It allows for stable MCS TX up to MCS15.
  
  Specifics:
  
  * Refactor out a bunch of the TX power calibration code -
    setting/obtaining the power detector / gain boundaries,
    programming the PDADC
  * Take the -5 dBm TX power offset into account on Merlin -
    "0" in the per-rate TX power register means -5 dBm, not
    0 dBm
  * When doing OLC
  * Enforce min (0) and max (AR5416_MAX_RATE_POWER) when fiddling
    with the TX power, to avoid the TX power values from wrapping
    when low.
  * Implement the 1 dBm cck power offset when doing OLC
  * Implement temperature compensation for 2.4ghz mode when doing OLC
  * Implement an AR9280 specific TX power calibration routine which
    includes the OLC twiddles, leaving the earlier chipset path
    (AR5416, AR9160) alone
  
  Whilst here, use these refactored routines for the AR9285 TX power
  calibration/programming code and enforce correct overflow/underflow
  handling when fiddling with TX power values.
  
  Obtained from:	linux ath9k

Added:
  head/sys/dev/ath/ath_hal/ar9002/ar9002phy.h   (contents, props changed)
  head/sys/dev/ath/ath_hal/ar9002/ar9280_olc.c   (contents, props changed)
  head/sys/dev/ath/ath_hal/ar9002/ar9280_olc.h   (contents, props changed)
Modified:
  head/sys/conf/files
  head/sys/dev/ath/ath_hal/ar5416/ar5416.h
  head/sys/dev/ath/ath_hal/ar5416/ar5416_attach.c
  head/sys/dev/ath/ath_hal/ar5416/ar5416_cal.c
  head/sys/dev/ath/ath_hal/ar5416/ar5416_reset.c
  head/sys/dev/ath/ath_hal/ar9002/ar9280.h
  head/sys/dev/ath/ath_hal/ar9002/ar9280_attach.c
  head/sys/dev/ath/ath_hal/ar9002/ar9285_reset.c
  head/sys/modules/ath/Makefile

Modified: head/sys/conf/files
==============================================================================
--- head/sys/conf/files	Tue Mar  8 03:04:07 2011	(r219392)
+++ head/sys/conf/files	Tue Mar  8 06:59:59 2011	(r219393)
@@ -759,6 +759,9 @@ dev/ath/ath_hal/ar9001/ar9160_attach.c o
 dev/ath/ath_hal/ar9002/ar9280_attach.c optional ath_hal | ath_ar9280 | \
 	ath_ar9285 \ 
 	compile-with "${NORMAL_C} -I$S/dev/ath -I$S/dev/ath/ath_hal"
+dev/ath/ath_hal/ar9002/ar9280_olc.c optional ath_hal | ath_ar9280 | \
+	ath_ar9285 \ 
+	compile-with "${NORMAL_C} -I$S/dev/ath -I$S/dev/ath/ath_hal"
 # ar9285 (depends on ar5416 and ar9280)
 dev/ath/ath_hal/ar9002/ar9285_attach.c optional ath_hal | ath_ar9285 \ 
 	compile-with "${NORMAL_C} -I$S/dev/ath -I$S/dev/ath/ath_hal"

Modified: head/sys/dev/ath/ath_hal/ar5416/ar5416.h
==============================================================================
--- head/sys/dev/ath/ath_hal/ar5416/ar5416.h	Tue Mar  8 03:04:07 2011	(r219392)
+++ head/sys/dev/ath/ath_hal/ar5416/ar5416.h	Tue Mar  8 06:59:59 2011	(r219393)
@@ -74,6 +74,16 @@ struct ath_hal_5416 {
 	void		(*ah_spurMitigate)(struct ath_hal *,
 			    const struct ieee80211_channel *);
 
+	/* optional open-loop tx power control related methods */
+	void		(*ah_olcInit)(struct ath_hal *);
+	void		(*ah_olcTempCompensation)(struct ath_hal *);
+
+	/* tx power control */
+	HAL_BOOL	(*ah_setPowerCalTable) (struct ath_hal *ah,
+			    struct ar5416eeprom *pEepData,
+			    const struct ieee80211_channel *chan,
+        		    int16_t *pTxPowerIndexOffset);
+
 	u_int       	ah_globaltxtimeout;	/* global tx timeout */
 	u_int		ah_gpioMask;
 	int		ah_hangs;		/* h/w hangs state */
@@ -93,6 +103,8 @@ struct ath_hal_5416 {
 
 	struct ar5416NfLimits nf_2g;
 	struct ar5416NfLimits nf_5g;
+
+	int		initPDADC;
 };
 #define	AH5416(_ah)	((struct ath_hal_5416 *)(_ah))
 
@@ -192,6 +204,7 @@ extern	HAL_RFGAIN ar5416GetRfgain(struct
 extern	HAL_BOOL ar5416Disable(struct ath_hal *ah);
 extern	HAL_BOOL ar5416ChipReset(struct ath_hal *ah,
 		const struct ieee80211_channel *);
+extern	int ar5416GetRegChainOffset(struct ath_hal *ah, int i);
 extern	HAL_BOOL ar5416SetBoardValues(struct ath_hal *,
 		const struct ieee80211_channel *);
 extern	HAL_BOOL ar5416SetResetReg(struct ath_hal *, uint32_t type);
@@ -215,6 +228,28 @@ extern	void ar5416GetTargetPowersLeg(str
 extern	void ar5416InitChainMasks(struct ath_hal *ah);
 extern	void ar5416RestoreChainMask(struct ath_hal *ah);
 
+/* TX power setup related routines in ar5416_reset.c */
+extern	HAL_BOOL getLowerUpperIndex(uint8_t target, uint8_t *pList,
+	uint16_t listSize,  uint16_t *indexL, uint16_t *indexR);
+extern	void ar5416GetGainBoundariesAndPdadcs(struct ath_hal *ah,
+	const struct ieee80211_channel *chan, CAL_DATA_PER_FREQ *pRawDataSet,
+	uint8_t * bChans, uint16_t availPiers,
+	uint16_t tPdGainOverlap, int16_t *pMinCalPower,
+	uint16_t * pPdGainBoundaries, uint8_t * pPDADCValues,
+	uint16_t numXpdGains);
+extern	void ar5416SetGainBoundariesClosedLoop(struct ath_hal *ah,
+	int regChainOffset, uint16_t pdGainOverlap_t2,
+	uint16_t gainBoundaries[]);
+extern	uint16_t ar5416GetXpdGainValues(struct ath_hal *ah, uint16_t xpdMask,
+	uint16_t xpdGainValues[]);
+extern	void ar5416WriteDetectorGainBiases(struct ath_hal *ah,
+	uint16_t numXpdGain, uint16_t xpdGainValues[]);
+extern	void ar5416WritePdadcValues(struct ath_hal *ah, int regChainOffset,
+	uint8_t pdadcValues[]);
+extern	HAL_BOOL ar5416SetPowerCalTable(struct ath_hal *ah,
+	struct ar5416eeprom *pEepData, const struct ieee80211_channel *chan,
+	int16_t *pTxPowerIndexOffset);
+
 extern	HAL_BOOL ar5416StopTxDma(struct ath_hal *ah, u_int q);
 extern	HAL_BOOL ar5416SetupTxDesc(struct ath_hal *ah, struct ath_desc *ds,
 		u_int pktLen, u_int hdrLen, HAL_PKT_TYPE type, u_int txPower,

Modified: head/sys/dev/ath/ath_hal/ar5416/ar5416_attach.c
==============================================================================
--- head/sys/dev/ath/ath_hal/ar5416/ar5416_attach.c	Tue Mar  8 03:04:07 2011	(r219392)
+++ head/sys/dev/ath/ath_hal/ar5416/ar5416_attach.c	Tue Mar  8 06:59:59 2011	(r219393)
@@ -62,6 +62,19 @@ ar5416AniSetup(struct ath_hal *ah)
 }
 
 /*
+ * AR5416 doesn't do OLC or temperature compensation.
+ */
+static void
+ar5416olcInit(struct ath_hal *ah)
+{
+}
+
+static void
+ar5416olcTempCompensation(struct ath_hal *ah)
+{
+}
+
+/*
  * Attach for an AR5416 part.
  */
 void
@@ -161,8 +174,15 @@ ar5416InitState(struct ath_hal_5416 *ahp
 #endif
 	ahp->ah_priv.ah_getChipPowerLimits = ar5416GetChipPowerLimits;
 
+	/* Internal ops */
 	AH5416(ah)->ah_writeIni		= ar5416WriteIni;
 	AH5416(ah)->ah_spurMitigate	= ar5416SpurMitigate;
+
+	/* Internal TX power control related operations */
+	AH5416(ah)->ah_olcInit = ar5416olcInit;
+	AH5416(ah)->ah_olcTempCompensation	= ar5416olcTempCompensation;
+	AH5416(ah)->ah_setPowerCalTable	= ar5416SetPowerCalTable;
+
 	/*
 	 * Start by setting all Owl devices to 2x2
 	 */

Modified: head/sys/dev/ath/ath_hal/ar5416/ar5416_cal.c
==============================================================================
--- head/sys/dev/ath/ath_hal/ar5416/ar5416_cal.c	Tue Mar  8 03:04:07 2011	(r219392)
+++ head/sys/dev/ath/ath_hal/ar5416/ar5416_cal.c	Tue Mar  8 06:59:59 2011	(r219393)
@@ -468,6 +468,9 @@ ar5416PerCalibrationN(struct ath_hal *ah
 
 	/* Do NF cal only at longer intervals */
 	if (longcal) {
+		/* Do temperature compensation if the chipset needs it */
+		AH5416(ah)->ah_olcTempCompensation(ah);
+
 		/*
 		 * Get the value from the previous NF cal
 		 * and update the history buffer.

Modified: head/sys/dev/ath/ath_hal/ar5416/ar5416_reset.c
==============================================================================
--- head/sys/dev/ath/ath_hal/ar5416/ar5416_reset.c	Tue Mar  8 03:04:07 2011	(r219392)
+++ head/sys/dev/ath/ath_hal/ar5416/ar5416_reset.c	Tue Mar  8 06:59:59 2011	(r219393)
@@ -61,24 +61,12 @@ static HAL_BOOL ar5416SetPowerPerRateTab
 	uint16_t cfgCtl, uint16_t AntennaReduction,
 	uint16_t twiceMaxRegulatoryPower, 
 	uint16_t powerLimit);
-static HAL_BOOL ar5416SetPowerCalTable(struct ath_hal *ah,
-	struct ar5416eeprom *pEepData,
-	const struct ieee80211_channel *chan,
-	int16_t *pTxPowerIndexOffset);
 static uint16_t ar5416GetMaxEdgePower(uint16_t freq,
 	CAL_CTL_EDGES *pRdEdgesPower, HAL_BOOL is2GHz);
 
 static int16_t interpolate(uint16_t target, uint16_t srcLeft,
 	uint16_t srcRight, int16_t targetLeft, int16_t targetRight);
 static void ar5416Set11nRegs(struct ath_hal *ah, const struct ieee80211_channel *chan);
-static void ar5416GetGainBoundariesAndPdadcs(struct ath_hal *ah, 
-	const struct ieee80211_channel *chan, CAL_DATA_PER_FREQ *pRawDataSet,
-	uint8_t * bChans, uint16_t availPiers,
-	uint16_t tPdGainOverlap, int16_t *pMinCalPower,
-	uint16_t * pPdGainBoundaries, uint8_t * pPDADCValues,
-	uint16_t numXpdGains);
-static HAL_BOOL getLowerUpperIndex(uint8_t target, uint8_t *pList,
-	uint16_t listSize,  uint16_t *indexL, uint16_t *indexR);
 static HAL_BOOL ar5416FillVpdTable(uint8_t pwrMin, uint8_t pwrMax,
 	uint8_t *pPwrList, uint8_t *pVpdList,
 	uint16_t numIntercepts, uint8_t *pRetVpdList);
@@ -224,6 +212,7 @@ ar5416Reset(struct ath_hal *ah, HAL_OPMO
 	 * before any radio register twiddling is done.
 	 */
 	ar5416InitChainMasks(ah);
+	AH5416(ah)->ah_olcInit(ah);
 
 	/* Setup the transmit power values. */
 	if (!ah->ah_setTxPower(ah, chan, rfXpdGain)) {
@@ -848,7 +837,7 @@ ar5416SetTransmitPower(struct ath_hal *a
         return AH_FALSE;
     }
 
-    if (!ar5416SetPowerCalTable(ah,  pEepData, chan, &txPowerIndexOffset)) {
+    if (!AH5416(ah)->ah_setPowerCalTable(ah,  pEepData, chan, &txPowerIndexOffset)) {
         HALDEBUG(ah, HAL_DEBUG_ANY, "%s: unable to set power table\n",
 	    __func__);
         return AH_FALSE;
@@ -879,9 +868,71 @@ ar5416SetTransmitPower(struct ath_hal *a
     }
 
 #ifdef AH_EEPROM_DUMP
+    /*
+     * Dump the rate array whilst it represents the intended dBm*2
+     * values versus what's being adjusted before being programmed
+     * in. Keep this in mind if you code up this function and enable
+     * this debugging; the values won't necessarily be what's being
+     * programmed into the hardware.
+     */
     ar5416PrintPowerPerRate(ah, ratesArray);
 #endif
 
+    /*
+     * Merlin and later have a power offset, so subtract
+     * pwr_table_offset * 2 from each value. The default
+     * power offset is -5 dBm - ie, a register value of 0
+     * equates to a TX power of -5 dBm.
+     */
+    if (AR_SREV_MERLIN_20_OR_LATER(ah)) {
+        int8_t pwr_table_offset;
+
+	(void) ath_hal_eepromGet(ah, AR_EEP_PWR_TABLE_OFFSET,
+	    &pwr_table_offset);
+	/* Underflow power gets clamped at raw value 0 */
+	/* Overflow power gets camped at AR5416_MAX_RATE_POWER */
+	for (i = 0; i < N(ratesArray); i++) {
+		/*
+		 * + pwr_table_offset is in dBm
+		 * + ratesArray is in 1/2 dBm
+		 */
+		ratesArray[i] -= (pwr_table_offset * 2);
+		if (ratesArray[i] < 0)
+			ratesArray[i] = 0;
+		else if (ratesArray[i] > AR5416_MAX_RATE_POWER)
+		    ratesArray[i] = AR5416_MAX_RATE_POWER;
+	}
+    }
+
+    /*
+     * Adjust rates for OLC where needed
+     *
+     * The following CCK rates need adjusting when doing 2.4ghz
+     * CCK transmission.
+     *
+     * + rate2s, rate2l, rate1l, rate11s, rate11l, rate5_5s, rate5_5l
+     * + rateExtCck, rateDupCck
+     *
+     * They're adjusted here regardless. The hardware then gets
+     * programmed as needed. 5GHz operation doesn't program in CCK
+     * rates for legacy mode but they seem to be initialised for
+     * HT40 regardless of channel type.
+     */
+    if (AR_SREV_MERLIN_20_OR_LATER(ah) &&
+	    ath_hal_eepromGetFlag(ah, AR_EEP_OL_PWRCTRL)) {
+        int adj[] = {
+	              rate2s, rate2l, rate1l, rate11s, rate11l,
+	              rate5_5s, rate5_5l, rateExtCck, rateDupCck
+		    };
+        int cck_ofdm_delta = 2;
+	int i;
+	for (i = 0; i < N(adj); i++) {
+            ratesArray[i] -= cck_ofdm_delta;
+	    if (ratesArray[i] < 0)
+	        ratesArray[i] = 0;
+        }
+    }
+
     /* Write the OFDM power per rate set */
     OS_REG_WRITE(ah, AR_PHY_POWER_TX_RATE1,
         POW_SM(ratesArray[rate18mb], 24)
@@ -1297,7 +1348,32 @@ ar5416SetDefGainValues(struct ath_hal *a
 	}
 }
 
+/*
+ * Get the register chain offset for the given chain.
+ *
+ * Take into account the register chain swapping with AR5416 v2.0.
+ *
+ * XXX make sure that the reg chain swapping is only done for
+ * XXX AR5416 v2.0 or greater, and not later chips?
+ */
+int
+ar5416GetRegChainOffset(struct ath_hal *ah, int i)
+{
+	int regChainOffset;
+
+	if (AR_SREV_OWL_20_OR_LATER(ah) && 
+	    (AH5416(ah)->ah_rx_chainmask == 0x5 ||
+	    AH5416(ah)->ah_tx_chainmask == 0x5) && (i != 0)) {
+		/* Regs are swapped from chain 2 to 1 for 5416 2_0 with 
+		 * only chains 0 and 2 populated 
+		 */
+		regChainOffset = (i == 1) ? 0x2000 : 0x1000;
+	} else {
+		regChainOffset = i * 0x1000;
+	}
 
+	return regChainOffset;
+}
 
 /*
  * Read EEPROM header info and program the device for correct operation
@@ -1323,16 +1399,7 @@ ar5416SetBoardValues(struct ath_hal *ah,
 	   if (AR_SREV_MERLIN(ah)) {
 		if (i >= 2) break;
 	   }
-       	   if (AR_SREV_OWL_20_OR_LATER(ah) &&
-            (AH5416(ah)->ah_rx_chainmask == 0x5 ||
-	     AH5416(ah)->ah_tx_chainmask == 0x5) && i != 0) {
-            /* Regs are swapped from chain 2 to 1 for 5416 2_0 with 
-             * only chains 0 and 2 populated 
-             */
-            regChainOffset = (i == 1) ? 0x2000 : 0x1000;
-        } else {
-            regChainOffset = i * 0x1000;
-        }
+	regChainOffset = ar5416GetRegChainOffset(ah, i);
 
         OS_REG_WRITE(ah, AR_PHY_SWITCH_CHAIN_0 + regChainOffset, pModal->antCtrlChain[i]);
 
@@ -1857,6 +1924,120 @@ ar5416GetTargetPowersLeg(struct ath_hal 
     }
 }
 
+/*
+ * Set the gain boundaries for the given radio chain.
+ *
+ * The gain boundaries tell the hardware at what point in the
+ * PDADC array to "switch over" from one PD gain setting
+ * to another. There's also a gain overlap between two
+ * PDADC array gain curves where there's valid PD values
+ * for 2 gain settings.
+ *
+ * The hardware uses the gain overlap and gain boundaries
+ * to determine which gain curve to use for the given
+ * target TX power.
+ */
+void
+ar5416SetGainBoundariesClosedLoop(struct ath_hal *ah, int regChainOffset,
+    uint16_t pdGainOverlap_t2, uint16_t gainBoundaries[])
+{
+	OS_REG_WRITE(ah, AR_PHY_TPCRG5 + regChainOffset,
+	    SM(pdGainOverlap_t2, AR_PHY_TPCRG5_PD_GAIN_OVERLAP) |
+	    SM(gainBoundaries[0], AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_1)  |
+	    SM(gainBoundaries[1], AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_2)  |
+	    SM(gainBoundaries[2], AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_3)  |
+	    SM(gainBoundaries[3], AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_4));
+}
+
+/*
+ * Get the gain values and the number of gain levels given
+ * in xpdMask.
+ *
+ * The EEPROM xpdMask determines which power detector gain
+ * levels were used during calibration. Each of these mask
+ * bits maps to a fixed gain level in hardware.
+ */
+uint16_t
+ar5416GetXpdGainValues(struct ath_hal *ah, uint16_t xpdMask,
+    uint16_t xpdGainValues[])
+{
+    int i;
+    uint16_t numXpdGain = 0;
+
+    for (i = 1; i <= AR5416_PD_GAINS_IN_MASK; i++) {
+        if ((xpdMask >> (AR5416_PD_GAINS_IN_MASK - i)) & 1) {
+            if (numXpdGain >= AR5416_NUM_PD_GAINS) {
+                HALASSERT(0);
+                break;
+            }
+            xpdGainValues[numXpdGain] = (uint16_t)(AR5416_PD_GAINS_IN_MASK - i);
+            numXpdGain++;
+        }
+    }
+    return numXpdGain;
+}
+
+/*
+ * Write the detector gain and biases.
+ *
+ * There are four power detector gain levels. The xpdMask in the EEPROM
+ * determines which power detector gain levels have TX power calibration
+ * data associated with them. This function writes the number of
+ * PD gain levels and their values into the hardware.
+ *
+ * This is valid for all TX chains - the calibration data itself however
+ * will likely differ per-chain.
+ */
+void
+ar5416WriteDetectorGainBiases(struct ath_hal *ah, uint16_t numXpdGain,
+    uint16_t xpdGainValues[])
+{
+    OS_REG_WRITE(ah, AR_PHY_TPCRG1, (OS_REG_READ(ah, AR_PHY_TPCRG1) & 
+    	~(AR_PHY_TPCRG1_NUM_PD_GAIN | AR_PHY_TPCRG1_PD_GAIN_1 |
+	AR_PHY_TPCRG1_PD_GAIN_2 | AR_PHY_TPCRG1_PD_GAIN_3)) | 
+	SM(numXpdGain - 1, AR_PHY_TPCRG1_NUM_PD_GAIN) |
+	SM(xpdGainValues[0], AR_PHY_TPCRG1_PD_GAIN_1 ) |
+	SM(xpdGainValues[1], AR_PHY_TPCRG1_PD_GAIN_2) |
+	SM(xpdGainValues[2],  AR_PHY_TPCRG1_PD_GAIN_3));
+}
+
+/*
+ * Write the PDADC array to the given chain offset.
+ *
+ * The 32 PDADC registers are written without any care about
+ * their contents - so if various chips treat values as "special",
+ * this routine will not care.
+ */
+void
+ar5416WritePdadcValues(struct ath_hal *ah, int regChainOffset,
+    uint8_t pdadcValues[])
+{
+	int regOffset;
+	int j;
+	int reg32;
+
+	regOffset = AR_PHY_BASE + (672 << 2) + regChainOffset;
+
+	for (j = 0; j < 32; j++) {
+		reg32 = ((pdadcValues[4*j + 0] & 0xFF) << 0)  |
+		    ((pdadcValues[4*j + 1] & 0xFF) << 8)  |
+		    ((pdadcValues[4*j + 2] & 0xFF) << 16) |
+		    ((pdadcValues[4*j + 3] & 0xFF) << 24) ;
+		OS_REG_WRITE(ah, regOffset, reg32);
+#ifdef PDADC_DUMP
+		ath_hal_printf(ah, "PDADC: Chain %d | PDADC %3d Value %3d |"
+		    " PDADC %3d Value %3d | PDADC %3d Value %3d | PDADC %3d"
+		    " Value %3d |\n",
+		    i,
+		    4*j, pdadcValues[4*j],
+		    4*j+1, pdadcValues[4*j + 1],
+		    4*j+2, pdadcValues[4*j + 2],
+		    4*j+3, pdadcValues[4*j + 3]);
+#endif
+		regOffset += 4;
+	}
+}
+
 /**************************************************************
  * ar5416SetPowerCalTable
  *
@@ -1864,7 +2045,7 @@ ar5416GetTargetPowersLeg(struct ath_hal 
  * points as well as from the nearest pier(s) to get a power detector
  * linear voltage to power level table.
  */
-static HAL_BOOL
+HAL_BOOL
 ar5416SetPowerCalTable(struct ath_hal *ah, struct ar5416eeprom *pEepData,
 	const struct ieee80211_channel *chan, int16_t *pTxPowerIndexOffset)
 {
@@ -1873,11 +2054,11 @@ ar5416SetPowerCalTable(struct ath_hal *a
     uint16_t pdGainOverlap_t2;
     static uint8_t  pdadcValues[AR5416_NUM_PDADC_VALUES];
     uint16_t gainBoundaries[AR5416_PD_GAINS_IN_MASK];
-    uint16_t numPiers, i, j;
+    uint16_t numPiers, i;
     int16_t  tMinCalPower;
     uint16_t numXpdGain, xpdMask;
     uint16_t xpdGainValues[AR5416_NUM_PD_GAINS];
-    uint32_t reg32, regOffset, regChainOffset;
+    uint32_t regChainOffset;
 
     OS_MEMZERO(xpdGainValues, sizeof(xpdGainValues));
     
@@ -1897,36 +2078,14 @@ ar5416SetPowerCalTable(struct ath_hal *a
         numPiers = AR5416_NUM_5G_CAL_PIERS;
     }
 
-    numXpdGain = 0;
     /* Calculate the value of xpdgains from the xpdGain Mask */
-    for (i = 1; i <= AR5416_PD_GAINS_IN_MASK; i++) {
-        if ((xpdMask >> (AR5416_PD_GAINS_IN_MASK - i)) & 1) {
-            if (numXpdGain >= AR5416_NUM_PD_GAINS) {
-                HALASSERT(0);
-                break;
-            }
-            xpdGainValues[numXpdGain] = (uint16_t)(AR5416_PD_GAINS_IN_MASK - i);
-            numXpdGain++;
-        }
-    }
+    numXpdGain = ar5416GetXpdGainValues(ah, xpdMask, xpdGainValues);
     
     /* Write the detector gain biases and their number */
-    OS_REG_WRITE(ah, AR_PHY_TPCRG1, (OS_REG_READ(ah, AR_PHY_TPCRG1) & 
-    	~(AR_PHY_TPCRG1_NUM_PD_GAIN | AR_PHY_TPCRG1_PD_GAIN_1 | AR_PHY_TPCRG1_PD_GAIN_2 | AR_PHY_TPCRG1_PD_GAIN_3)) | 
-	SM(numXpdGain - 1, AR_PHY_TPCRG1_NUM_PD_GAIN) | SM(xpdGainValues[0], AR_PHY_TPCRG1_PD_GAIN_1 ) |
-	SM(xpdGainValues[1], AR_PHY_TPCRG1_PD_GAIN_2) | SM(xpdGainValues[2],  AR_PHY_TPCRG1_PD_GAIN_3));
+    ar5416WriteDetectorGainBiases(ah, numXpdGain, xpdGainValues);
 
     for (i = 0; i < AR5416_MAX_CHAINS; i++) {
-
-            if (AR_SREV_OWL_20_OR_LATER(ah) && 
-            ( AH5416(ah)->ah_rx_chainmask == 0x5 || AH5416(ah)->ah_tx_chainmask == 0x5) && (i != 0)) {
-            /* Regs are swapped from chain 2 to 1 for 5416 2_0 with 
-             * only chains 0 and 2 populated 
-             */
-            regChainOffset = (i == 1) ? 0x2000 : 0x1000;
-        } else {
-            regChainOffset = i * 0x1000;
-        }
+	regChainOffset = ar5416GetRegChainOffset(ah, i);
 
         if (pEepData->baseEepHeader.txMask & (1 << i)) {
             if (IEEE80211_IS_CHAN_2GHZ(chan)) {
@@ -1935,47 +2094,20 @@ ar5416SetPowerCalTable(struct ath_hal *a
                 pRawDataset = pEepData->calPierData5G[i];
             }
 
-            ar5416GetGainBoundariesAndPdadcs(ah,  chan, pRawDataset,
+            /* Fetch the gain boundaries and the PDADC values */
+	    ar5416GetGainBoundariesAndPdadcs(ah,  chan, pRawDataset,
                                              pCalBChans, numPiers,
                                              pdGainOverlap_t2,
                                              &tMinCalPower, gainBoundaries,
                                              pdadcValues, numXpdGain);
 
             if ((i == 0) || AR_SREV_OWL_20_OR_LATER(ah)) {
-                /*
-                 * Note the pdadc table may not start at 0 dBm power, could be
-                 * negative or greater than 0.  Need to offset the power
-                 * values by the amount of minPower for griffin
-                 */
-
-                OS_REG_WRITE(ah, AR_PHY_TPCRG5 + regChainOffset,
-                     SM(pdGainOverlap_t2, AR_PHY_TPCRG5_PD_GAIN_OVERLAP) |
-                     SM(gainBoundaries[0], AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_1)  |
-                     SM(gainBoundaries[1], AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_2)  |
-                     SM(gainBoundaries[2], AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_3)  |
-                     SM(gainBoundaries[3], AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_4));
+		ar5416SetGainBoundariesClosedLoop(ah, regChainOffset,
+		  pdGainOverlap_t2, gainBoundaries);
             }
 
             /* Write the power values into the baseband power table */
-            regOffset = AR_PHY_BASE + (672 << 2) + regChainOffset;
-
-            for (j = 0; j < 32; j++) {
-                reg32 = ((pdadcValues[4*j + 0] & 0xFF) << 0)  |
-                    ((pdadcValues[4*j + 1] & 0xFF) << 8)  |
-                    ((pdadcValues[4*j + 2] & 0xFF) << 16) |
-                    ((pdadcValues[4*j + 3] & 0xFF) << 24) ;
-                OS_REG_WRITE(ah, regOffset, reg32);
-
-#ifdef PDADC_DUMP
-		ath_hal_printf(ah, "PDADC: Chain %d | PDADC %3d Value %3d | PDADC %3d Value %3d | PDADC %3d Value %3d | PDADC %3d Value %3d |\n",
-			       i,
-			       4*j, pdadcValues[4*j],
-			       4*j+1, pdadcValues[4*j + 1],
-			       4*j+2, pdadcValues[4*j + 2],
-			       4*j+3, pdadcValues[4*j + 3]);
-#endif
-                regOffset += 4;
-            }
+	    ar5416WritePdadcValues(ah, regChainOffset, pdadcValues);
         }
     }
     *pTxPowerIndexOffset = 0;
@@ -1989,7 +2121,7 @@ ar5416SetPowerCalTable(struct ath_hal *a
  * Uses the data points read from EEPROM to reconstruct the pdadc power table
  * Called by ar5416SetPowerCalTable only.
  */
-static void
+void
 ar5416GetGainBoundariesAndPdadcs(struct ath_hal *ah, 
                                  const struct ieee80211_channel *chan,
 				 CAL_DATA_PER_FREQ *pRawDataSet,

Added: head/sys/dev/ath/ath_hal/ar9002/ar9002phy.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/dev/ath/ath_hal/ar9002/ar9002phy.h	Tue Mar  8 06:59:59 2011	(r219393)
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2010-2011 Adrian Chadd, Xenion Pty Ltd.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef	__ATH_AR9002PHY_H__
+#define	__ATH_AR9002PHY_H__
+
+#define	AR_PHY_TX_PWRCTRL4			0xa264
+#define	AR_PHY_TX_PWRCTRL_PD_AVG_VALID		0x00000001
+#define	AR_PHY_TX_PWRCTRL_PD_AVG_VALID_S	0
+#define	AR_PHY_TX_PWRCTRL_PD_AVG_OUT		0x000001FE
+#define	AR_PHY_TX_PWRCTRL_PD_AVG_OUT_S		1
+
+#define	AR_PHY_TX_PWRCTRL6_0			0xa270
+#define	AR_PHY_TX_PWRCTRL6_1			0xb270
+#define	AR_PHY_TX_PWRCTRL_ERR_EST_MODE		0x03000000
+#define	AR_PHY_TX_PWRCTRL_ERR_EST_MODE_S	24
+
+#define	AR_PHY_TX_PWRCTRL7			0xa274
+#define	AR_PHY_TX_PWRCTRL_INIT_TX_GAIN		0x01F80000
+#define	AR_PHY_TX_PWRCTRL_INIT_TX_GAIN_S	19
+
+
+#define	AR_PHY_TX_GAIN_TBL1			0xa300
+#define	AR_PHY_TX_GAIN				0x0007F000
+#define	AR_PHY_TX_GAIN_S			12
+
+#endif

Modified: head/sys/dev/ath/ath_hal/ar9002/ar9280.h
==============================================================================
--- head/sys/dev/ath/ath_hal/ar9002/ar9280.h	Tue Mar  8 03:04:07 2011	(r219392)
+++ head/sys/dev/ath/ath_hal/ar9002/ar9280.h	Tue Mar  8 06:59:59 2011	(r219393)
@@ -20,12 +20,23 @@
 
 #include "ar5416/ar5416.h"
 
+/*
+ * This is a chip thing, but it's used here as part of the
+ * ath_hal_9280 struct; so it's convienent to locate the
+ * define here.
+ */
+#define AR9280_TX_GAIN_TABLE_SIZE               22
+
 struct ath_hal_9280 {
 	struct ath_hal_5416 ah_5416;
 
 	HAL_INI_ARRAY	ah_ini_xmodes;
 	HAL_INI_ARRAY	ah_ini_rxgain;
 	HAL_INI_ARRAY	ah_ini_txgain;
+
+	int PDADCdelta;
+
+	uint32_t	originalGain[AR9280_TX_GAIN_TABLE_SIZE];
 };
 #define	AH9280(_ah)	((struct ath_hal_9280 *)(_ah))
 

Modified: head/sys/dev/ath/ath_hal/ar9002/ar9280_attach.c
==============================================================================
--- head/sys/dev/ath/ath_hal/ar9002/ar9280_attach.c	Tue Mar  8 03:04:07 2011	(r219392)
+++ head/sys/dev/ath/ath_hal/ar9002/ar9280_attach.c	Tue Mar  8 06:59:59 2011	(r219393)
@@ -30,6 +30,7 @@
 
 #include "ar9002/ar9280v1.ini"
 #include "ar9002/ar9280v2.ini"
+#include "ar9002/ar9280_olc.h"
 
 static const HAL_PERCAL_DATA ar9280_iq_cal = {		/* single sample */
 	.calName = "IQ", .calType = IQ_MISMATCH_CAL,
@@ -112,6 +113,7 @@ ar9280Attach(uint16_t devid, HAL_SOFTC s
 	uint32_t val;
 	HAL_STATUS ecode;
 	HAL_BOOL rfStatus;
+	int8_t pwr_table_offset;
 
 	HALDEBUG(AH_NULL, HAL_DEBUG_ATTACH, "%s: sc %p st %p sh %p\n",
 	    __func__, sc, (void*) st, (void*) sh);
@@ -142,6 +144,10 @@ ar9280Attach(uint16_t devid, HAL_SOFTC s
 
 	AH5416(ah)->ah_spurMitigate	= ar9280SpurMitigate;
 	AH5416(ah)->ah_writeIni		= ar9280WriteIni;
+	AH5416(ah)->ah_olcInit		= ar9280olcInit;
+	AH5416(ah)->ah_olcTempCompensation = ar9280olcTemperatureCompensation;
+	AH5416(ah)->ah_setPowerCalTable	= ar9280SetPowerCalTable;
+
 	AH5416(ah)->ah_rx_chainmask	= AR9280_DEFAULT_RXCHAINMASK;
 	AH5416(ah)->ah_tx_chainmask	= AR9280_DEFAULT_TXCHAINMASK;
 
@@ -240,6 +246,18 @@ ar9280Attach(uint16_t devid, HAL_SOFTC s
 		goto bad;
 	}
 
+        /*
+         * Check whether the power table offset isn't the default.
+         * This can occur with eeprom minor V21 or greater on Merlin.
+         */
+	(void) ath_hal_eepromGet(ah, AR_EEP_PWR_TABLE_OFFSET, &pwr_table_offset);
+	if (pwr_table_offset != AR5416_PWR_TABLE_OFFSET_DB) {
+		ath_hal_printf(ah, "ERROR: default pwr offset: %d dBm != EEPROM pwr offset: %d dBm\n",
+		    AR5416_PWR_TABLE_OFFSET_DB, (int) pwr_table_offset);
+		ecode = HAL_ENOTSUPP;
+		goto bad;
+	}
+
 	if (AR_SREV_MERLIN_20_OR_LATER(ah)) {
 		/* setup rxgain table */
 		switch (ath_hal_eepromGet(ah, AR_EEP_RXGAIN_TYPE, AH_NULL)) {

Added: head/sys/dev/ath/ath_hal/ar9002/ar9280_olc.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/dev/ath/ath_hal/ar9002/ar9280_olc.c	Tue Mar  8 06:59:59 2011	(r219393)
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2011 Adrian Chadd, Xenion Pty Ltd.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#include "opt_ah.h"
+
+#include "ah.h"
+#include "ah_internal.h"
+
+#include "ah_eeprom_v14.h"
+
+#include "ar9002/ar9280.h"
+#include "ar5416/ar5416reg.h"
+#include "ar5416/ar5416phy.h"
+#include "ar9002/ar9002phy.h"
+
+#include "ar9002/ar9280_olc.h"
+
+void
+ar9280olcInit(struct ath_hal *ah)
+{
+	uint32_t i;
+
+	for (i = 0; i < AR9280_TX_GAIN_TABLE_SIZE; i++)
+		AH9280(ah)->originalGain[i] = MS(OS_REG_READ(ah,
+		    AR_PHY_TX_GAIN_TBL1 + i * 4), AR_PHY_TX_GAIN);
+
+	AH9280(ah)->PDADCdelta = 0;
+}
+
+void
+ar9280olcGetTxGainIndex(struct ath_hal *ah,
+    const struct ieee80211_channel *chan,
+    struct calDataPerFreqOpLoop *rawDatasetOpLoop,
+    uint8_t *calChans, uint16_t availPiers, uint8_t *pwr, uint8_t *pcdacIdx)
+{
+	uint8_t pcdac, i = 0;
+	uint16_t idxL = 0, idxR = 0, numPiers;
+	HAL_BOOL match;
+	CHAN_CENTERS centers;
+
+	ar5416GetChannelCenters(ah, chan, &centers);
+
+	for (numPiers = 0; numPiers < availPiers; numPiers++)
+		if (calChans[numPiers] == AR5416_BCHAN_UNUSED)
+			break;
+
+	match = getLowerUpperIndex((uint8_t)FREQ2FBIN(centers.synth_center,
+		    IEEE80211_IS_CHAN_2GHZ(chan)), calChans, numPiers,
+		    &idxL, &idxR);
+	if (match) {
+		pcdac = rawDatasetOpLoop[idxL].pcdac[0][0];
+		*pwr = rawDatasetOpLoop[idxL].pwrPdg[0][0];
+	} else {
+		pcdac = rawDatasetOpLoop[idxR].pcdac[0][0];
+		*pwr = (rawDatasetOpLoop[idxL].pwrPdg[0][0] +
+				rawDatasetOpLoop[idxR].pwrPdg[0][0])/2;
+	}
+	while (pcdac > AH9280(ah)->originalGain[i] &&
+			i < (AR9280_TX_GAIN_TABLE_SIZE - 1))
+		i++;
+
+	*pcdacIdx = i;
+}
+
+/*
+ * XXX txPower here is likely not the target txPower in the traditional
+ * XXX sense, but is set by a call to ar9280olcGetTxGainIndex().
+ * XXX Thus, be careful if you're trying to use this routine yourself.
+ */
+void
+ar9280olcGetPDADCs(struct ath_hal *ah, uint32_t initTxGain, int txPower,
+    uint8_t *pPDADCValues)
+{
+	uint32_t i;
+	uint32_t offset;
+
+	OS_REG_RMW_FIELD(ah, AR_PHY_TX_PWRCTRL6_0, AR_PHY_TX_PWRCTRL_ERR_EST_MODE, 3);
+	OS_REG_RMW_FIELD(ah, AR_PHY_TX_PWRCTRL6_1, AR_PHY_TX_PWRCTRL_ERR_EST_MODE, 3);
+
+	OS_REG_RMW_FIELD(ah, AR_PHY_TX_PWRCTRL7, AR_PHY_TX_PWRCTRL_INIT_TX_GAIN, initTxGain);
+
+	offset = txPower;
+	for (i = 0; i < AR5416_NUM_PDADC_VALUES; i++)
+		if (i < offset)
+			pPDADCValues[i] = 0x0;
+		else
+			pPDADCValues[i] = 0xFF;
+}
+
+/*
+ * Run temperature compensation calibration.
+ *
+ * The TX gain table is adjusted depending upon the difference
+ * between the initial PDADC value and the currently read
+ * average TX power sample value. This value is only valid if
+ * frames have been transmitted, so currPDADC will be 0 if
+ * no frames have yet been transmitted.
+ */
+void
+ar9280olcTemperatureCompensation(struct ath_hal *ah)
+{
+	uint32_t rddata, i;
+	int delta, currPDADC, regval;
+	uint8_t hpwr_5g = 0;
+
+	rddata = OS_REG_READ(ah, AR_PHY_TX_PWRCTRL4);
+	currPDADC = MS(rddata, AR_PHY_TX_PWRCTRL_PD_AVG_OUT);
+
+	HALDEBUG(ah, HAL_DEBUG_PERCAL,
+	    "%s: called: initPDADC=%d, currPDADC=%d\n",
+	    __func__, AH5416(ah)->initPDADC, currPDADC);
+
+	if (AH5416(ah)->initPDADC == 0 || currPDADC == 0)
+		return;
+
+	(void) (ath_hal_eepromGet(ah, AR_EEP_DAC_HPWR_5G, &hpwr_5g));
+
+	if (hpwr_5g)
+		delta = (currPDADC - AH5416(ah)->initPDADC + 4) / 8;
+	else
+		delta = (currPDADC - AH5416(ah)->initPDADC + 5) / 10;
+
+	HALDEBUG(ah, HAL_DEBUG_PERCAL, "%s: delta=%d, PDADCdelta=%d\n",
+	    __func__, delta, AH9280(ah)->PDADCdelta);
+
+	if (delta != AH9280(ah)->PDADCdelta) {
+		AH9280(ah)->PDADCdelta = delta;
+		for (i = 1; i < AR9280_TX_GAIN_TABLE_SIZE; i++) {
+			regval = AH9280(ah)->originalGain[i] - delta;
+			if (regval < 0)
+				regval = 0;
+
+			OS_REG_RMW_FIELD(ah,
+				      AR_PHY_TX_GAIN_TBL1 + i * 4,
+				      AR_PHY_TX_GAIN, regval);
+		}
+	}
+}
+
+/*
+ * This effectively disables the gain boundaries leaving it
+ * to the open-loop TX power control.
+ */
+static void
+ar9280SetGainBoundariesOpenLoop(struct ath_hal *ah, int regChainOffset,
+    uint16_t pdGainOverlap_t2, uint16_t gainBoundaries[])
+{
+	/* These are unused for OLC */
+	(void) pdGainOverlap_t2;
+	(void) gainBoundaries;
+
+	OS_REG_WRITE(ah, AR_PHY_TPCRG5 + regChainOffset,
+	    SM(0x6, AR_PHY_TPCRG5_PD_GAIN_OVERLAP) |
+	    SM(0x38, AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_1)  |
+	    SM(0x38, AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_2)  |
+	    SM(0x38, AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_3)  |
+	    SM(0x38, AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_4));
+}
+
+/* Eeprom versioning macros. Returns true if the version is equal or newer than the ver specified */
+/* XXX shouldn't be here! */
+#define EEP_MINOR(_ah) \
+        (AH_PRIVATE(_ah)->ah_eeversion & AR5416_EEP_VER_MINOR_MASK)
+#define IS_EEP_MINOR_V2(_ah)    (EEP_MINOR(_ah) >= AR5416_EEP_MINOR_VER_2)
+#define IS_EEP_MINOR_V3(_ah)    (EEP_MINOR(_ah) >= AR5416_EEP_MINOR_VER_3)
+
+/**************************************************************
+ * ar9280SetPowerCalTable
+ *
+ * Pull the PDADC piers from cal data and interpolate them across the given
+ * points as well as from the nearest pier(s) to get a power detector
+ * linear voltage to power level table.
+ *
+ * Handle OLC for Merlin where required.
+ */
+HAL_BOOL
+ar9280SetPowerCalTable(struct ath_hal *ah, struct ar5416eeprom *pEepData,
+	const struct ieee80211_channel *chan, int16_t *pTxPowerIndexOffset)
+{
+	CAL_DATA_PER_FREQ *pRawDataset;
+	uint8_t  *pCalBChans = AH_NULL;
+	uint16_t pdGainOverlap_t2;
+	static uint8_t  pdadcValues[AR5416_NUM_PDADC_VALUES];
+	uint16_t gainBoundaries[AR5416_PD_GAINS_IN_MASK];
+	uint16_t numPiers, i;
+	int16_t  tMinCalPower;
+	uint16_t numXpdGain, xpdMask;
+	uint16_t xpdGainValues[AR5416_NUM_PD_GAINS];
+	uint32_t regChainOffset;
+
+	OS_MEMZERO(xpdGainValues, sizeof(xpdGainValues));
+	    
+	xpdMask = pEepData->modalHeader[IEEE80211_IS_CHAN_2GHZ(chan)].xpdGain;
+
+	if (IS_EEP_MINOR_V2(ah)) {
+		pdGainOverlap_t2 = pEepData->modalHeader[IEEE80211_IS_CHAN_2GHZ(chan)].pdGainOverlap;
+	} else { 
+		pdGainOverlap_t2 = (uint16_t)(MS(OS_REG_READ(ah, AR_PHY_TPCRG5), AR_PHY_TPCRG5_PD_GAIN_OVERLAP));
+	}
+
+	if (IEEE80211_IS_CHAN_2GHZ(chan)) {
+		pCalBChans = pEepData->calFreqPier2G;
+		numPiers = AR5416_NUM_2G_CAL_PIERS;
+	} else {
+		pCalBChans = pEepData->calFreqPier5G;
+		numPiers = AR5416_NUM_5G_CAL_PIERS;
+	}
+
+	/* If OLC is being done, set the init PDADC value appropriately */
+	if (IEEE80211_IS_CHAN_2GHZ(chan) && AR_SREV_MERLIN_20_OR_LATER(ah) &&
+	    ath_hal_eepromGetFlag(ah, AR_EEP_OL_PWRCTRL)) {
+		struct calDataPerFreq *pRawDataset = pEepData->calPierData2G[0];
+		AH5416(ah)->initPDADC = ((struct calDataPerFreqOpLoop *) pRawDataset)->vpdPdg[0][0];
+	} else {
+		/*
+		 * XXX ath9k doesn't clear this for 5ghz mode if
+		 * it were set in 2ghz mode before!
+		 * The Merlin OLC temperature compensation code
+		 * uses this to calculate the PDADC delta during
+		 * calibration ; 0 here effectively stops the
+		 * temperature compensation calibration from
+		 * occuring.
+		 */
+		AH5416(ah)->initPDADC = 0;
+	}
+
+	/* Calculate the value of xpdgains from the xpdGain Mask */
+	numXpdGain = ar5416GetXpdGainValues(ah, xpdMask, xpdGainValues);
+	    
+	/* Write the detector gain biases and their number */
+	ar5416WriteDetectorGainBiases(ah, numXpdGain, xpdGainValues);
+
+	for (i = 0; i < AR5416_MAX_CHAINS; i++) {
+		regChainOffset = ar5416GetRegChainOffset(ah, i);
+		if (pEepData->baseEepHeader.txMask & (1 << i)) {
+			if (IEEE80211_IS_CHAN_2GHZ(chan)) {
+				pRawDataset = pEepData->calPierData2G[i];
+			} else {
+				pRawDataset = pEepData->calPierData5G[i];
+			}
+
+			/* Fetch the gain boundaries and the PDADC values */
+			if (AR_SREV_MERLIN_20_OR_LATER(ah) &&
+			    ath_hal_eepromGetFlag(ah, AR_EEP_OL_PWRCTRL)) {
+				uint8_t pcdacIdx;
+				uint8_t txPower;
+
+				ar9280olcGetTxGainIndex(ah, chan,
+				    (struct calDataPerFreqOpLoop *) pRawDataset,
+				    pCalBChans, numPiers, &txPower, &pcdacIdx);
+				ar9280olcGetPDADCs(ah, pcdacIdx, txPower / 2, pdadcValues);
+			} else {
+				ar5416GetGainBoundariesAndPdadcs(ah,  chan,
+				    pRawDataset, pCalBChans, numPiers,
+				    pdGainOverlap_t2, &tMinCalPower,
+				    gainBoundaries, pdadcValues, numXpdGain);
+			}
+
+			/*
+			 * Prior to writing the boundaries or the pdadc vs. power table
+			 * into the chip registers the default starting point on the pdadc
+			 * vs. power table needs to be checked and the curve boundaries
+			 * adjusted accordingly
+			 */
+			// XXX ath9k_change_gain_boundary_setting();
+
+			if ((i == 0) || AR_SREV_OWL_20_OR_LATER(ah)) {
+				/* Set gain boundaries for either open- or closed-loop TPC */
+				if (AR_SREV_MERLIN_20_OR_LATER(ah) &&
+				    ath_hal_eepromGetFlag(ah, AR_EEP_OL_PWRCTRL))
+					ar9280SetGainBoundariesOpenLoop(ah,
+					    regChainOffset, pdGainOverlap_t2,
+					    gainBoundaries);
+				else
+					ar5416SetGainBoundariesClosedLoop(ah,
+					    regChainOffset, pdGainOverlap_t2,
+					    gainBoundaries);
+			}
+
+			/*
+			 * If this is a board that has a pwrTableOffset that differs from
+			 * the default AR5416_PWR_TABLE_OFFSET_DB then the start of the
+			 * pdadc vs pwr table needs to be adjusted prior to writing to the
+			 * chip.
+			 */
+			/* XXX ath9k_adjust_pdadc_values() */
+
+			/* Write the power values into the baseband power table */
+			ar5416WritePdadcValues(ah, regChainOffset, pdadcValues);
+		}
+	}
+	*pTxPowerIndexOffset = 0;
+
+	return AH_TRUE;
+}

Added: head/sys/dev/ath/ath_hal/ar9002/ar9280_olc.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/dev/ath/ath_hal/ar9002/ar9280_olc.h	Tue Mar  8 06:59:59 2011	(r219393)
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2010-2011 Adrian Chadd, Xenion Pty Ltd.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***


More information about the svn-src-all mailing list