1*4b1a56afSjsg /* $OpenBSD: ar9287.c,v 1.30 2022/01/09 05:42:38 jsg Exp $ */
2498e8a28Sdamien
3498e8a28Sdamien /*-
4498e8a28Sdamien * Copyright (c) 2009 Damien Bergamini <damien.bergamini@free.fr>
5498e8a28Sdamien * Copyright (c) 2008-2009 Atheros Communications Inc.
6498e8a28Sdamien *
7498e8a28Sdamien * Permission to use, copy, modify, and/or distribute this software for any
8498e8a28Sdamien * purpose with or without fee is hereby granted, provided that the above
9498e8a28Sdamien * copyright notice and this permission notice appear in all copies.
10498e8a28Sdamien *
11498e8a28Sdamien * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12498e8a28Sdamien * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13498e8a28Sdamien * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14498e8a28Sdamien * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15498e8a28Sdamien * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16498e8a28Sdamien * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17498e8a28Sdamien * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18498e8a28Sdamien */
19498e8a28Sdamien
20498e8a28Sdamien /*
21498e8a28Sdamien * Driver for Atheros 802.11a/g/n chipsets.
22498e8a28Sdamien * Routines for AR9227 and AR9287 chipsets.
23498e8a28Sdamien */
24498e8a28Sdamien
25498e8a28Sdamien #include "bpfilter.h"
26498e8a28Sdamien
27498e8a28Sdamien #include <sys/param.h>
28498e8a28Sdamien #include <sys/sockio.h>
29498e8a28Sdamien #include <sys/mbuf.h>
30498e8a28Sdamien #include <sys/kernel.h>
31498e8a28Sdamien #include <sys/socket.h>
32498e8a28Sdamien #include <sys/systm.h>
33498e8a28Sdamien #include <sys/malloc.h>
34498e8a28Sdamien #include <sys/queue.h>
35498e8a28Sdamien #include <sys/timeout.h>
36498e8a28Sdamien #include <sys/conf.h>
37498e8a28Sdamien #include <sys/device.h>
389b18ffb8Sguenther #include <sys/endian.h>
39498e8a28Sdamien
40498e8a28Sdamien #include <machine/bus.h>
41498e8a28Sdamien #include <machine/intr.h>
42498e8a28Sdamien
43498e8a28Sdamien #if NBPFILTER > 0
44498e8a28Sdamien #include <net/bpf.h>
45498e8a28Sdamien #endif
46498e8a28Sdamien #include <net/if.h>
47498e8a28Sdamien #include <net/if_media.h>
48498e8a28Sdamien
49498e8a28Sdamien #include <netinet/in.h>
50498e8a28Sdamien #include <netinet/if_ether.h>
51498e8a28Sdamien
52498e8a28Sdamien #include <net80211/ieee80211_var.h>
53498e8a28Sdamien #include <net80211/ieee80211_amrr.h>
54d2dd70acSstsp #include <net80211/ieee80211_ra.h>
55498e8a28Sdamien #include <net80211/ieee80211_radiotap.h>
56498e8a28Sdamien
57498e8a28Sdamien #include <dev/ic/athnreg.h>
58498e8a28Sdamien #include <dev/ic/athnvar.h>
59498e8a28Sdamien
60bd6ea91dSdamien #include <dev/ic/ar5008reg.h>
61498e8a28Sdamien #include <dev/ic/ar9280reg.h>
62498e8a28Sdamien #include <dev/ic/ar9287reg.h>
63498e8a28Sdamien
64498e8a28Sdamien int ar9287_attach(struct athn_softc *);
65498e8a28Sdamien void ar9287_setup(struct athn_softc *);
66498e8a28Sdamien void ar9287_swap_rom(struct athn_softc *);
67498e8a28Sdamien const struct ar_spur_chan *ar9287_get_spur_chans(struct athn_softc *, int);
68498e8a28Sdamien void ar9287_init_from_rom(struct athn_softc *, struct ieee80211_channel *,
69498e8a28Sdamien struct ieee80211_channel *);
70498e8a28Sdamien void ar9287_get_pdadcs(struct athn_softc *, struct ieee80211_channel *,
71498e8a28Sdamien int, int, uint8_t, uint8_t *, uint8_t *);
72498e8a28Sdamien void ar9287_olpc_get_pdgain(struct athn_softc *, struct ieee80211_channel *,
73498e8a28Sdamien int, int8_t *);
74498e8a28Sdamien void ar9287_set_power_calib(struct athn_softc *,
75498e8a28Sdamien struct ieee80211_channel *);
76498e8a28Sdamien void ar9287_set_txpower(struct athn_softc *, struct ieee80211_channel *,
77498e8a28Sdamien struct ieee80211_channel *);
78498e8a28Sdamien void ar9287_olpc_init(struct athn_softc *);
79498e8a28Sdamien void ar9287_olpc_temp_compensation(struct athn_softc *);
801c1c6997Sdamien void ar9287_1_3_enable_async_fifo(struct athn_softc *);
811c1c6997Sdamien void ar9287_1_3_setup_async_fifo(struct athn_softc *);
82bd6ea91dSdamien
83bd6ea91dSdamien /* Extern functions. */
84bd6ea91dSdamien uint8_t athn_chan2fbin(struct ieee80211_channel *);
85bd6ea91dSdamien void athn_get_pier_ival(uint8_t, const uint8_t *, int, int *, int *);
86bd6ea91dSdamien int ar5008_attach(struct athn_softc *);
87bd6ea91dSdamien void ar5008_write_txpower(struct athn_softc *, int16_t power[]);
88bd6ea91dSdamien void ar5008_get_pdadcs(struct athn_softc *, uint8_t, struct athn_pier *,
89bd6ea91dSdamien struct athn_pier *, int, int, uint8_t, uint8_t *, uint8_t *);
90bd6ea91dSdamien void ar5008_get_lg_tpow(struct athn_softc *, struct ieee80211_channel *,
91bd6ea91dSdamien uint8_t, const struct ar_cal_target_power_leg *, int, uint8_t[]);
92bd6ea91dSdamien void ar5008_get_ht_tpow(struct athn_softc *, struct ieee80211_channel *,
93bd6ea91dSdamien uint8_t, const struct ar_cal_target_power_ht *, int, uint8_t[]);
94bd6ea91dSdamien int ar9280_set_synth(struct athn_softc *, struct ieee80211_channel *,
95bd6ea91dSdamien struct ieee80211_channel *);
96bd6ea91dSdamien void ar9280_spur_mitigate(struct athn_softc *, struct ieee80211_channel *,
97bd6ea91dSdamien struct ieee80211_channel *);
98bd6ea91dSdamien
99498e8a28Sdamien
100498e8a28Sdamien int
ar9287_attach(struct athn_softc * sc)101498e8a28Sdamien ar9287_attach(struct athn_softc *sc)
102498e8a28Sdamien {
103792ad11eSstsp sc->eep_base = (sc->flags & ATHN_FLAG_USB) ?
104792ad11eSstsp AR9287_HTC_EEP_START_LOC : AR9287_EEP_START_LOC;
105498e8a28Sdamien sc->eep_size = sizeof(struct ar9287_eeprom);
10611a1e31dSdamien sc->ngpiopins = (sc->flags & ATHN_FLAG_USB) ? 16 : 11;
1071b746f22Skevlo sc->led_pin = (sc->flags & ATHN_FLAG_USB) ? 10 : 8;
108498e8a28Sdamien sc->workaround = AR9285_WA_DEFAULT;
109498e8a28Sdamien sc->ops.setup = ar9287_setup;
110498e8a28Sdamien sc->ops.swap_rom = ar9287_swap_rom;
111498e8a28Sdamien sc->ops.init_from_rom = ar9287_init_from_rom;
112498e8a28Sdamien sc->ops.set_txpower = ar9287_set_txpower;
113498e8a28Sdamien sc->ops.set_synth = ar9280_set_synth;
114498e8a28Sdamien sc->ops.spur_mitigate = ar9280_spur_mitigate;
115498e8a28Sdamien sc->ops.get_spur_chans = ar9287_get_spur_chans;
116498e8a28Sdamien sc->ops.olpc_init = ar9287_olpc_init;
117b46f6896Sdamien sc->ops.olpc_temp_compensation = ar9287_olpc_temp_compensation;
1189d1f2812Sstsp sc->cca_min_2g = AR9287_PHY_CCA_MIN_GOOD_VAL_2GHZ;
1199d1f2812Sstsp sc->cca_max_2g = AR9287_PHY_CCA_MAX_GOOD_VAL_2GHZ;
120498e8a28Sdamien sc->ini = &ar9287_1_1_ini;
121328b15b2Skettenis sc->serdes = &ar9280_2_0_serdes;
122bd6ea91dSdamien
123bd6ea91dSdamien return (ar5008_attach(sc));
124498e8a28Sdamien }
125498e8a28Sdamien
126498e8a28Sdamien void
ar9287_setup(struct athn_softc * sc)127498e8a28Sdamien ar9287_setup(struct athn_softc *sc)
128498e8a28Sdamien {
129498e8a28Sdamien const struct ar9287_eeprom *eep = sc->eep;
130498e8a28Sdamien
131498e8a28Sdamien /* Determine if open loop power control should be used. */
132498e8a28Sdamien if (eep->baseEepHeader.openLoopPwrCntl)
133498e8a28Sdamien sc->flags |= ATHN_FLAG_OLPC;
13482e6bbbcSdamien
135498e8a28Sdamien sc->rx_gain = &ar9287_1_1_rx_gain;
136498e8a28Sdamien sc->tx_gain = &ar9287_1_1_tx_gain;
137498e8a28Sdamien }
138498e8a28Sdamien
139498e8a28Sdamien void
ar9287_swap_rom(struct athn_softc * sc)140498e8a28Sdamien ar9287_swap_rom(struct athn_softc *sc)
141498e8a28Sdamien {
142498e8a28Sdamien struct ar9287_eeprom *eep = sc->eep;
143498e8a28Sdamien int i;
144498e8a28Sdamien
145498e8a28Sdamien eep->modalHeader.antCtrlCommon =
146498e8a28Sdamien swap32(eep->modalHeader.antCtrlCommon);
147498e8a28Sdamien
148498e8a28Sdamien for (i = 0; i < AR9287_MAX_CHAINS; i++) {
149498e8a28Sdamien eep->modalHeader.antCtrlChain[i] =
150498e8a28Sdamien swap32(eep->modalHeader.antCtrlChain[i]);
151498e8a28Sdamien }
152498e8a28Sdamien for (i = 0; i < AR_EEPROM_MODAL_SPURS; i++) {
153498e8a28Sdamien eep->modalHeader.spurChans[i].spurChan =
154498e8a28Sdamien swap16(eep->modalHeader.spurChans[i].spurChan);
155498e8a28Sdamien }
156498e8a28Sdamien }
157498e8a28Sdamien
158498e8a28Sdamien const struct ar_spur_chan *
ar9287_get_spur_chans(struct athn_softc * sc,int is2ghz)159498e8a28Sdamien ar9287_get_spur_chans(struct athn_softc *sc, int is2ghz)
160498e8a28Sdamien {
161498e8a28Sdamien const struct ar9287_eeprom *eep = sc->eep;
162498e8a28Sdamien
163498e8a28Sdamien KASSERT(is2ghz);
164bd6ea91dSdamien return (eep->modalHeader.spurChans);
165498e8a28Sdamien }
166498e8a28Sdamien
167498e8a28Sdamien void
ar9287_init_from_rom(struct athn_softc * sc,struct ieee80211_channel * c,struct ieee80211_channel * extc)168498e8a28Sdamien ar9287_init_from_rom(struct athn_softc *sc, struct ieee80211_channel *c,
169498e8a28Sdamien struct ieee80211_channel *extc)
170498e8a28Sdamien {
171498e8a28Sdamien const struct ar9287_eeprom *eep = sc->eep;
172498e8a28Sdamien const struct ar9287_modal_eep_header *modal = &eep->modalHeader;
173498e8a28Sdamien uint32_t reg, offset;
174498e8a28Sdamien int i;
175498e8a28Sdamien
17680a670fbSdamien AR_WRITE(sc, AR_PHY_SWITCH_COM, modal->antCtrlCommon);
177498e8a28Sdamien
178498e8a28Sdamien for (i = 0; i < AR9287_MAX_CHAINS; i++) {
179498e8a28Sdamien offset = i * 0x1000;
180498e8a28Sdamien
181498e8a28Sdamien AR_WRITE(sc, AR_PHY_SWITCH_CHAIN_0 + offset,
182498e8a28Sdamien modal->antCtrlChain[i]);
183498e8a28Sdamien
184498e8a28Sdamien reg = AR_READ(sc, AR_PHY_TIMING_CTRL4_0 + offset);
185498e8a28Sdamien reg = RW(reg, AR_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF,
186498e8a28Sdamien modal->iqCalICh[i]);
187498e8a28Sdamien reg = RW(reg, AR_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF,
188498e8a28Sdamien modal->iqCalQCh[i]);
189498e8a28Sdamien AR_WRITE(sc, AR_PHY_TIMING_CTRL4_0 + offset, reg);
190498e8a28Sdamien
191498e8a28Sdamien reg = AR_READ(sc, AR_PHY_GAIN_2GHZ + offset);
192498e8a28Sdamien reg = RW(reg, AR_PHY_GAIN_2GHZ_XATTEN1_MARGIN,
193498e8a28Sdamien modal->bswMargin[i]);
194498e8a28Sdamien reg = RW(reg, AR_PHY_GAIN_2GHZ_XATTEN1_DB,
195498e8a28Sdamien modal->bswAtten[i]);
196498e8a28Sdamien AR_WRITE(sc, AR_PHY_GAIN_2GHZ + offset, reg);
197498e8a28Sdamien
198498e8a28Sdamien reg = AR_READ(sc, AR_PHY_RXGAIN + offset);
199498e8a28Sdamien reg = RW(reg, AR9280_PHY_RXGAIN_TXRX_MARGIN,
200498e8a28Sdamien modal->rxTxMarginCh[i]);
201498e8a28Sdamien reg = RW(reg, AR9280_PHY_RXGAIN_TXRX_ATTEN,
202498e8a28Sdamien modal->txRxAttenCh[i]);
203498e8a28Sdamien AR_WRITE(sc, AR_PHY_RXGAIN + offset, reg);
204498e8a28Sdamien }
205498e8a28Sdamien
206498e8a28Sdamien reg = AR_READ(sc, AR_PHY_SETTLING);
207498e8a28Sdamien if (extc != NULL)
208498e8a28Sdamien reg = RW(reg, AR_PHY_SETTLING_SWITCH, modal->swSettleHt40);
209498e8a28Sdamien else
210498e8a28Sdamien reg = RW(reg, AR_PHY_SETTLING_SWITCH, modal->switchSettling);
211498e8a28Sdamien AR_WRITE(sc, AR_PHY_SETTLING, reg);
212498e8a28Sdamien
213498e8a28Sdamien reg = AR_READ(sc, AR_PHY_DESIRED_SZ);
214498e8a28Sdamien reg = RW(reg, AR_PHY_DESIRED_SZ_ADC, modal->adcDesiredSize);
215498e8a28Sdamien AR_WRITE(sc, AR_PHY_DESIRED_SZ, reg);
216498e8a28Sdamien
217498e8a28Sdamien reg = SM(AR_PHY_RF_CTL4_TX_END_XPAA_OFF, modal->txEndToXpaOff);
218498e8a28Sdamien reg |= SM(AR_PHY_RF_CTL4_TX_END_XPAB_OFF, modal->txEndToXpaOff);
219498e8a28Sdamien reg |= SM(AR_PHY_RF_CTL4_FRAME_XPAA_ON, modal->txFrameToXpaOn);
220498e8a28Sdamien reg |= SM(AR_PHY_RF_CTL4_FRAME_XPAB_ON, modal->txFrameToXpaOn);
221498e8a28Sdamien AR_WRITE(sc, AR_PHY_RF_CTL4, reg);
222498e8a28Sdamien
223498e8a28Sdamien reg = AR_READ(sc, AR_PHY_RF_CTL3);
224498e8a28Sdamien reg = RW(reg, AR_PHY_TX_END_TO_A2_RX_ON, modal->txEndToRxOn);
225498e8a28Sdamien AR_WRITE(sc, AR_PHY_RF_CTL3, reg);
226498e8a28Sdamien
227498e8a28Sdamien reg = AR_READ(sc, AR_PHY_CCA(0));
228498e8a28Sdamien reg = RW(reg, AR9280_PHY_CCA_THRESH62, modal->thresh62);
229498e8a28Sdamien AR_WRITE(sc, AR_PHY_CCA(0), reg);
230498e8a28Sdamien
231498e8a28Sdamien reg = AR_READ(sc, AR_PHY_EXT_CCA0);
232498e8a28Sdamien reg = RW(reg, AR_PHY_EXT_CCA0_THRESH62, modal->thresh62);
233498e8a28Sdamien AR_WRITE(sc, AR_PHY_EXT_CCA0, reg);
234498e8a28Sdamien
235498e8a28Sdamien reg = AR_READ(sc, AR9287_AN_RF2G3_CH0);
236498e8a28Sdamien reg = RW(reg, AR9287_AN_RF2G3_DB1, modal->db1);
237498e8a28Sdamien reg = RW(reg, AR9287_AN_RF2G3_DB2, modal->db2);
238498e8a28Sdamien reg = RW(reg, AR9287_AN_RF2G3_OB_CCK, modal->ob_cck);
239498e8a28Sdamien reg = RW(reg, AR9287_AN_RF2G3_OB_PSK, modal->ob_psk);
240498e8a28Sdamien reg = RW(reg, AR9287_AN_RF2G3_OB_QAM, modal->ob_qam);
241498e8a28Sdamien reg = RW(reg, AR9287_AN_RF2G3_OB_PAL_OFF, modal->ob_pal_off);
242498e8a28Sdamien AR_WRITE(sc, AR9287_AN_RF2G3_CH0, reg);
243c0a11cf8Sdamien AR_WRITE_BARRIER(sc);
244498e8a28Sdamien DELAY(100);
245498e8a28Sdamien
246498e8a28Sdamien reg = AR_READ(sc, AR9287_AN_RF2G3_CH1);
247498e8a28Sdamien reg = RW(reg, AR9287_AN_RF2G3_DB1, modal->db1);
248498e8a28Sdamien reg = RW(reg, AR9287_AN_RF2G3_DB2, modal->db2);
249498e8a28Sdamien reg = RW(reg, AR9287_AN_RF2G3_OB_CCK, modal->ob_cck);
250498e8a28Sdamien reg = RW(reg, AR9287_AN_RF2G3_OB_PSK, modal->ob_psk);
251498e8a28Sdamien reg = RW(reg, AR9287_AN_RF2G3_OB_QAM, modal->ob_qam);
252498e8a28Sdamien reg = RW(reg, AR9287_AN_RF2G3_OB_PAL_OFF, modal->ob_pal_off);
253498e8a28Sdamien AR_WRITE(sc, AR9287_AN_RF2G3_CH1, reg);
254c0a11cf8Sdamien AR_WRITE_BARRIER(sc);
255498e8a28Sdamien DELAY(100);
256498e8a28Sdamien
257498e8a28Sdamien reg = AR_READ(sc, AR_PHY_RF_CTL2);
258498e8a28Sdamien reg = RW(reg, AR_PHY_TX_END_DATA_START, modal->txFrameToDataStart);
259498e8a28Sdamien reg = RW(reg, AR_PHY_TX_END_PA_ON, modal->txFrameToPaOn);
260498e8a28Sdamien AR_WRITE(sc, AR_PHY_RF_CTL2, reg);
261498e8a28Sdamien
262498e8a28Sdamien reg = AR_READ(sc, AR9287_AN_TOP2);
263498e8a28Sdamien reg = RW(reg, AR9287_AN_TOP2_XPABIAS_LVL, modal->xpaBiasLvl);
264498e8a28Sdamien AR_WRITE(sc, AR9287_AN_TOP2, reg);
265c0a11cf8Sdamien AR_WRITE_BARRIER(sc);
266498e8a28Sdamien DELAY(100);
267498e8a28Sdamien }
268498e8a28Sdamien
269498e8a28Sdamien void
ar9287_get_pdadcs(struct athn_softc * sc,struct ieee80211_channel * c,int chain,int nxpdgains,uint8_t overlap,uint8_t * boundaries,uint8_t * pdadcs)270498e8a28Sdamien ar9287_get_pdadcs(struct athn_softc *sc, struct ieee80211_channel *c,
271498e8a28Sdamien int chain, int nxpdgains, uint8_t overlap, uint8_t *boundaries,
272498e8a28Sdamien uint8_t *pdadcs)
273498e8a28Sdamien {
274498e8a28Sdamien const struct ar9287_eeprom *eep = sc->eep;
275498e8a28Sdamien const struct ar9287_cal_data_per_freq *pierdata;
276498e8a28Sdamien const uint8_t *pierfreq;
277498e8a28Sdamien struct athn_pier lopier, hipier;
278498e8a28Sdamien int16_t delta;
279498e8a28Sdamien uint8_t fbin;
280498e8a28Sdamien int i, lo, hi, npiers;
281498e8a28Sdamien
282498e8a28Sdamien pierfreq = eep->calFreqPier2G;
283498e8a28Sdamien pierdata = (const struct ar9287_cal_data_per_freq *)
284498e8a28Sdamien eep->calPierData2G[chain];
285498e8a28Sdamien npiers = AR9287_NUM_2G_CAL_PIERS;
286498e8a28Sdamien
287498e8a28Sdamien /* Find channel in ROM pier table. */
288498e8a28Sdamien fbin = athn_chan2fbin(c);
289498e8a28Sdamien athn_get_pier_ival(fbin, pierfreq, npiers, &lo, &hi);
290498e8a28Sdamien
291498e8a28Sdamien lopier.fbin = pierfreq[lo];
292498e8a28Sdamien hipier.fbin = pierfreq[hi];
293498e8a28Sdamien for (i = 0; i < nxpdgains; i++) {
294498e8a28Sdamien lopier.pwr[i] = pierdata[lo].pwrPdg[i];
295498e8a28Sdamien lopier.vpd[i] = pierdata[lo].vpdPdg[i];
296498e8a28Sdamien hipier.pwr[i] = pierdata[lo].pwrPdg[i];
297498e8a28Sdamien hipier.vpd[i] = pierdata[lo].vpdPdg[i];
298498e8a28Sdamien }
299bd6ea91dSdamien ar5008_get_pdadcs(sc, fbin, &lopier, &hipier, nxpdgains,
300498e8a28Sdamien AR9287_PD_GAIN_ICEPTS, overlap, boundaries, pdadcs);
301498e8a28Sdamien
302498e8a28Sdamien delta = (eep->baseEepHeader.pwrTableOffset -
303498e8a28Sdamien AR_PWR_TABLE_OFFSET_DB) * 2; /* In half dB. */
304498e8a28Sdamien if (delta != 0) {
305498e8a28Sdamien /* Shift the PDADC table to start at the new offset. */
306498e8a28Sdamien /* XXX Our padding value differs from Linux. */
307498e8a28Sdamien for (i = 0; i < AR_NUM_PDADC_VALUES; i++)
308498e8a28Sdamien pdadcs[i] = pdadcs[MIN(i + delta,
309498e8a28Sdamien AR_NUM_PDADC_VALUES - 1)];
310498e8a28Sdamien }
311498e8a28Sdamien }
312498e8a28Sdamien
313498e8a28Sdamien void
ar9287_olpc_get_pdgain(struct athn_softc * sc,struct ieee80211_channel * c,int chain,int8_t * pwr)314498e8a28Sdamien ar9287_olpc_get_pdgain(struct athn_softc *sc, struct ieee80211_channel *c,
315498e8a28Sdamien int chain, int8_t *pwr)
316498e8a28Sdamien {
317498e8a28Sdamien const struct ar9287_eeprom *eep = sc->eep;
318498e8a28Sdamien const struct ar_cal_data_per_freq_olpc *pierdata;
319498e8a28Sdamien const uint8_t *pierfreq;
320498e8a28Sdamien uint8_t fbin;
321498e8a28Sdamien int lo, hi, npiers;
322498e8a28Sdamien
323498e8a28Sdamien pierfreq = eep->calFreqPier2G;
324498e8a28Sdamien pierdata = (const struct ar_cal_data_per_freq_olpc *)
325498e8a28Sdamien eep->calPierData2G[chain];
326498e8a28Sdamien npiers = AR9287_NUM_2G_CAL_PIERS;
327498e8a28Sdamien
328498e8a28Sdamien /* Find channel in ROM pier table. */
329498e8a28Sdamien fbin = athn_chan2fbin(c);
330498e8a28Sdamien athn_get_pier_ival(fbin, pierfreq, npiers, &lo, &hi);
331498e8a28Sdamien
332498e8a28Sdamien #if 0
333498e8a28Sdamien *pwr = athn_interpolate(fbin,
334498e8a28Sdamien pierfreq[lo], pierdata[lo].pwrPdg[0][0],
335498e8a28Sdamien pierfreq[hi], pierdata[hi].pwrPdg[0][0]);
336498e8a28Sdamien #else
337498e8a28Sdamien *pwr = (pierdata[lo].pwrPdg[0][0] + pierdata[hi].pwrPdg[0][0]) / 2;
338498e8a28Sdamien #endif
339498e8a28Sdamien }
340498e8a28Sdamien
341498e8a28Sdamien void
ar9287_set_power_calib(struct athn_softc * sc,struct ieee80211_channel * c)342498e8a28Sdamien ar9287_set_power_calib(struct athn_softc *sc, struct ieee80211_channel *c)
343498e8a28Sdamien {
344498e8a28Sdamien const struct ar9287_eeprom *eep = sc->eep;
345498e8a28Sdamien uint8_t boundaries[AR_PD_GAINS_IN_MASK];
346498e8a28Sdamien uint8_t pdadcs[AR_NUM_PDADC_VALUES];
347498e8a28Sdamien uint8_t xpdgains[AR9287_NUM_PD_GAINS];
348498e8a28Sdamien int8_t txpower;
349498e8a28Sdamien uint8_t overlap;
350498e8a28Sdamien uint32_t reg, offset;
351498e8a28Sdamien int i, j, nxpdgains;
352498e8a28Sdamien
353498e8a28Sdamien if (sc->eep_rev < AR_EEP_MINOR_VER_2) {
354498e8a28Sdamien overlap = MS(AR_READ(sc, AR_PHY_TPCRG5),
355498e8a28Sdamien AR_PHY_TPCRG5_PD_GAIN_OVERLAP);
356498e8a28Sdamien } else
357498e8a28Sdamien overlap = eep->modalHeader.pdGainOverlap;
358498e8a28Sdamien
359498e8a28Sdamien if (sc->flags & ATHN_FLAG_OLPC) {
360498e8a28Sdamien /* XXX not here. */
361498e8a28Sdamien sc->pdadc =
362498e8a28Sdamien ((const struct ar_cal_data_per_freq_olpc *)
363498e8a28Sdamien eep->calPierData2G[0])->vpdPdg[0][0];
364498e8a28Sdamien }
365498e8a28Sdamien
366498e8a28Sdamien nxpdgains = 0;
367bd6ea91dSdamien memset(xpdgains, 0, sizeof(xpdgains));
368498e8a28Sdamien for (i = AR9287_PD_GAINS_IN_MASK - 1; i >= 0; i--) {
369498e8a28Sdamien if (nxpdgains >= AR9287_NUM_PD_GAINS)
370498e8a28Sdamien break; /* Can't happen. */
371498e8a28Sdamien if (eep->modalHeader.xpdGain & (1 << i))
372498e8a28Sdamien xpdgains[nxpdgains++] = i;
373498e8a28Sdamien }
374498e8a28Sdamien reg = AR_READ(sc, AR_PHY_TPCRG1);
375498e8a28Sdamien reg = RW(reg, AR_PHY_TPCRG1_NUM_PD_GAIN, nxpdgains - 1);
376498e8a28Sdamien reg = RW(reg, AR_PHY_TPCRG1_PD_GAIN_1, xpdgains[0]);
377498e8a28Sdamien reg = RW(reg, AR_PHY_TPCRG1_PD_GAIN_2, xpdgains[1]);
378498e8a28Sdamien AR_WRITE(sc, AR_PHY_TPCRG1, reg);
379c0a11cf8Sdamien AR_WRITE_BARRIER(sc);
380498e8a28Sdamien
381498e8a28Sdamien for (i = 0; i < AR9287_MAX_CHAINS; i++) {
382498e8a28Sdamien if (!(sc->txchainmask & (1 << i)))
383498e8a28Sdamien continue;
384498e8a28Sdamien
385498e8a28Sdamien offset = i * 0x1000;
386498e8a28Sdamien
387498e8a28Sdamien if (sc->flags & ATHN_FLAG_OLPC) {
388498e8a28Sdamien ar9287_olpc_get_pdgain(sc, c, i, &txpower);
389498e8a28Sdamien
390498e8a28Sdamien reg = AR_READ(sc, AR_PHY_TX_PWRCTRL6_0);
391498e8a28Sdamien reg = RW(reg, AR_PHY_TX_PWRCTRL_ERR_EST_MODE, 3);
392498e8a28Sdamien AR_WRITE(sc, AR_PHY_TX_PWRCTRL6_0, reg);
393498e8a28Sdamien
394498e8a28Sdamien reg = AR_READ(sc, AR_PHY_TX_PWRCTRL6_1);
395498e8a28Sdamien reg = RW(reg, AR_PHY_TX_PWRCTRL_ERR_EST_MODE, 3);
396498e8a28Sdamien AR_WRITE(sc, AR_PHY_TX_PWRCTRL6_1, reg);
397498e8a28Sdamien
398498e8a28Sdamien /* NB: txpower is in half dB. */
399498e8a28Sdamien reg = AR_READ(sc, AR_PHY_CH0_TX_PWRCTRL11 + offset);
400498e8a28Sdamien reg = RW(reg, AR_PHY_TX_PWRCTRL_OLPC_PWR, txpower);
401498e8a28Sdamien AR_WRITE(sc, AR_PHY_CH0_TX_PWRCTRL11 + offset, reg);
402498e8a28Sdamien
403c0a11cf8Sdamien AR_WRITE_BARRIER(sc);
404498e8a28Sdamien continue; /* That's it for open loop mode. */
405498e8a28Sdamien }
406498e8a28Sdamien
407498e8a28Sdamien /* Closed loop power control. */
408498e8a28Sdamien ar9287_get_pdadcs(sc, c, i, nxpdgains, overlap,
409498e8a28Sdamien boundaries, pdadcs);
410498e8a28Sdamien
411498e8a28Sdamien /* Write boundaries. */
412498e8a28Sdamien if (i == 0) {
413498e8a28Sdamien reg = SM(AR_PHY_TPCRG5_PD_GAIN_OVERLAP,
414498e8a28Sdamien overlap);
415498e8a28Sdamien reg |= SM(AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_1,
416498e8a28Sdamien boundaries[0]);
417498e8a28Sdamien reg |= SM(AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_2,
418498e8a28Sdamien boundaries[1]);
419498e8a28Sdamien reg |= SM(AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_3,
420498e8a28Sdamien boundaries[2]);
421498e8a28Sdamien reg |= SM(AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_4,
422498e8a28Sdamien boundaries[3]);
423498e8a28Sdamien AR_WRITE(sc, AR_PHY_TPCRG5 + offset, reg);
424498e8a28Sdamien }
425498e8a28Sdamien /* Write PDADC values. */
426498e8a28Sdamien for (j = 0; j < AR_NUM_PDADC_VALUES; j += 4) {
427498e8a28Sdamien AR_WRITE(sc, AR_PHY_PDADC_TBL_BASE + offset + j,
428498e8a28Sdamien pdadcs[j + 0] << 0 |
429498e8a28Sdamien pdadcs[j + 1] << 8 |
430498e8a28Sdamien pdadcs[j + 2] << 16 |
431498e8a28Sdamien pdadcs[j + 3] << 24);
432498e8a28Sdamien }
433c0a11cf8Sdamien AR_WRITE_BARRIER(sc);
434498e8a28Sdamien }
435498e8a28Sdamien }
436498e8a28Sdamien
437498e8a28Sdamien void
ar9287_set_txpower(struct athn_softc * sc,struct ieee80211_channel * c,struct ieee80211_channel * extc)438498e8a28Sdamien ar9287_set_txpower(struct athn_softc *sc, struct ieee80211_channel *c,
439498e8a28Sdamien struct ieee80211_channel *extc)
440498e8a28Sdamien {
441498e8a28Sdamien const struct ar9287_eeprom *eep = sc->eep;
442498e8a28Sdamien const struct ar9287_modal_eep_header *modal = &eep->modalHeader;
443498e8a28Sdamien uint8_t tpow_cck[4], tpow_ofdm[4];
444498e8a28Sdamien uint8_t tpow_cck_ext[4], tpow_ofdm_ext[4];
445498e8a28Sdamien uint8_t tpow_ht20[8], tpow_ht40[8];
446498e8a28Sdamien uint8_t ht40inc;
44797e63874Skrw int16_t pwr = 0, max_ant_gain, power[ATHN_POWER_COUNT];
448498e8a28Sdamien int i;
449498e8a28Sdamien
45082e6bbbcSdamien ar9287_set_power_calib(sc, c);
45182e6bbbcSdamien
452498e8a28Sdamien /* Compute transmit power reduction due to antenna gain. */
453498e8a28Sdamien max_ant_gain = MAX(modal->antennaGainCh[0], modal->antennaGainCh[1]);
454498e8a28Sdamien /* XXX */
455498e8a28Sdamien
456498e8a28Sdamien /*
457498e8a28Sdamien * Reduce scaled power by number of active chains to get per-chain
458498e8a28Sdamien * transmit power level.
459498e8a28Sdamien */
460498e8a28Sdamien if (sc->ntxchains == 2)
461498e8a28Sdamien pwr -= AR_PWR_DECREASE_FOR_2_CHAIN;
462498e8a28Sdamien if (pwr < 0)
463498e8a28Sdamien pwr = 0;
464498e8a28Sdamien
465498e8a28Sdamien /* Get CCK target powers. */
466bd6ea91dSdamien ar5008_get_lg_tpow(sc, c, AR_CTL_11B, eep->calTargetPowerCck,
467498e8a28Sdamien AR9287_NUM_2G_CCK_TARGET_POWERS, tpow_cck);
468498e8a28Sdamien
469498e8a28Sdamien /* Get OFDM target powers. */
470bd6ea91dSdamien ar5008_get_lg_tpow(sc, c, AR_CTL_11G, eep->calTargetPower2G,
471498e8a28Sdamien AR9287_NUM_2G_20_TARGET_POWERS, tpow_ofdm);
472498e8a28Sdamien
473498e8a28Sdamien /* Get HT-20 target powers. */
474bd6ea91dSdamien ar5008_get_ht_tpow(sc, c, AR_CTL_2GHT20, eep->calTargetPower2GHT20,
475498e8a28Sdamien AR9287_NUM_2G_20_TARGET_POWERS, tpow_ht20);
476498e8a28Sdamien
477498e8a28Sdamien if (extc != NULL) {
478498e8a28Sdamien /* Get HT-40 target powers. */
479bd6ea91dSdamien ar5008_get_ht_tpow(sc, c, AR_CTL_2GHT40,
480498e8a28Sdamien eep->calTargetPower2GHT40, AR9287_NUM_2G_40_TARGET_POWERS,
481498e8a28Sdamien tpow_ht40);
482498e8a28Sdamien
483498e8a28Sdamien /* Get secondary channel CCK target powers. */
484bd6ea91dSdamien ar5008_get_lg_tpow(sc, extc, AR_CTL_11B,
485bd6ea91dSdamien eep->calTargetPowerCck, AR9287_NUM_2G_CCK_TARGET_POWERS,
486bd6ea91dSdamien tpow_cck_ext);
487498e8a28Sdamien
488498e8a28Sdamien /* Get secondary channel OFDM target powers. */
489bd6ea91dSdamien ar5008_get_lg_tpow(sc, extc, AR_CTL_11G,
490bd6ea91dSdamien eep->calTargetPower2G, AR9287_NUM_2G_20_TARGET_POWERS,
491bd6ea91dSdamien tpow_ofdm_ext);
492498e8a28Sdamien }
493498e8a28Sdamien
494bd6ea91dSdamien memset(power, 0, sizeof(power));
495*4b1a56afSjsg /* Shuffle target powers across transmit rates. */
496498e8a28Sdamien power[ATHN_POWER_OFDM6 ] =
497498e8a28Sdamien power[ATHN_POWER_OFDM9 ] =
498498e8a28Sdamien power[ATHN_POWER_OFDM12 ] =
499498e8a28Sdamien power[ATHN_POWER_OFDM18 ] =
500498e8a28Sdamien power[ATHN_POWER_OFDM24 ] = tpow_ofdm[0];
501498e8a28Sdamien power[ATHN_POWER_OFDM36 ] = tpow_ofdm[1];
502498e8a28Sdamien power[ATHN_POWER_OFDM48 ] = tpow_ofdm[2];
503498e8a28Sdamien power[ATHN_POWER_OFDM54 ] = tpow_ofdm[3];
504498e8a28Sdamien power[ATHN_POWER_XR ] = tpow_ofdm[0];
505498e8a28Sdamien power[ATHN_POWER_CCK1_LP ] = tpow_cck[0];
506498e8a28Sdamien power[ATHN_POWER_CCK2_LP ] =
507498e8a28Sdamien power[ATHN_POWER_CCK2_SP ] = tpow_cck[1];
508498e8a28Sdamien power[ATHN_POWER_CCK55_LP] =
509498e8a28Sdamien power[ATHN_POWER_CCK55_SP] = tpow_cck[2];
510498e8a28Sdamien power[ATHN_POWER_CCK11_LP] =
511498e8a28Sdamien power[ATHN_POWER_CCK11_SP] = tpow_cck[3];
512498e8a28Sdamien for (i = 0; i < nitems(tpow_ht20); i++)
513498e8a28Sdamien power[ATHN_POWER_HT20(i)] = tpow_ht20[i];
514498e8a28Sdamien if (extc != NULL) {
515498e8a28Sdamien /* Correct PAR difference between HT40 and HT20/Legacy. */
516498e8a28Sdamien if (sc->eep_rev >= AR_EEP_MINOR_VER_2)
517498e8a28Sdamien ht40inc = modal->ht40PowerIncForPdadc;
518498e8a28Sdamien else
519498e8a28Sdamien ht40inc = AR_HT40_POWER_INC_FOR_PDADC;
520498e8a28Sdamien for (i = 0; i < nitems(tpow_ht40); i++)
521498e8a28Sdamien power[ATHN_POWER_HT40(i)] = tpow_ht40[i] + ht40inc;
522498e8a28Sdamien power[ATHN_POWER_OFDM_DUP] = tpow_ht40[0];
523498e8a28Sdamien power[ATHN_POWER_CCK_DUP ] = tpow_ht40[0];
524498e8a28Sdamien power[ATHN_POWER_OFDM_EXT] = tpow_ofdm_ext[0];
525498e8a28Sdamien if (IEEE80211_IS_CHAN_2GHZ(c))
526498e8a28Sdamien power[ATHN_POWER_CCK_EXT] = tpow_cck_ext[0];
527498e8a28Sdamien }
528498e8a28Sdamien
529498e8a28Sdamien for (i = 0; i < ATHN_POWER_COUNT; i++) {
530498e8a28Sdamien power[i] -= AR_PWR_TABLE_OFFSET_DB * 2; /* In half dB. */
531498e8a28Sdamien if (power[i] > AR_MAX_RATE_POWER)
532498e8a28Sdamien power[i] = AR_MAX_RATE_POWER;
533498e8a28Sdamien }
534498e8a28Sdamien /* Commit transmit power values to hardware. */
535bd6ea91dSdamien ar5008_write_txpower(sc, power);
536498e8a28Sdamien }
537498e8a28Sdamien
538498e8a28Sdamien void
ar9287_olpc_init(struct athn_softc * sc)539498e8a28Sdamien ar9287_olpc_init(struct athn_softc *sc)
540498e8a28Sdamien {
541498e8a28Sdamien uint32_t reg;
542498e8a28Sdamien
543498e8a28Sdamien AR_SETBITS(sc, AR_PHY_TX_PWRCTRL9, AR_PHY_TX_PWRCTRL9_RES_DC_REMOVAL);
544498e8a28Sdamien
545498e8a28Sdamien reg = AR_READ(sc, AR9287_AN_TXPC0);
546498e8a28Sdamien reg = RW(reg, AR9287_AN_TXPC0_TXPCMODE,
547498e8a28Sdamien AR9287_AN_TXPC0_TXPCMODE_TEMPSENSE);
548498e8a28Sdamien AR_WRITE(sc, AR9287_AN_TXPC0, reg);
549c0a11cf8Sdamien AR_WRITE_BARRIER(sc);
550498e8a28Sdamien DELAY(100);
551498e8a28Sdamien }
552498e8a28Sdamien
553498e8a28Sdamien void
ar9287_olpc_temp_compensation(struct athn_softc * sc)554498e8a28Sdamien ar9287_olpc_temp_compensation(struct athn_softc *sc)
555498e8a28Sdamien {
556498e8a28Sdamien const struct ar9287_eeprom *eep = sc->eep;
557498e8a28Sdamien int8_t pdadc, slope, tcomp;
558498e8a28Sdamien uint32_t reg;
559498e8a28Sdamien
560498e8a28Sdamien reg = AR_READ(sc, AR_PHY_TX_PWRCTRL4);
561498e8a28Sdamien pdadc = MS(reg, AR_PHY_TX_PWRCTRL_PD_AVG_OUT);
562a67c32bdSdamien DPRINTFN(3, ("PD Avg Out=%d\n", pdadc));
563498e8a28Sdamien
564498e8a28Sdamien if (sc->pdadc == 0 || pdadc == 0)
565498e8a28Sdamien return; /* No frames transmitted yet. */
566498e8a28Sdamien
567498e8a28Sdamien /* Compute Tx gain temperature compensation. */
568498e8a28Sdamien if (sc->eep_rev >= AR_EEP_MINOR_VER_2)
569498e8a28Sdamien slope = eep->baseEepHeader.tempSensSlope;
570498e8a28Sdamien else
571498e8a28Sdamien slope = 0;
572498e8a28Sdamien if (slope != 0) /* Prevents division by zero. */
573498e8a28Sdamien tcomp = ((pdadc - sc->pdadc) * 4) / slope;
574498e8a28Sdamien else
575498e8a28Sdamien tcomp = 0;
576a67c32bdSdamien DPRINTFN(3, ("OLPC temp compensation=%d\n", tcomp));
577498e8a28Sdamien
578498e8a28Sdamien /* Write compensation value for both Tx chains. */
579498e8a28Sdamien reg = AR_READ(sc, AR_PHY_CH0_TX_PWRCTRL11);
580498e8a28Sdamien reg = RW(reg, AR_PHY_TX_PWRCTRL_OLPC_TEMP_COMP, tcomp);
581498e8a28Sdamien AR_WRITE(sc, AR_PHY_CH0_TX_PWRCTRL11, reg);
582498e8a28Sdamien
583498e8a28Sdamien reg = AR_READ(sc, AR_PHY_CH1_TX_PWRCTRL11);
584498e8a28Sdamien reg = RW(reg, AR_PHY_TX_PWRCTRL_OLPC_TEMP_COMP, tcomp);
585498e8a28Sdamien AR_WRITE(sc, AR_PHY_CH1_TX_PWRCTRL11, reg);
586c0a11cf8Sdamien AR_WRITE_BARRIER(sc);
587498e8a28Sdamien }
588498e8a28Sdamien
589498e8a28Sdamien void
ar9287_1_3_enable_async_fifo(struct athn_softc * sc)5901c1c6997Sdamien ar9287_1_3_enable_async_fifo(struct athn_softc *sc)
591498e8a28Sdamien {
592498e8a28Sdamien /* Enable ASYNC FIFO. */
593498e8a28Sdamien AR_SETBITS(sc, AR_MAC_PCU_ASYNC_FIFO_REG3,
594498e8a28Sdamien AR_MAC_PCU_ASYNC_FIFO_REG3_DATAPATH_SEL);
595498e8a28Sdamien AR_SETBITS(sc, AR_PHY_MODE, AR_PHY_MODE_ASYNCFIFO);
596498e8a28Sdamien AR_CLRBITS(sc, AR_MAC_PCU_ASYNC_FIFO_REG3,
597498e8a28Sdamien AR_MAC_PCU_ASYNC_FIFO_REG3_SOFT_RESET);
598498e8a28Sdamien AR_SETBITS(sc, AR_MAC_PCU_ASYNC_FIFO_REG3,
599498e8a28Sdamien AR_MAC_PCU_ASYNC_FIFO_REG3_SOFT_RESET);
600c0a11cf8Sdamien AR_WRITE_BARRIER(sc);
601498e8a28Sdamien }
602498e8a28Sdamien
603498e8a28Sdamien void
ar9287_1_3_setup_async_fifo(struct athn_softc * sc)6041c1c6997Sdamien ar9287_1_3_setup_async_fifo(struct athn_softc *sc)
605498e8a28Sdamien {
606498e8a28Sdamien uint32_t reg;
607498e8a28Sdamien
60882e6bbbcSdamien /*
60982e6bbbcSdamien * MAC runs at 117MHz (instead of 88/44MHz) when ASYNC FIFO is
61082e6bbbcSdamien * enabled, so the following counters have to be changed.
61182e6bbbcSdamien */
612498e8a28Sdamien AR_WRITE(sc, AR_D_GBL_IFS_SIFS, AR_D_GBL_IFS_SIFS_ASYNC_FIFO_DUR);
613498e8a28Sdamien AR_WRITE(sc, AR_D_GBL_IFS_SLOT, AR_D_GBL_IFS_SLOT_ASYNC_FIFO_DUR);
614498e8a28Sdamien AR_WRITE(sc, AR_D_GBL_IFS_EIFS, AR_D_GBL_IFS_EIFS_ASYNC_FIFO_DUR);
615498e8a28Sdamien
616498e8a28Sdamien AR_WRITE(sc, AR_TIME_OUT, AR_TIME_OUT_ACK_CTS_ASYNC_FIFO_DUR);
617498e8a28Sdamien AR_WRITE(sc, AR_USEC, AR_USEC_ASYNC_FIFO_DUR);
618498e8a28Sdamien
619498e8a28Sdamien AR_SETBITS(sc, AR_MAC_PCU_LOGIC_ANALYZER,
620498e8a28Sdamien AR_MAC_PCU_LOGIC_ANALYZER_DISBUG20768);
621498e8a28Sdamien
622498e8a28Sdamien reg = AR_READ(sc, AR_AHB_MODE);
623498e8a28Sdamien reg = RW(reg, AR_AHB_CUSTOM_BURST, AR_AHB_CUSTOM_BURST_ASYNC_FIFO_VAL);
624498e8a28Sdamien AR_WRITE(sc, AR_AHB_MODE, reg);
625498e8a28Sdamien
626498e8a28Sdamien AR_SETBITS(sc, AR_PCU_MISC_MODE2, AR_PCU_MISC_MODE2_ENABLE_AGGWEP);
627c0a11cf8Sdamien AR_WRITE_BARRIER(sc);
628498e8a28Sdamien }
629