ah_regdomain.c revision 224718
1193323Sed/*
2193323Sed * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting
3193323Sed * Copyright (c) 2005-2006 Atheros Communications, Inc.
4193323Sed * All rights reserved.
5193323Sed *
6193323Sed * Permission to use, copy, modify, and/or distribute this software for any
7193323Sed * purpose with or without fee is hereby granted, provided that the above
8193323Sed * copyright notice and this permission notice appear in all copies.
9193323Sed *
10193323Sed * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11193323Sed * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12193323Sed * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13193323Sed * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14193323Sed * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15193323Sed * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16193323Sed * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17193323Sed *
18193323Sed * $FreeBSD: head/sys/dev/ath/ath_hal/ah_regdomain.c 224718 2011-08-08 17:33:35Z adrian $
19193323Sed */
20193323Sed#include "opt_ah.h"
21193323Sed
22198892Srdivacky#include "ah.h"
23193323Sed
24193323Sed#include <net80211/_ieee80211.h>
25193323Sed#include <net80211/ieee80211_regdomain.h>
26193323Sed
27193323Sed#include "ah_internal.h"
28193323Sed#include "ah_eeprom.h"
29193323Sed#include "ah_devid.h"
30193323Sed
31198090Srdivacky#include "ah_regdomain.h"
32198090Srdivacky
33198090Srdivacky/*
34193323Sed * XXX this code needs a audit+review
35198090Srdivacky */
36198090Srdivacky
37193323Sed/* used throughout this file... */
38193323Sed#define	N(a)	(sizeof (a) / sizeof (a[0]))
39193323Sed
40193323Sed#define HAL_MODE_11A_TURBO	HAL_MODE_108A
41193323Sed#define HAL_MODE_11G_TURBO	HAL_MODE_108G
42193323Sed
43198090Srdivacky/*
44198892Srdivacky * Mask to check whether a domain is a multidomain or a single domain
45193323Sed */
46193323Sed#define MULTI_DOMAIN_MASK 0xFF00
47193323Sed
48193323Sed/*
49205218Srdivacky * Enumerated Regulatory Domain Information 8 bit values indicate that
50193323Sed * the regdomain is really a pair of unitary regdomains.  12 bit values
51193323Sed * are the real unitary regdomains and are the only ones which have the
52193323Sed * frequency bitmasks and flags set.
53193323Sed */
54193323Sed#include "ah_regdomain/ah_rd_regenum.h"
55193323Sed
56212904Sdim#define	WORLD_SKU_MASK		0x00F0
57193323Sed#define	WORLD_SKU_PREFIX	0x0060
58193323Sed
59193323Sed/*
60193323Sed * THE following table is the mapping of regdomain pairs specified by
61212904Sdim * an 8 bit regdomain value to the individual unitary reg domains
62193323Sed */
63193323Sed#include "ah_regdomain/ah_rd_regmap.h"
64193323Sed
65193323Sed/*
66193323Sed * The following tables are the master list for all different freqeuncy
67193323Sed * bands with the complete matrix of all possible flags and settings
68193323Sed * for each band if it is used in ANY reg domain.
69193323Sed */
70193323Sed
71224145Sdim#define	COUNTRY_ERD_FLAG        0x8000
72193323Sed#define WORLDWIDE_ROAMING_FLAG  0x4000
73193323Sed
74198090Srdivacky/*
75193323Sed * This table maps country ISO codes from net80211 into regulatory
76193323Sed * domains which the ath regulatory domain code understands.
77193323Sed */
78193323Sed#include "ah_regdomain/ah_rd_ctry.h"
79193323Sed
80193323Sed/*
81207618Srdivacky * The frequency band collections are a set of frequency ranges
82193323Sed * with shared properties - max tx power, max antenna gain, channel width,
83199481Srdivacky * channel spacing, DFS requirements and passive scanning requirements.
84193323Sed *
85193323Sed * These are represented as entries in a frequency band bitmask.
86193323Sed * Each regulatory domain entry in ah_regdomain_domains.h uses one
87193323Sed * or more frequency band entries for each of the channel modes
88193323Sed * supported (11bg, 11a, half, quarter, turbo, etc.)
89193323Sed *
90193323Sed */
91198090Srdivacky#include "ah_regdomain/ah_rd_freqbands.h"
92193323Sed
93193323Sed/*
94193323Sed * This is the main regulatory database. It defines the supported
95193323Sed * set of features and requirements for each of the defined regulatory
96193323Sed * zones. It uses combinations of frequency ranges - represented in
97193323Sed * a bitmask - to determine the requirements and limitations needed.
98193323Sed */
99193323Sed#include "ah_regdomain/ah_rd_domains.h"
100193323Sed
101193323Sedstatic const struct cmode modes[] = {
102193323Sed	{ HAL_MODE_TURBO,	IEEE80211_CHAN_ST },
103193323Sed	{ HAL_MODE_11A,		IEEE80211_CHAN_A },
104193323Sed	{ HAL_MODE_11B,		IEEE80211_CHAN_B },
105198090Srdivacky	{ HAL_MODE_11G,		IEEE80211_CHAN_G },
106193323Sed	{ HAL_MODE_11G_TURBO,	IEEE80211_CHAN_108G },
107193323Sed	{ HAL_MODE_11A_TURBO,	IEEE80211_CHAN_108A },
108193323Sed	{ HAL_MODE_11A_QUARTER_RATE,
109198090Srdivacky	  IEEE80211_CHAN_A | IEEE80211_CHAN_QUARTER },
110198090Srdivacky	{ HAL_MODE_11A_HALF_RATE,
111193323Sed	  IEEE80211_CHAN_A | IEEE80211_CHAN_HALF },
112193323Sed	{ HAL_MODE_11G_QUARTER_RATE,
113193323Sed	  IEEE80211_CHAN_G | IEEE80211_CHAN_QUARTER },
114193323Sed	{ HAL_MODE_11G_HALF_RATE,
115193323Sed	  IEEE80211_CHAN_G | IEEE80211_CHAN_HALF },
116205218Srdivacky	{ HAL_MODE_11NG_HT20,	IEEE80211_CHAN_G | IEEE80211_CHAN_HT20 },
117205218Srdivacky	{ HAL_MODE_11NG_HT40PLUS,
118193323Sed	  IEEE80211_CHAN_G | IEEE80211_CHAN_HT40U },
119193323Sed	{ HAL_MODE_11NG_HT40MINUS,
120193323Sed	  IEEE80211_CHAN_G | IEEE80211_CHAN_HT40D },
121193323Sed	{ HAL_MODE_11NA_HT20,	IEEE80211_CHAN_A | IEEE80211_CHAN_HT20 },
122193323Sed	{ HAL_MODE_11NA_HT40PLUS,
123193323Sed	  IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U },
124193323Sed	{ HAL_MODE_11NA_HT40MINUS,
125202375Srdivacky	  IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D },
126198090Srdivacky};
127193323Sed
128193323Sedstatic void ath_hal_update_dfsdomain(struct ath_hal *ah);
129193323Sed
130193323Sedstatic OS_INLINE uint16_t
131218893SdimgetEepromRD(struct ath_hal *ah)
132193323Sed{
133224145Sdim	return AH_PRIVATE(ah)->ah_currentRD &~ WORLDWIDE_ROAMING_FLAG;
134193323Sed}
135193323Sed
136193323Sed/*
137193323Sed * Test to see if the bitmask array is all zeros
138210299Sed */
139193323Sedstatic HAL_BOOL
140193323SedisChanBitMaskZero(const uint64_t *bitmask)
141193323Sed{
142193323Sed#if BMLEN > 2
143193323Sed#error	"add more cases"
144193323Sed#endif
145193323Sed#if BMLEN > 1
146212904Sdim	if (bitmask[1] != 0)
147212904Sdim		return AH_FALSE;
148212904Sdim#endif
149212904Sdim	return (bitmask[0] == 0);
150212904Sdim}
151224145Sdim
152212904Sdim/*
153212904Sdim * Return whether or not the regulatory domain/country in EEPROM
154212904Sdim * is acceptable.
155212904Sdim */
156212904Sdimstatic HAL_BOOL
157212904SdimisEepromValid(struct ath_hal *ah)
158212904Sdim{
159212904Sdim	uint16_t rd = getEepromRD(ah);
160212904Sdim	int i;
161212904Sdim
162224145Sdim	if (rd & COUNTRY_ERD_FLAG) {
163212904Sdim		uint16_t cc = rd &~ COUNTRY_ERD_FLAG;
164212904Sdim		for (i = 0; i < N(allCountries); i++)
165212904Sdim			if (allCountries[i].countryCode == cc)
166212904Sdim				return AH_TRUE;
167212904Sdim	} else {
168212904Sdim		for (i = 0; i < N(regDomainPairs); i++)
169212904Sdim			if (regDomainPairs[i].regDmnEnum == rd)
170226633Sdim				return AH_TRUE;
171212904Sdim	}
172212904Sdim	HALDEBUG_G(ah, HAL_DEBUG_REGDOMAIN,
173212904Sdim	    "%s: invalid regulatory domain/country code 0x%x\n", __func__, rd);
174212904Sdim	return AH_FALSE;
175212904Sdim}
176212904Sdim
177212904Sdim/*
178212904Sdim * Find the pointer to the country element in the country table
179212904Sdim * corresponding to the country code
180212904Sdim */
181212904Sdimstatic COUNTRY_CODE_TO_ENUM_RD*
182212904SdimfindCountry(HAL_CTRY_CODE countryCode)
183212904Sdim{
184212904Sdim	int i;
185212904Sdim
186212904Sdim	for (i = 0; i < N(allCountries); i++) {
187212904Sdim		if (allCountries[i].countryCode == countryCode)
188212904Sdim			return &allCountries[i];
189212904Sdim	}
190212904Sdim	return AH_NULL;
191212904Sdim}
192212904Sdim
193212904Sdimstatic REG_DOMAIN *
194212904SdimfindRegDmn(int regDmn)
195212904Sdim{
196212904Sdim	int i;
197212904Sdim
198212904Sdim	for (i = 0; i < N(regDomains); i++) {
199212904Sdim		if (regDomains[i].regDmnEnum == regDmn)
200212904Sdim			return &regDomains[i];
201212904Sdim	}
202212904Sdim	return AH_NULL;
203212904Sdim}
204212904Sdim
205212904Sdimstatic REG_DMN_PAIR_MAPPING *
206212904SdimfindRegDmnPair(int regDmnPair)
207212904Sdim{
208212904Sdim	int i;
209212904Sdim
210212904Sdim	if (regDmnPair != NO_ENUMRD) {
211212904Sdim		for (i = 0; i < N(regDomainPairs); i++) {
212212904Sdim			if (regDomainPairs[i].regDmnEnum == regDmnPair)
213212904Sdim				return &regDomainPairs[i];
214212904Sdim		}
215212904Sdim	}
216212904Sdim	return AH_NULL;
217212904Sdim}
218212904Sdim
219212904Sdim/*
220212904Sdim * Calculate a default country based on the EEPROM setting.
221212904Sdim */
222212904Sdimstatic HAL_CTRY_CODE
223212904SdimgetDefaultCountry(struct ath_hal *ah)
224212904Sdim{
225212904Sdim	REG_DMN_PAIR_MAPPING *regpair;
226212904Sdim	uint16_t rd;
227212904Sdim
228212904Sdim	rd = getEepromRD(ah);
229212904Sdim	if (rd & COUNTRY_ERD_FLAG) {
230212904Sdim		COUNTRY_CODE_TO_ENUM_RD *country;
231212904Sdim		uint16_t cc = rd & ~COUNTRY_ERD_FLAG;
232212904Sdim		country = findCountry(cc);
233212904Sdim		if (country != AH_NULL)
234212904Sdim			return cc;
235212904Sdim	}
236212904Sdim	/*
237212904Sdim	 * Check reg domains that have only one country
238212904Sdim	 */
239212904Sdim	regpair = findRegDmnPair(rd);
240212904Sdim	return (regpair != AH_NULL) ? regpair->singleCC : CTRY_DEFAULT;
241212904Sdim}
242212904Sdim
243193323Sedstatic HAL_BOOL
244193323SedIS_BIT_SET(int bit, const uint64_t bitmask[])
245193323Sed{
246193323Sed	int byteOffset, bitnum;
247193323Sed	uint64_t val;
248193323Sed
249193323Sed	byteOffset = bit/64;
250193323Sed	bitnum = bit - byteOffset*64;
251193323Sed	val = ((uint64_t) 1) << bitnum;
252193323Sed	return (bitmask[byteOffset] & val) != 0;
253193323Sed}
254193323Sed
255193323Sedstatic HAL_STATUS
256193323Sedgetregstate(struct ath_hal *ah, HAL_CTRY_CODE cc, HAL_REG_DOMAIN regDmn,
257193323Sed    COUNTRY_CODE_TO_ENUM_RD **pcountry,
258193323Sed    REG_DOMAIN **prd2GHz, REG_DOMAIN **prd5GHz)
259193323Sed{
260207618Srdivacky	COUNTRY_CODE_TO_ENUM_RD *country;
261207618Srdivacky	REG_DOMAIN *rd5GHz, *rd2GHz;
262193323Sed
263193323Sed	if (cc == CTRY_DEFAULT && regDmn == SKU_NONE) {
264193323Sed		/*
265198090Srdivacky		 * Validate the EEPROM setting and setup defaults
266193323Sed		 */
267193323Sed		if (!isEepromValid(ah)) {
268193323Sed			/*
269193323Sed			 * Don't return any channels if the EEPROM has an
270193323Sed			 * invalid regulatory domain/country code setting.
271193323Sed			 */
272207618Srdivacky			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
273207618Srdivacky			    "%s: invalid EEPROM contents\n",__func__);
274193323Sed			return HAL_EEBADREG;
275207618Srdivacky		}
276193323Sed
277193323Sed		cc = getDefaultCountry(ah);
278193323Sed		country = findCountry(cc);
279193323Sed		if (country == AH_NULL) {
280193323Sed			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
281193323Sed			    "NULL Country!, cc %d\n", cc);
282193323Sed			return HAL_EEBADCC;
283193323Sed		}
284193323Sed		regDmn = country->regDmnEnum;
285193323Sed		HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: EEPROM cc %u rd 0x%x\n",
286193323Sed		    __func__, cc, regDmn);
287193323Sed
288193323Sed		if (country->countryCode == CTRY_DEFAULT) {
289193323Sed			/*
290193323Sed			 * Check EEPROM; SKU may be for a country, single
291203954Srdivacky			 * domain, or multiple domains (WWR).
292203954Srdivacky			 */
293203954Srdivacky			uint16_t rdnum = getEepromRD(ah);
294203954Srdivacky			if ((rdnum & COUNTRY_ERD_FLAG) == 0 &&
295203954Srdivacky			    (findRegDmn(rdnum) != AH_NULL ||
296203954Srdivacky			     findRegDmnPair(rdnum) != AH_NULL)) {
297193323Sed				regDmn = rdnum;
298203954Srdivacky				HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
299203954Srdivacky				    "%s: EEPROM rd 0x%x\n", __func__, rdnum);
300193323Sed			}
301193323Sed		}
302193323Sed	} else {
303193323Sed		country = findCountry(cc);
304193323Sed		if (country == AH_NULL) {
305193323Sed			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
306193323Sed			    "unknown country, cc %d\n", cc);
307193323Sed			return HAL_EINVAL;
308193323Sed		}
309193323Sed		if (regDmn == SKU_NONE)
310193323Sed			regDmn = country->regDmnEnum;
311193323Sed		HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: cc %u rd 0x%x\n",
312193323Sed		    __func__, cc, regDmn);
313193323Sed	}
314193323Sed
315193323Sed	/*
316193323Sed	 * Setup per-band state.
317193323Sed	 */
318193323Sed	if ((regDmn & MULTI_DOMAIN_MASK) == 0) {
319193323Sed		REG_DMN_PAIR_MAPPING *regpair = findRegDmnPair(regDmn);
320193323Sed		if (regpair == AH_NULL) {
321193323Sed			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
322193323Sed			    "%s: no reg domain pair %u for country %u\n",
323193323Sed			    __func__, regDmn, country->countryCode);
324193323Sed			return HAL_EINVAL;
325193323Sed		}
326193323Sed		rd5GHz = findRegDmn(regpair->regDmn5GHz);
327193323Sed		if (rd5GHz == AH_NULL) {
328193323Sed			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
329193323Sed			    "%s: no 5GHz reg domain %u for country %u\n",
330193323Sed			    __func__, regpair->regDmn5GHz, country->countryCode);
331193323Sed			return HAL_EINVAL;
332193323Sed		}
333193323Sed		rd2GHz = findRegDmn(regpair->regDmn2GHz);
334193323Sed		if (rd2GHz == AH_NULL) {
335193323Sed			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
336193323Sed			    "%s: no 2GHz reg domain %u for country %u\n",
337193323Sed			    __func__, regpair->regDmn2GHz, country->countryCode);
338193323Sed			return HAL_EINVAL;
339193323Sed		}
340193323Sed	} else {
341193323Sed		rd5GHz = rd2GHz = findRegDmn(regDmn);
342193323Sed		if (rd2GHz == AH_NULL) {
343193323Sed			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
344193323Sed			    "%s: no unitary reg domain %u for country %u\n",
345193323Sed			    __func__, regDmn, country->countryCode);
346193323Sed			return HAL_EINVAL;
347193323Sed		}
348193323Sed	}
349193323Sed	if (pcountry != AH_NULL)
350193323Sed		*pcountry = country;
351193323Sed	*prd2GHz = rd2GHz;
352193323Sed	*prd5GHz = rd5GHz;
353193323Sed	return HAL_OK;
354193323Sed}
355193323Sed
356226633Sdim/*
357193323Sed * Construct the channel list for the specified regulatory config.
358193323Sed */
359193323Sedstatic HAL_STATUS
360193323Sedgetchannels(struct ath_hal *ah,
361193323Sed    struct ieee80211_channel chans[], u_int maxchans, int *nchans,
362193323Sed    u_int modeSelect, HAL_CTRY_CODE cc, HAL_REG_DOMAIN regDmn,
363193323Sed    HAL_BOOL enableExtendedChannels,
364193323Sed    COUNTRY_CODE_TO_ENUM_RD **pcountry,
365193323Sed    REG_DOMAIN **prd2GHz, REG_DOMAIN **prd5GHz)
366193323Sed{
367193323Sed#define CHANNEL_HALF_BW		10
368193323Sed#define CHANNEL_QUARTER_BW	5
369193323Sed#define	HAL_MODE_11A_ALL \
370193323Sed	(HAL_MODE_11A | HAL_MODE_11A_TURBO | HAL_MODE_TURBO | \
371193323Sed	 HAL_MODE_11A_QUARTER_RATE | HAL_MODE_11A_HALF_RATE)
372193323Sed	REG_DOMAIN *rd5GHz, *rd2GHz;
373193323Sed	u_int modesAvail;
374193323Sed	const struct cmode *cm;
375193323Sed	struct ieee80211_channel *ic;
376193323Sed	int next, b;
377193323Sed	HAL_STATUS status;
378193323Sed
379193323Sed	HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: cc %u regDmn 0x%x mode 0x%x%s\n",
380193323Sed	    __func__, cc, regDmn, modeSelect,
381193323Sed	    enableExtendedChannels ? " ecm" : "");
382193323Sed
383193323Sed	status = getregstate(ah, cc, regDmn, pcountry, &rd2GHz, &rd5GHz);
384193323Sed	if (status != HAL_OK)
385193323Sed		return status;
386193323Sed
387198090Srdivacky	/* get modes that HW is capable of */
388198090Srdivacky	modesAvail = ath_hal_getWirelessModes(ah);
389198090Srdivacky	/* optimize work below if no 11a channels */
390193323Sed	if (isChanBitMaskZero(rd5GHz->chan11a) &&
391198090Srdivacky	    (modesAvail & HAL_MODE_11A_ALL)) {
392198090Srdivacky		HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
393198090Srdivacky		    "%s: disallow all 11a\n", __func__);
394198090Srdivacky		modesAvail &= ~HAL_MODE_11A_ALL;
395198090Srdivacky	}
396198090Srdivacky
397198090Srdivacky	next = 0;
398193323Sed	ic = &chans[0];
399193323Sed	for (cm = modes; cm < &modes[N(modes)]; cm++) {
400193323Sed		uint16_t c, c_hi, c_lo;
401193323Sed		uint64_t *channelBM = AH_NULL;
402198090Srdivacky		REG_DMN_FREQ_BAND *fband = AH_NULL,*freqs;
403198090Srdivacky		int low_adj, hi_adj, channelSep, lastc;
404198090Srdivacky		uint32_t rdflags;
405193323Sed		uint64_t dfsMask;
406193323Sed		uint64_t pscan;
407193323Sed
408193323Sed		if ((cm->mode & modeSelect) == 0) {
409193323Sed			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
410193323Sed			    "%s: skip mode 0x%x flags 0x%x\n",
411198090Srdivacky			    __func__, cm->mode, cm->flags);
412193323Sed			continue;
413193323Sed		}
414198090Srdivacky		if ((cm->mode & modesAvail) == 0) {
415198090Srdivacky			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
416198090Srdivacky			    "%s: !avail mode 0x%x (0x%x) flags 0x%x\n",
417193323Sed			    __func__, modesAvail, cm->mode, cm->flags);
418193323Sed			continue;
419198090Srdivacky		}
420198090Srdivacky		if (!ath_hal_getChannelEdges(ah, cm->flags, &c_lo, &c_hi)) {
421193323Sed			/* channel not supported by hardware, skip it */
422193323Sed			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
423198090Srdivacky			    "%s: channels 0x%x not supported by hardware\n",
424198090Srdivacky			    __func__,cm->flags);
425199481Srdivacky			continue;
426198090Srdivacky		}
427198090Srdivacky		switch (cm->mode) {
428193323Sed		case HAL_MODE_TURBO:
429198090Srdivacky		case HAL_MODE_11A_TURBO:
430198090Srdivacky			rdflags = rd5GHz->flags;
431193323Sed			dfsMask = rd5GHz->dfsMask;
432198090Srdivacky			pscan = rd5GHz->pscan;
433198090Srdivacky			if (cm->mode == HAL_MODE_TURBO)
434193323Sed				channelBM = rd5GHz->chan11a_turbo;
435193323Sed			else
436193323Sed				channelBM = rd5GHz->chan11a_dyn_turbo;
437193323Sed			freqs = &regDmn5GhzTurboFreq[0];
438193323Sed			break;
439198090Srdivacky		case HAL_MODE_11G_TURBO:
440198090Srdivacky			rdflags = rd2GHz->flags;
441193323Sed			dfsMask = rd2GHz->dfsMask;
442193323Sed			pscan = rd2GHz->pscan;
443193323Sed			channelBM = rd2GHz->chan11g_turbo;
444193323Sed			freqs = &regDmn2Ghz11gTurboFreq[0];
445193323Sed			break;
446193323Sed		case HAL_MODE_11A:
447193323Sed		case HAL_MODE_11A_HALF_RATE:
448198090Srdivacky		case HAL_MODE_11A_QUARTER_RATE:
449198090Srdivacky		case HAL_MODE_11NA_HT20:
450193323Sed		case HAL_MODE_11NA_HT40PLUS:
451198090Srdivacky		case HAL_MODE_11NA_HT40MINUS:
452193323Sed			rdflags = rd5GHz->flags;
453193323Sed			dfsMask = rd5GHz->dfsMask;
454193323Sed			pscan = rd5GHz->pscan;
455193323Sed			if (cm->mode == HAL_MODE_11A_HALF_RATE)
456193323Sed				channelBM = rd5GHz->chan11a_half;
457193323Sed			else if (cm->mode == HAL_MODE_11A_QUARTER_RATE)
458198090Srdivacky				channelBM = rd5GHz->chan11a_quarter;
459193323Sed			else
460193323Sed				channelBM = rd5GHz->chan11a;
461193323Sed			freqs = &regDmn5GhzFreq[0];
462193323Sed			break;
463193323Sed		case HAL_MODE_11B:
464193323Sed		case HAL_MODE_11G:
465193323Sed		case HAL_MODE_11G_HALF_RATE:
466193323Sed		case HAL_MODE_11G_QUARTER_RATE:
467193323Sed		case HAL_MODE_11NG_HT20:
468193323Sed		case HAL_MODE_11NG_HT40PLUS:
469193323Sed		case HAL_MODE_11NG_HT40MINUS:
470193323Sed			rdflags = rd2GHz->flags;
471193323Sed			dfsMask = rd2GHz->dfsMask;
472207618Srdivacky			pscan = rd2GHz->pscan;
473207618Srdivacky			if (cm->mode == HAL_MODE_11G_HALF_RATE)
474207618Srdivacky				channelBM = rd2GHz->chan11g_half;
475207618Srdivacky			else if (cm->mode == HAL_MODE_11G_QUARTER_RATE)
476207618Srdivacky				channelBM = rd2GHz->chan11g_quarter;
477207618Srdivacky			else if (cm->mode == HAL_MODE_11B)
478207618Srdivacky				channelBM = rd2GHz->chan11b;
479207618Srdivacky			else
480207618Srdivacky				channelBM = rd2GHz->chan11g;
481207618Srdivacky			if (cm->mode == HAL_MODE_11B)
482193323Sed				freqs = &regDmn2GhzFreq[0];
483198090Srdivacky			else
484198090Srdivacky				freqs = &regDmn2Ghz11gFreq[0];
485198090Srdivacky			break;
486198090Srdivacky		default:
487198090Srdivacky			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
488193323Sed			    "%s: Unkonwn HAL mode 0x%x\n", __func__, cm->mode);
489198090Srdivacky			continue;
490198090Srdivacky		}
491198090Srdivacky		if (isChanBitMaskZero(channelBM))
492203954Srdivacky			continue;
493203954Srdivacky		/*
494226633Sdim		 * Setup special handling for HT40 channels; e.g.
495203954Srdivacky		 * 5G HT40 channels require 40Mhz channel separation.
496203954Srdivacky		 */
497198090Srdivacky		hi_adj = (cm->mode == HAL_MODE_11NA_HT40PLUS ||
498203954Srdivacky		    cm->mode == HAL_MODE_11NG_HT40PLUS) ? -20 : 0;
499203954Srdivacky		low_adj = (cm->mode == HAL_MODE_11NA_HT40MINUS ||
500203954Srdivacky		    cm->mode == HAL_MODE_11NG_HT40MINUS) ? 20 : 0;
501203954Srdivacky		channelSep = (cm->mode == HAL_MODE_11NA_HT40PLUS ||
502203954Srdivacky		    cm->mode == HAL_MODE_11NA_HT40MINUS) ? 40 : 0;
503203954Srdivacky
504203954Srdivacky		for (b = 0; b < 64*BMLEN; b++) {
505203954Srdivacky			if (!IS_BIT_SET(b, channelBM))
506203954Srdivacky				continue;
507193323Sed			fband = &freqs[b];
508198090Srdivacky			lastc = 0;
509203954Srdivacky
510193323Sed			for (c = fband->lowChannel + low_adj;
511203954Srdivacky			     c <= fband->highChannel + hi_adj;
512203954Srdivacky			     c += fband->channelSep) {
513203954Srdivacky				if (!(c_lo <= c && c <= c_hi)) {
514203954Srdivacky					HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
515203954Srdivacky					    "%s: c %u out of range [%u..%u]\n",
516203954Srdivacky					    __func__, c, c_lo, c_hi);
517203954Srdivacky					continue;
518203954Srdivacky				}
519193323Sed				if (next >= maxchans){
520203954Srdivacky					HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
521203954Srdivacky					    "%s: too many channels for channel table\n",
522203954Srdivacky					    __func__);
523203954Srdivacky					goto done;
524203954Srdivacky				}
525203954Srdivacky				if ((fband->usePassScan & IS_ECM_CHAN) &&
526203954Srdivacky				    !enableExtendedChannels) {
527203954Srdivacky					HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
528203954Srdivacky					    "skip ecm channel\n");
529203954Srdivacky					continue;
530203954Srdivacky				}
531203954Srdivacky#if 0
532203954Srdivacky				if ((fband->useDfs & dfsMask) &&
533203954Srdivacky				    (cm->flags & IEEE80211_CHAN_HT40)) {
534203954Srdivacky					/* NB: DFS and HT40 don't mix */
535203954Srdivacky					HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
536203954Srdivacky					    "skip HT40 chan, DFS required\n");
537193323Sed					continue;
538203954Srdivacky				}
539203954Srdivacky#endif
540203954Srdivacky				/*
541203954Srdivacky				 * Make sure that channel separation
542203954Srdivacky				 * meets the requirement.
543203954Srdivacky				 */
544203954Srdivacky				if (lastc && channelSep &&
545203954Srdivacky				    (c-lastc) < channelSep)
546203954Srdivacky					continue;
547203954Srdivacky				lastc = c;
548203954Srdivacky
549207618Srdivacky				OS_MEMZERO(ic, sizeof(*ic));
550203954Srdivacky				ic->ic_freq = c;
551203954Srdivacky				ic->ic_flags = cm->flags;
552203954Srdivacky				ic->ic_maxregpower = fband->powerDfs;
553203954Srdivacky				ath_hal_getpowerlimits(ah, ic);
554203954Srdivacky				ic->ic_maxantgain = fband->antennaMax;
555203954Srdivacky				if (fband->usePassScan & pscan)
556203954Srdivacky					ic->ic_flags |= IEEE80211_CHAN_PASSIVE;
557203954Srdivacky				if (fband->useDfs & dfsMask)
558203954Srdivacky					ic->ic_flags |= IEEE80211_CHAN_DFS;
559203954Srdivacky				if (IEEE80211_IS_CHAN_5GHZ(ic) &&
560193323Sed				    (rdflags & DISALLOW_ADHOC_11A))
561203954Srdivacky					ic->ic_flags |= IEEE80211_CHAN_NOADHOC;
562226633Sdim				if (IEEE80211_IS_CHAN_TURBO(ic) &&
563203954Srdivacky				    (rdflags & DISALLOW_ADHOC_11A_TURB))
564193323Sed					ic->ic_flags |= IEEE80211_CHAN_NOADHOC;
565203954Srdivacky				if (rdflags & NO_HOSTAP)
566203954Srdivacky					ic->ic_flags |= IEEE80211_CHAN_NOHOSTAP;
567203954Srdivacky				if (rdflags & LIMIT_FRAME_4MS)
568203954Srdivacky					ic->ic_flags |= IEEE80211_CHAN_4MSXMIT;
569203954Srdivacky				if (rdflags & NEED_NFC)
570226633Sdim					ic->ic_flags |= CHANNEL_NFCREQUIRED;
571203954Srdivacky
572203954Srdivacky				ic++, next++;
573203954Srdivacky			}
574203954Srdivacky		}
575226633Sdim	}
576203954Srdivackydone:
577203954Srdivacky	*nchans = next;
578226633Sdim	/* NB: pcountry set above by getregstate */
579203954Srdivacky	if (prd2GHz != AH_NULL)
580203954Srdivacky		*prd2GHz = rd2GHz;
581203954Srdivacky	if (prd5GHz != AH_NULL)
582193323Sed		*prd5GHz = rd5GHz;
583203954Srdivacky	return HAL_OK;
584203954Srdivacky#undef HAL_MODE_11A_ALL
585203954Srdivacky#undef CHANNEL_HALF_BW
586203954Srdivacky#undef CHANNEL_QUARTER_BW
587203954Srdivacky}
588203954Srdivacky
589203954Srdivacky/*
590193323Sed * Retrieve a channel list without affecting runtime state.
591193323Sed */
592228379SdimHAL_STATUS
593228379Sdimath_hal_getchannels(struct ath_hal *ah,
594228379Sdim    struct ieee80211_channel chans[], u_int maxchans, int *nchans,
595228379Sdim    u_int modeSelect, HAL_CTRY_CODE cc, HAL_REG_DOMAIN regDmn,
596228379Sdim    HAL_BOOL enableExtendedChannels)
597228379Sdim{
598228379Sdim	return getchannels(ah, chans, maxchans, nchans, modeSelect,
599193323Sed	    cc, regDmn, enableExtendedChannels, AH_NULL, AH_NULL, AH_NULL);
600218893Sdim}
601224145Sdim
602202375Srdivacky/*
603218893Sdim * Handle frequency mapping from 900Mhz range to 2.4GHz range
604218893Sdim * for GSM radios.  This is done when we need the h/w frequency
605218893Sdim * and the channel is marked IEEE80211_CHAN_GSM.
606228379Sdim */
607228379Sdimstatic int
608228379Sdimath_hal_mapgsm(int sku, int freq)
609228379Sdim{
610228379Sdim	if (sku == SKU_XR9)
611228379Sdim		return 1520 + freq;
612228379Sdim	if (sku == SKU_GZ901)
613228379Sdim		return 1544 + freq;
614228379Sdim	if (sku == SKU_SR9)
615228379Sdim		return 3344 - freq;
616228379Sdim	HALDEBUG_G(AH_NULL, HAL_DEBUG_ANY,
617228379Sdim	    "%s: cannot map freq %u unknown gsm sku %u\n",
618228379Sdim	    __func__, freq, sku);
619228379Sdim	return freq;
620228379Sdim}
621228379Sdim
622228379Sdim/*
623218893Sdim * Setup the internal/private channel state given a table of
624218893Sdim * net80211 channels.  We collapse entries for the same frequency
625193323Sed * and record the frequency for doing noise floor processing
626198090Srdivacky * where we don't have net80211 channel context.
627198090Srdivacky */
628193323Sedstatic HAL_BOOL
629193323SedassignPrivateChannels(struct ath_hal *ah,
630193323Sed	struct ieee80211_channel chans[], int nchans, int sku)
631198090Srdivacky{
632198090Srdivacky	HAL_CHANNEL_INTERNAL *ic;
633193323Sed	int i, j, next, freq;
634193323Sed
635193323Sed	next = 0;
636193323Sed	for (i = 0; i < nchans; i++) {
637193323Sed		struct ieee80211_channel *c = &chans[i];
638193323Sed		for (j = i-1; j >= 0; j--)
639193323Sed			if (chans[j].ic_freq == c->ic_freq) {
640193323Sed				c->ic_devdata = chans[j].ic_devdata;
641193323Sed				break;
642198090Srdivacky			}
643193323Sed		if (j < 0) {
644193323Sed			/* new entry, assign a private channel entry */
645193323Sed			if (next >= N(AH_PRIVATE(ah)->ah_channels)) {
646193323Sed				HALDEBUG(ah, HAL_DEBUG_ANY,
647198090Srdivacky				    "%s: too many channels, max %zu\n",
648198090Srdivacky				    __func__, N(AH_PRIVATE(ah)->ah_channels));
649193323Sed				return AH_FALSE;
650193323Sed			}
651198090Srdivacky			/*
652198090Srdivacky			 * Handle frequency mapping for 900MHz devices.
653193323Sed			 * The hardware uses 2.4GHz frequencies that are
654193323Sed			 * down-converted.  The 802.11 layer uses the
655198090Srdivacky			 * true frequencies.
656198090Srdivacky			 */
657193323Sed			freq = IEEE80211_IS_CHAN_GSM(c) ?
658193323Sed			    ath_hal_mapgsm(sku, c->ic_freq) : c->ic_freq;
659193323Sed
660193323Sed			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
661193323Sed			    "%s: private[%3u] %u/0x%x -> channel %u\n",
662193323Sed			    __func__, next, c->ic_freq, c->ic_flags, freq);
663221345Sdim
664221345Sdim			ic = &AH_PRIVATE(ah)->ah_channels[next];
665193323Sed			/*
666193323Sed			 * NB: This clears privFlags which means ancillary
667198090Srdivacky			 *     code like ANI and IQ calibration will be
668198090Srdivacky			 *     restarted and re-setup any per-channel state.
669198090Srdivacky			 */
670198090Srdivacky			OS_MEMZERO(ic, sizeof(*ic));
671193323Sed			ic->channel = freq;
672193323Sed			c->ic_devdata = next;
673193323Sed			next++;
674193323Sed		}
675193323Sed	}
676193323Sed	AH_PRIVATE(ah)->ah_nchan = next;
677193323Sed	HALDEBUG(ah, HAL_DEBUG_ANY, "%s: %u public, %u private channels\n",
678193323Sed	    __func__, nchans, next);
679193323Sed	return AH_TRUE;
680193323Sed}
681193323Sed
682193323Sed/*
683193323Sed * Setup the channel list based on the information in the EEPROM.
684193323Sed */
685193323SedHAL_STATUS
686198090Srdivackyath_hal_init_channels(struct ath_hal *ah,
687193323Sed    struct ieee80211_channel chans[], u_int maxchans, int *nchans,
688193323Sed    u_int modeSelect, HAL_CTRY_CODE cc, HAL_REG_DOMAIN regDmn,
689193323Sed    HAL_BOOL enableExtendedChannels)
690198090Srdivacky{
691193323Sed	COUNTRY_CODE_TO_ENUM_RD *country;
692212904Sdim	REG_DOMAIN *rd5GHz, *rd2GHz;
693193323Sed	HAL_STATUS status;
694193323Sed
695193323Sed	status = getchannels(ah, chans, maxchans, nchans, modeSelect,
696193323Sed	    cc, regDmn, enableExtendedChannels, &country, &rd2GHz, &rd5GHz);
697193323Sed	if (status == HAL_OK &&
698193323Sed	    assignPrivateChannels(ah, chans, *nchans, AH_PRIVATE(ah)->ah_currentRD)) {
699193323Sed		AH_PRIVATE(ah)->ah_rd2GHz = rd2GHz;
700193323Sed		AH_PRIVATE(ah)->ah_rd5GHz = rd5GHz;
701198090Srdivacky
702198090Srdivacky		ah->ah_countryCode = country->countryCode;
703193323Sed		HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: cc %u\n",
704193323Sed		    __func__, ah->ah_countryCode);
705193323Sed
706193323Sed		/* Update current DFS domain */
707193323Sed		ath_hal_update_dfsdomain(ah);
708221345Sdim	} else
709221345Sdim		status = HAL_EINVAL;
710221345Sdim
711221345Sdim	return status;
712221345Sdim}
713221345Sdim
714193323Sed/*
715193323Sed * Set the channel list.
716193323Sed */
717193323SedHAL_STATUS
718193323Sedath_hal_set_channels(struct ath_hal *ah,
719224145Sdim    struct ieee80211_channel chans[], int nchans,
720193323Sed    HAL_CTRY_CODE cc, HAL_REG_DOMAIN rd)
721224145Sdim{
722193323Sed	COUNTRY_CODE_TO_ENUM_RD *country;
723193323Sed	REG_DOMAIN *rd5GHz, *rd2GHz;
724193323Sed	HAL_STATUS status;
725203954Srdivacky
726193323Sed	switch (rd) {
727198090Srdivacky	case SKU_SR9:
728198090Srdivacky	case SKU_XR9:
729193323Sed	case SKU_GZ901:
730193323Sed		/*
731193323Sed		 * Map 900MHz sku's.  The frequencies will be mapped
732193323Sed		 * according to the sku to compensate for the down-converter.
733193323Sed		 * We use the FCC for these sku's as the mapped channel
734212904Sdim		 * list is known compatible (will need to change if/when
735198090Srdivacky		 * vendors do different mapping in different locales).
736193323Sed		 */
737212904Sdim		status = getregstate(ah, CTRY_DEFAULT, SKU_FCC,
738212904Sdim		    &country, &rd2GHz, &rd5GHz);
739212904Sdim		break;
740212904Sdim	default:
741212904Sdim		status = getregstate(ah, cc, rd,
742212904Sdim		    &country, &rd2GHz, &rd5GHz);
743203954Srdivacky		rd = AH_PRIVATE(ah)->ah_currentRD;
744193323Sed		break;
745193323Sed	}
746198090Srdivacky	if (status == HAL_OK && assignPrivateChannels(ah, chans, nchans, rd)) {
747207618Srdivacky		AH_PRIVATE(ah)->ah_rd2GHz = rd2GHz;
748193323Sed		AH_PRIVATE(ah)->ah_rd5GHz = rd5GHz;
749212904Sdim
750205218Srdivacky		ah->ah_countryCode = country->countryCode;
751203954Srdivacky		HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: cc %u\n",
752205218Srdivacky		    __func__, ah->ah_countryCode);
753193323Sed	} else
754212904Sdim		status = HAL_EINVAL;
755203954Srdivacky
756203954Srdivacky	if (status == HAL_OK) {
757193323Sed		/* Update current DFS domain */
758193323Sed		(void) ath_hal_update_dfsdomain(ah);
759193323Sed	}
760193323Sed	return status;
761203954Srdivacky}
762193323Sed
763193323Sed#ifdef AH_DEBUG
764193323Sed/*
765193323Sed * Return the internal channel corresponding to a public channel.
766193323Sed * NB: normally this routine is inline'd (see ah_internal.h)
767193323Sed */
768193323SedHAL_CHANNEL_INTERNAL *
769193323Sedath_hal_checkchannel(struct ath_hal *ah, const struct ieee80211_channel *c)
770193323Sed{
771198090Srdivacky	HAL_CHANNEL_INTERNAL *cc = &AH_PRIVATE(ah)->ah_channels[c->ic_devdata];
772193323Sed
773193323Sed	if (c->ic_devdata < AH_PRIVATE(ah)->ah_nchan &&
774198090Srdivacky	    (c->ic_freq == cc->channel || IEEE80211_IS_CHAN_GSM(c)))
775198090Srdivacky		return cc;
776198090Srdivacky	if (c->ic_devdata >= AH_PRIVATE(ah)->ah_nchan) {
777198090Srdivacky		HALDEBUG(ah, HAL_DEBUG_ANY,
778193323Sed		    "%s: bad mapping, devdata %u nchans %u\n",
779202375Srdivacky		   __func__, c->ic_devdata, AH_PRIVATE(ah)->ah_nchan);
780202375Srdivacky		HALASSERT(c->ic_devdata < AH_PRIVATE(ah)->ah_nchan);
781202375Srdivacky	} else {
782202375Srdivacky		HALDEBUG(ah, HAL_DEBUG_ANY,
783202375Srdivacky		    "%s: no match for %u/0x%x devdata %u channel %u\n",
784193323Sed		   __func__, c->ic_freq, c->ic_flags, c->ic_devdata,
785198090Srdivacky		   cc->channel);
786198090Srdivacky		HALASSERT(c->ic_freq == cc->channel || IEEE80211_IS_CHAN_GSM(c));
787198090Srdivacky	}
788193323Sed	return AH_NULL;
789198090Srdivacky}
790198090Srdivacky#endif /* AH_DEBUG */
791198090Srdivacky
792199481Srdivacky#define isWwrSKU(_ah) \
793198090Srdivacky	((getEepromRD((_ah)) & WORLD_SKU_MASK) == WORLD_SKU_PREFIX || \
794198090Srdivacky	  getEepromRD(_ah) == WORLD)
795198090Srdivacky
796198090Srdivacky/*
797198090Srdivacky * Return the test group for the specific channel based on
798198090Srdivacky * the current regulatory setup.
799198090Srdivacky */
800203954Srdivackyu_int
801203954Srdivackyath_hal_getctl(struct ath_hal *ah, const struct ieee80211_channel *c)
802203954Srdivacky{
803203954Srdivacky	u_int ctl;
804203954Srdivacky
805203954Srdivacky	if (AH_PRIVATE(ah)->ah_rd2GHz == AH_PRIVATE(ah)->ah_rd5GHz ||
806198090Srdivacky	    (ah->ah_countryCode == CTRY_DEFAULT && isWwrSKU(ah)))
807198090Srdivacky		ctl = SD_NO_CTL;
808212904Sdim	else if (IEEE80211_IS_CHAN_2GHZ(c))
809212904Sdim		ctl = AH_PRIVATE(ah)->ah_rd2GHz->conformanceTestLimit;
810198090Srdivacky	else
811198090Srdivacky		ctl = AH_PRIVATE(ah)->ah_rd5GHz->conformanceTestLimit;
812198090Srdivacky	if (IEEE80211_IS_CHAN_B(c))
813203954Srdivacky		return ctl | CTL_11B;
814198090Srdivacky	if (IEEE80211_IS_CHAN_G(c))
815203954Srdivacky		return ctl | CTL_11G;
816193323Sed	if (IEEE80211_IS_CHAN_108G(c))
817198090Srdivacky		return ctl | CTL_108G;
818198090Srdivacky	if (IEEE80211_IS_CHAN_TURBO(c))
819198090Srdivacky		return ctl | CTL_TURBO;
820226633Sdim	if (IEEE80211_IS_CHAN_A(c))
821226633Sdim		return ctl | CTL_11A;
822193323Sed	return ctl;
823198090Srdivacky}
824198090Srdivacky
825198090Srdivacky
826198090Srdivacky/*
827203954Srdivacky * Update the current dfsDomain setting based on the given
828198090Srdivacky * country code.
829198090Srdivacky *
830198090Srdivacky * Since FreeBSD/net80211 allows the channel set to change
831193323Sed * after the card has been setup (via ath_hal_init_channels())
832198090Srdivacky * this function method is needed to update ah_dfsDomain.
833198090Srdivacky */
834198090Srdivackyvoid
835198090Srdivackyath_hal_update_dfsdomain(struct ath_hal *ah)
836198090Srdivacky{
837198090Srdivacky	const REG_DOMAIN *rd5GHz = AH_PRIVATE(ah)->ah_rd5GHz;
838198090Srdivacky	HAL_CTRY_CODE cc = ah->ah_countryCode;
839198090Srdivacky	HAL_DFS_DOMAIN dfsDomain = HAL_DFS_UNINIT_DOMAIN;
840198090Srdivacky	HAL_REG_DOMAIN regDmn = AH_PRIVATE(ah)->ah_currentRD;
841198090Srdivacky
842198090Srdivacky	HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s CC: %d, RegDmn: %d\n",__func__,
843199481Srdivacky	    cc, regDmn);
844198090Srdivacky	if (rd5GHz->dfsMask & DFS_FCC3)
845198090Srdivacky		dfsDomain = HAL_DFS_FCC_DOMAIN;
846198090Srdivacky	if (rd5GHz->dfsMask & DFS_ETSI)
847198090Srdivacky		dfsDomain = HAL_DFS_ETSI_DOMAIN;
848198090Srdivacky	if (rd5GHz->dfsMask & DFS_MKK4)
849198090Srdivacky		dfsDomain = HAL_DFS_MKK4_DOMAIN;
850193323Sed	AH_PRIVATE(ah)->ah_dfsDomain = dfsDomain;
851198090Srdivacky	HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s ah_dfsDomain: %d\n",
852193323Sed	    __func__, AH_PRIVATE(ah)->ah_dfsDomain);
853193323Sed}
854193323Sed
855193323Sed
856226633Sdim/*
857193323Sed * Return the max allowed antenna gain and apply any regulatory
858193323Sed * domain specific changes.
859198090Srdivacky *
860203954Srdivacky * NOTE: a negative reduction is possible in RD's that only
861193323Sed * measure radiated power (e.g., ETSI) which would increase
862193323Sed * that actual conducted output power (though never beyond
863193323Sed * the calibrated target power).
864193323Sed */
865193323Sedu_int
866226633Sdimath_hal_getantennareduction(struct ath_hal *ah,
867193323Sed    const struct ieee80211_channel *chan, u_int twiceGain)
868210299Sed{
869193323Sed	int8_t antennaMax = twiceGain - chan->ic_maxantgain*2;
870198090Srdivacky	return (antennaMax < 0) ? 0 : antennaMax;
871203954Srdivacky}
872193323Sed