1/*-
2 * SPDX-License-Identifier: ISC
3 *
4 * Copyright (c) 2002-2008 Sam Leffler, Errno Consulting
5 * Copyright (c) 2002-2008 Atheros Communications, Inc.
6 *
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 *
19 * $FreeBSD: releng/12.0/sys/dev/ath/ath_hal/ar5212/ar5212_rfgain.c 326695 2017-12-08 15:57:29Z pfg $
20 */
21#include "opt_ah.h"
22
23#include "ah.h"
24#include "ah_internal.h"
25#include "ah_devid.h"
26
27#include "ar5212/ar5212.h"
28#include "ar5212/ar5212reg.h"
29#include "ar5212/ar5212phy.h"
30
31#include "ah_eeprom_v3.h"
32
33static const GAIN_OPTIMIZATION_LADDER gainLadder = {
34	9,					/* numStepsInLadder */
35	4,					/* defaultStepNum */
36	{ { {4, 1, 1, 1},  6, "FG8"},
37	  { {4, 0, 1, 1},  4, "FG7"},
38	  { {3, 1, 1, 1},  3, "FG6"},
39	  { {4, 0, 0, 1},  1, "FG5"},
40	  { {4, 1, 1, 0},  0, "FG4"},	/* noJack */
41	  { {4, 0, 1, 0}, -2, "FG3"},	/* halfJack */
42	  { {3, 1, 1, 0}, -3, "FG2"},	/* clip3 */
43	  { {4, 0, 0, 0}, -4, "FG1"},	/* noJack */
44	  { {2, 1, 1, 0}, -6, "FG0"} 	/* clip2 */
45	}
46};
47
48static const GAIN_OPTIMIZATION_LADDER gainLadder5112 = {
49	8,					/* numStepsInLadder */
50	1,					/* defaultStepNum */
51	{ { {3, 0,0,0, 0,0,0},   6, "FG7"},	/* most fixed gain */
52	  { {2, 0,0,0, 0,0,0},   0, "FG6"},
53	  { {1, 0,0,0, 0,0,0},  -3, "FG5"},
54	  { {0, 0,0,0, 0,0,0},  -6, "FG4"},
55	  { {0, 1,1,0, 0,0,0},  -8, "FG3"},
56	  { {0, 1,1,0, 1,1,0}, -10, "FG2"},
57	  { {0, 1,0,1, 1,1,0}, -13, "FG1"},
58	  { {0, 1,0,1, 1,0,1}, -16, "FG0"},	/* least fixed gain */
59	}
60};
61
62/*
63 * Initialize the gain structure to good values
64 */
65void
66ar5212InitializeGainValues(struct ath_hal *ah)
67{
68	struct ath_hal_5212 *ahp = AH5212(ah);
69	GAIN_VALUES *gv = &ahp->ah_gainValues;
70
71	/* initialize gain optimization values */
72	if (IS_RAD5112_ANY(ah)) {
73		gv->currStepNum = gainLadder5112.defaultStepNum;
74		gv->currStep =
75			&gainLadder5112.optStep[gainLadder5112.defaultStepNum];
76		gv->active = AH_TRUE;
77		gv->loTrig = 20;
78		gv->hiTrig = 85;
79	} else {
80		gv->currStepNum = gainLadder.defaultStepNum;
81		gv->currStep = &gainLadder.optStep[gainLadder.defaultStepNum];
82		gv->active = AH_TRUE;
83		gv->loTrig = 20;
84		gv->hiTrig = 35;
85	}
86}
87
88#define	MAX_ANALOG_START	319		/* XXX */
89
90/*
91 * Find analog bits of given parameter data and return a reversed value
92 */
93static uint32_t
94ar5212GetRfField(uint32_t *rfBuf, uint32_t numBits, uint32_t firstBit, uint32_t column)
95{
96	uint32_t reg32 = 0, mask, arrayEntry, lastBit;
97	uint32_t bitPosition, bitsShifted;
98	int32_t bitsLeft;
99
100	HALASSERT(column <= 3);
101	HALASSERT(numBits <= 32);
102	HALASSERT(firstBit + numBits <= MAX_ANALOG_START);
103
104	arrayEntry = (firstBit - 1) / 8;
105	bitPosition = (firstBit - 1) % 8;
106	bitsLeft = numBits;
107	bitsShifted = 0;
108	while (bitsLeft > 0) {
109		lastBit = (bitPosition + bitsLeft > 8) ?
110			(8) : (bitPosition + bitsLeft);
111		mask = (((1 << lastBit) - 1) ^ ((1 << bitPosition) - 1)) <<
112			(column * 8);
113		reg32 |= (((rfBuf[arrayEntry] & mask) >> (column * 8)) >>
114			bitPosition) << bitsShifted;
115		bitsShifted += lastBit - bitPosition;
116		bitsLeft -= (8 - bitPosition);
117		bitPosition = 0;
118		arrayEntry++;
119	}
120	reg32 = ath_hal_reverseBits(reg32, numBits);
121	return reg32;
122}
123
124static HAL_BOOL
125ar5212InvalidGainReadback(struct ath_hal *ah, GAIN_VALUES *gv)
126{
127	uint32_t gStep, g, mixOvr;
128	uint32_t L1, L2, L3, L4;
129
130	if (IS_RAD5112_ANY(ah)) {
131		mixOvr = ar5212GetRfField(ar5212GetRfBank(ah, 7), 1, 36, 0);
132		L1 = 0;
133		L2 = 107;
134		L3 = 0;
135		L4 = 107;
136		if (mixOvr == 1) {
137			L2 = 83;
138			L4 = 83;
139			gv->hiTrig = 55;
140		}
141	} else {
142		gStep = ar5212GetRfField(ar5212GetRfBank(ah, 7), 6, 37, 0);
143
144		L1 = 0;
145		L2 = (gStep == 0x3f) ? 50 : gStep + 4;
146		L3 = (gStep != 0x3f) ? 0x40 : L1;
147		L4 = L3 + 50;
148
149		gv->loTrig = L1 + (gStep == 0x3f ? DYN_ADJ_LO_MARGIN : 0);
150		/* never adjust if != 0x3f */
151		gv->hiTrig = L4 - (gStep == 0x3f ? DYN_ADJ_UP_MARGIN : -5);
152	}
153	g = gv->currGain;
154
155	return !((g >= L1 && g<= L2) || (g >= L3 && g <= L4));
156}
157
158/*
159 * Enable the probe gain check on the next packet
160 */
161void
162ar5212RequestRfgain(struct ath_hal *ah)
163{
164	struct ath_hal_5212 *ahp = AH5212(ah);
165	uint32_t probePowerIndex;
166
167	/* Enable the gain readback probe */
168	probePowerIndex = ahp->ah_ofdmTxPower + ahp->ah_txPowerIndexOffset;
169	OS_REG_WRITE(ah, AR_PHY_PAPD_PROBE,
170		  SM(probePowerIndex, AR_PHY_PAPD_PROBE_POWERTX)
171		| AR_PHY_PAPD_PROBE_NEXT_TX);
172
173	ahp->ah_rfgainState = HAL_RFGAIN_READ_REQUESTED;
174}
175
176/*
177 * Check to see if our readback gain level sits within the linear
178 * region of our current variable attenuation window
179 */
180static HAL_BOOL
181ar5212IsGainAdjustNeeded(struct ath_hal *ah, const GAIN_VALUES *gv)
182{
183	return (gv->currGain <= gv->loTrig || gv->currGain >= gv->hiTrig);
184}
185
186/*
187 * Move the rabbit ears in the correct direction.
188 */
189static int32_t
190ar5212AdjustGain(struct ath_hal *ah, GAIN_VALUES *gv)
191{
192	const GAIN_OPTIMIZATION_LADDER *gl;
193
194	if (IS_RAD5112_ANY(ah))
195		gl = &gainLadder5112;
196	else
197		gl = &gainLadder;
198	gv->currStep = &gl->optStep[gv->currStepNum];
199	if (gv->currGain >= gv->hiTrig) {
200		if (gv->currStepNum == 0) {
201			HALDEBUG(ah, HAL_DEBUG_ANY, "%s: Max gain limit.\n",
202			    __func__);
203			return -1;
204		}
205		HALDEBUG(ah, HAL_DEBUG_RFPARAM,
206		    "%s: Adding gain: currG=%d [%s] --> ",
207		    __func__, gv->currGain, gv->currStep->stepName);
208		gv->targetGain = gv->currGain;
209		while (gv->targetGain >= gv->hiTrig && gv->currStepNum > 0) {
210			gv->targetGain -= 2 * (gl->optStep[--(gv->currStepNum)].stepGain -
211				gv->currStep->stepGain);
212			gv->currStep = &gl->optStep[gv->currStepNum];
213		}
214		HALDEBUG(ah, HAL_DEBUG_RFPARAM, "targG=%d [%s]\n",
215		    gv->targetGain, gv->currStep->stepName);
216		return 1;
217	}
218	if (gv->currGain <= gv->loTrig) {
219		if (gv->currStepNum == gl->numStepsInLadder-1) {
220			HALDEBUG(ah, HAL_DEBUG_RFPARAM,
221			    "%s: Min gain limit.\n", __func__);
222			return -2;
223		}
224		HALDEBUG(ah, HAL_DEBUG_RFPARAM,
225		    "%s: Deducting gain: currG=%d [%s] --> ",
226		    __func__, gv->currGain, gv->currStep->stepName);
227		gv->targetGain = gv->currGain;
228		while (gv->targetGain <= gv->loTrig &&
229		      gv->currStepNum < (gl->numStepsInLadder - 1)) {
230			gv->targetGain -= 2 *
231				(gl->optStep[++(gv->currStepNum)].stepGain - gv->currStep->stepGain);
232			gv->currStep = &gl->optStep[gv->currStepNum];
233		}
234		HALDEBUG(ah, HAL_DEBUG_RFPARAM, "targG=%d [%s]\n",
235		    gv->targetGain, gv->currStep->stepName);
236		return 2;
237	}
238	return 0;		/* caller didn't call needAdjGain first */
239}
240
241/*
242 * Read rf register to determine if gainF needs correction
243 */
244static uint32_t
245ar5212GetGainFCorrection(struct ath_hal *ah)
246{
247	struct ath_hal_5212 *ahp = AH5212(ah);
248	uint32_t correction;
249
250	HALASSERT(IS_RADX112_REV2(ah));
251
252	correction = 0;
253	if (ar5212GetRfField(ar5212GetRfBank(ah, 7), 1, 36, 0) == 1) {
254		const GAIN_VALUES *gv = &ahp->ah_gainValues;
255		uint32_t mixGain = gv->currStep->paramVal[0];
256		uint32_t gainStep =
257			ar5212GetRfField(ar5212GetRfBank(ah, 7), 4, 32, 0);
258		switch (mixGain) {
259		case 0 :
260			correction = 0;
261			break;
262		case 1 :
263			correction = gainStep;
264			break;
265		case 2 :
266			correction = 2 * gainStep - 5;
267			break;
268		case 3 :
269			correction = 2 * gainStep;
270			break;
271		}
272	}
273	return correction;
274}
275
276/*
277 * Exported call to check for a recent gain reading and return
278 * the current state of the thermal calibration gain engine.
279 */
280HAL_RFGAIN
281ar5212GetRfgain(struct ath_hal *ah)
282{
283	struct ath_hal_5212 *ahp = AH5212(ah);
284	GAIN_VALUES *gv = &ahp->ah_gainValues;
285	uint32_t rddata, probeType;
286
287	/* NB: beware of touching the BB when PHY is powered down */
288	if (!gv->active || !ahp->ah_phyPowerOn)
289		return HAL_RFGAIN_INACTIVE;
290
291	if (ahp->ah_rfgainState == HAL_RFGAIN_READ_REQUESTED) {
292		/* Caller had asked to setup a new reading. Check it. */
293		rddata = OS_REG_READ(ah, AR_PHY_PAPD_PROBE);
294
295		if ((rddata & AR_PHY_PAPD_PROBE_NEXT_TX) == 0) {
296			/* bit got cleared, we have a new reading. */
297			gv->currGain = rddata >> AR_PHY_PAPD_PROBE_GAINF_S;
298			probeType = MS(rddata, AR_PHY_PAPD_PROBE_TYPE);
299			if (probeType == AR_PHY_PAPD_PROBE_TYPE_CCK) {
300				const HAL_EEPROM *ee = AH_PRIVATE(ah)->ah_eeprom;
301
302				HALASSERT(IS_RAD5112_ANY(ah));
303				HALASSERT(ah->ah_magic == AR5212_MAGIC);
304				if (AH_PRIVATE(ah)->ah_phyRev >= AR_PHY_CHIP_ID_REV_2)
305					gv->currGain += ee->ee_cckOfdmGainDelta;
306				else
307					gv->currGain += PHY_PROBE_CCK_CORRECTION;
308			}
309			if (IS_RADX112_REV2(ah)) {
310				uint32_t correct = ar5212GetGainFCorrection(ah);
311				if (gv->currGain >= correct)
312					gv->currGain -= correct;
313				else
314					gv->currGain = 0;
315			}
316			/* inactive by default */
317			ahp->ah_rfgainState = HAL_RFGAIN_INACTIVE;
318
319			if (!ar5212InvalidGainReadback(ah, gv) &&
320			    ar5212IsGainAdjustNeeded(ah, gv) &&
321			    ar5212AdjustGain(ah, gv) > 0) {
322				/*
323				 * Change needed. Copy ladder info
324				 * into eeprom info.
325				 */
326				ahp->ah_rfgainState = HAL_RFGAIN_NEED_CHANGE;
327				/* for ap51 */
328				ahp->ah_cwCalRequire = AH_TRUE;
329				/* Request IQ recalibration for temperature chang */
330				ahp->ah_bIQCalibration = IQ_CAL_INACTIVE;
331			}
332		}
333	}
334	return ahp->ah_rfgainState;
335}
336