1185377Ssam/*
2187831Ssam * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting
3185377Ssam * Copyright (c) 2005-2006 Atheros Communications, Inc.
4185377Ssam * All rights reserved.
5185377Ssam *
6185377Ssam * Permission to use, copy, modify, and/or distribute this software for any
7185377Ssam * purpose with or without fee is hereby granted, provided that the above
8185377Ssam * copyright notice and this permission notice appear in all copies.
9185377Ssam *
10185377Ssam * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11185377Ssam * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12185377Ssam * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13185377Ssam * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14185377Ssam * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15185377Ssam * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16185377Ssam * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17185377Ssam *
18187345Ssam * $FreeBSD$
19185377Ssam */
20185377Ssam#include "opt_ah.h"
21185377Ssam
22185377Ssam#include "ah.h"
23187831Ssam
24187831Ssam#include <net80211/_ieee80211.h>
25187831Ssam#include <net80211/ieee80211_regdomain.h>
26187831Ssam
27185377Ssam#include "ah_internal.h"
28185377Ssam#include "ah_eeprom.h"
29185377Ssam#include "ah_devid.h"
30185377Ssam
31219394Sadrian#include "ah_regdomain.h"
32219394Sadrian
33185377Ssam/*
34185377Ssam * XXX this code needs a audit+review
35185377Ssam */
36185377Ssam
37185377Ssam/* used throughout this file... */
38185377Ssam#define	N(a)	(sizeof (a) / sizeof (a[0]))
39185377Ssam
40185377Ssam#define HAL_MODE_11A_TURBO	HAL_MODE_108A
41185377Ssam#define HAL_MODE_11G_TURBO	HAL_MODE_108G
42185377Ssam
43185377Ssam/*
44185380Ssam * Mask to check whether a domain is a multidomain or a single domain
45185380Ssam */
46185377Ssam#define MULTI_DOMAIN_MASK 0xFF00
47185377Ssam
48185380Ssam/*
49185380Ssam * Enumerated Regulatory Domain Information 8 bit values indicate that
50185377Ssam * the regdomain is really a pair of unitary regdomains.  12 bit values
51185377Ssam * are the real unitary regdomains and are the only ones which have the
52185377Ssam * frequency bitmasks and flags set.
53185377Ssam */
54219442Sadrian#include "ah_regdomain/ah_rd_regenum.h"
55185377Ssam
56185377Ssam#define	WORLD_SKU_MASK		0x00F0
57185377Ssam#define	WORLD_SKU_PREFIX	0x0060
58185377Ssam
59185377Ssam/*
60185377Ssam * THE following table is the mapping of regdomain pairs specified by
61185377Ssam * an 8 bit regdomain value to the individual unitary reg domains
62185377Ssam */
63219442Sadrian#include "ah_regdomain/ah_rd_regmap.h"
64185377Ssam
65185377Ssam/*
66185380Ssam * The following tables are the master list for all different freqeuncy
67185377Ssam * bands with the complete matrix of all possible flags and settings
68185377Ssam * for each band if it is used in ANY reg domain.
69185377Ssam */
70185377Ssam
71185377Ssam#define	COUNTRY_ERD_FLAG        0x8000
72185377Ssam#define WORLDWIDE_ROAMING_FLAG  0x4000
73185377Ssam
74185380Ssam/*
75219442Sadrian * This table maps country ISO codes from net80211 into regulatory
76219442Sadrian * domains which the ath regulatory domain code understands.
77185380Ssam */
78219442Sadrian#include "ah_regdomain/ah_rd_ctry.h"
79185380Ssam
80185377Ssam/*
81219442Sadrian * The frequency band collections are a set of frequency ranges
82219442Sadrian * with shared properties - max tx power, max antenna gain, channel width,
83219442Sadrian * channel spacing, DFS requirements and passive scanning requirements.
84219442Sadrian *
85219442Sadrian * These are represented as entries in a frequency band bitmask.
86219442Sadrian * Each regulatory domain entry in ah_regdomain_domains.h uses one
87219442Sadrian * or more frequency band entries for each of the channel modes
88219442Sadrian * supported (11bg, 11a, half, quarter, turbo, etc.)
89219442Sadrian *
90185377Ssam */
91219442Sadrian#include "ah_regdomain/ah_rd_freqbands.h"
92185377Ssam
93185377Ssam/*
94219442Sadrian * This is the main regulatory database. It defines the supported
95219442Sadrian * set of features and requirements for each of the defined regulatory
96219442Sadrian * zones. It uses combinations of frequency ranges - represented in
97219442Sadrian * a bitmask - to determine the requirements and limitations needed.
98185377Ssam */
99219442Sadrian#include "ah_regdomain/ah_rd_domains.h"
100185377Ssam
101185377Ssamstatic const struct cmode modes[] = {
102187831Ssam	{ HAL_MODE_TURBO,	IEEE80211_CHAN_ST },
103187831Ssam	{ HAL_MODE_11A,		IEEE80211_CHAN_A },
104187831Ssam	{ HAL_MODE_11B,		IEEE80211_CHAN_B },
105187831Ssam	{ HAL_MODE_11G,		IEEE80211_CHAN_G },
106187831Ssam	{ HAL_MODE_11G_TURBO,	IEEE80211_CHAN_108G },
107187831Ssam	{ HAL_MODE_11A_TURBO,	IEEE80211_CHAN_108A },
108187831Ssam	{ HAL_MODE_11A_QUARTER_RATE,
109187831Ssam	  IEEE80211_CHAN_A | IEEE80211_CHAN_QUARTER },
110187831Ssam	{ HAL_MODE_11A_HALF_RATE,
111187831Ssam	  IEEE80211_CHAN_A | IEEE80211_CHAN_HALF },
112187831Ssam	{ HAL_MODE_11G_QUARTER_RATE,
113187831Ssam	  IEEE80211_CHAN_G | IEEE80211_CHAN_QUARTER },
114187831Ssam	{ HAL_MODE_11G_HALF_RATE,
115187831Ssam	  IEEE80211_CHAN_G | IEEE80211_CHAN_HALF },
116188264Ssam	{ HAL_MODE_11NG_HT20,	IEEE80211_CHAN_G | IEEE80211_CHAN_HT20 },
117187831Ssam	{ HAL_MODE_11NG_HT40PLUS,
118188264Ssam	  IEEE80211_CHAN_G | IEEE80211_CHAN_HT40U },
119187831Ssam	{ HAL_MODE_11NG_HT40MINUS,
120188264Ssam	  IEEE80211_CHAN_G | IEEE80211_CHAN_HT40D },
121188264Ssam	{ HAL_MODE_11NA_HT20,	IEEE80211_CHAN_A | IEEE80211_CHAN_HT20 },
122187831Ssam	{ HAL_MODE_11NA_HT40PLUS,
123188264Ssam	  IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U },
124187831Ssam	{ HAL_MODE_11NA_HT40MINUS,
125188264Ssam	  IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D },
126185377Ssam};
127185377Ssam
128224718Sadrianstatic void ath_hal_update_dfsdomain(struct ath_hal *ah);
129224718Sadrian
130187831Ssamstatic OS_INLINE uint16_t
131185377SsamgetEepromRD(struct ath_hal *ah)
132185377Ssam{
133185377Ssam	return AH_PRIVATE(ah)->ah_currentRD &~ WORLDWIDE_ROAMING_FLAG;
134185377Ssam}
135185377Ssam
136185377Ssam/*
137185377Ssam * Test to see if the bitmask array is all zeros
138185377Ssam */
139185377Ssamstatic HAL_BOOL
140185380SsamisChanBitMaskZero(const uint64_t *bitmask)
141185377Ssam{
142185377Ssam#if BMLEN > 2
143185377Ssam#error	"add more cases"
144185377Ssam#endif
145185377Ssam#if BMLEN > 1
146185377Ssam	if (bitmask[1] != 0)
147185377Ssam		return AH_FALSE;
148185377Ssam#endif
149185377Ssam	return (bitmask[0] == 0);
150185377Ssam}
151185377Ssam
152185377Ssam/*
153185377Ssam * Return whether or not the regulatory domain/country in EEPROM
154185377Ssam * is acceptable.
155185377Ssam */
156185377Ssamstatic HAL_BOOL
157185377SsamisEepromValid(struct ath_hal *ah)
158185377Ssam{
159185377Ssam	uint16_t rd = getEepromRD(ah);
160185377Ssam	int i;
161185377Ssam
162185377Ssam	if (rd & COUNTRY_ERD_FLAG) {
163185377Ssam		uint16_t cc = rd &~ COUNTRY_ERD_FLAG;
164185377Ssam		for (i = 0; i < N(allCountries); i++)
165185377Ssam			if (allCountries[i].countryCode == cc)
166185377Ssam				return AH_TRUE;
167185377Ssam	} else {
168185377Ssam		for (i = 0; i < N(regDomainPairs); i++)
169185377Ssam			if (regDomainPairs[i].regDmnEnum == rd)
170185377Ssam				return AH_TRUE;
171185377Ssam	}
172225883Sadrian	HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
173185377Ssam	    "%s: invalid regulatory domain/country code 0x%x\n", __func__, rd);
174185377Ssam	return AH_FALSE;
175185377Ssam}
176185377Ssam
177185377Ssam/*
178187831Ssam * Find the pointer to the country element in the country table
179187831Ssam * corresponding to the country code
180185377Ssam */
181187831Ssamstatic COUNTRY_CODE_TO_ENUM_RD*
182187831SsamfindCountry(HAL_CTRY_CODE countryCode)
183185377Ssam{
184187831Ssam	int i;
185185377Ssam
186187831Ssam	for (i = 0; i < N(allCountries); i++) {
187187831Ssam		if (allCountries[i].countryCode == countryCode)
188187831Ssam			return &allCountries[i];
189185377Ssam	}
190187831Ssam	return AH_NULL;
191185377Ssam}
192185377Ssam
193187831Ssamstatic REG_DOMAIN *
194187831SsamfindRegDmn(int regDmn)
195185377Ssam{
196187831Ssam	int i;
197185377Ssam
198187831Ssam	for (i = 0; i < N(regDomains); i++) {
199187831Ssam		if (regDomains[i].regDmnEnum == regDmn)
200187831Ssam			return &regDomains[i];
201185380Ssam	}
202187831Ssam	return AH_NULL;
203185377Ssam}
204185377Ssam
205187831Ssamstatic REG_DMN_PAIR_MAPPING *
206187831SsamfindRegDmnPair(int regDmnPair)
207185377Ssam{
208185377Ssam	int i;
209185377Ssam
210187831Ssam	if (regDmnPair != NO_ENUMRD) {
211187831Ssam		for (i = 0; i < N(regDomainPairs); i++) {
212187831Ssam			if (regDomainPairs[i].regDmnEnum == regDmnPair)
213187831Ssam				return &regDomainPairs[i];
214187831Ssam		}
215185377Ssam	}
216187831Ssam	return AH_NULL;
217185377Ssam}
218185377Ssam
219185377Ssam/*
220185377Ssam * Calculate a default country based on the EEPROM setting.
221185377Ssam */
222185377Ssamstatic HAL_CTRY_CODE
223185377SsamgetDefaultCountry(struct ath_hal *ah)
224185377Ssam{
225187831Ssam	REG_DMN_PAIR_MAPPING *regpair;
226185377Ssam	uint16_t rd;
227185377Ssam
228185377Ssam	rd = getEepromRD(ah);
229185377Ssam	if (rd & COUNTRY_ERD_FLAG) {
230187831Ssam		COUNTRY_CODE_TO_ENUM_RD *country;
231185377Ssam		uint16_t cc = rd & ~COUNTRY_ERD_FLAG;
232185377Ssam		country = findCountry(cc);
233185377Ssam		if (country != AH_NULL)
234185377Ssam			return cc;
235185377Ssam	}
236185377Ssam	/*
237185377Ssam	 * Check reg domains that have only one country
238185377Ssam	 */
239187831Ssam	regpair = findRegDmnPair(rd);
240187831Ssam	return (regpair != AH_NULL) ? regpair->singleCC : CTRY_DEFAULT;
241185377Ssam}
242185377Ssam
243185377Ssamstatic HAL_BOOL
244187831SsamIS_BIT_SET(int bit, const uint64_t bitmask[])
245185377Ssam{
246187831Ssam	int byteOffset, bitnum;
247187831Ssam	uint64_t val;
248185377Ssam
249187831Ssam	byteOffset = bit/64;
250187831Ssam	bitnum = bit - byteOffset*64;
251187831Ssam	val = ((uint64_t) 1) << bitnum;
252187831Ssam	return (bitmask[byteOffset] & val) != 0;
253185377Ssam}
254185377Ssam
255187831Ssamstatic HAL_STATUS
256187831Ssamgetregstate(struct ath_hal *ah, HAL_CTRY_CODE cc, HAL_REG_DOMAIN regDmn,
257187831Ssam    COUNTRY_CODE_TO_ENUM_RD **pcountry,
258187831Ssam    REG_DOMAIN **prd2GHz, REG_DOMAIN **prd5GHz)
259185377Ssam{
260187831Ssam	COUNTRY_CODE_TO_ENUM_RD *country;
261187831Ssam	REG_DOMAIN *rd5GHz, *rd2GHz;
262185377Ssam
263187831Ssam	if (cc == CTRY_DEFAULT && regDmn == SKU_NONE) {
264187831Ssam		/*
265187831Ssam		 * Validate the EEPROM setting and setup defaults
266187831Ssam		 */
267187831Ssam		if (!isEepromValid(ah)) {
268187831Ssam			/*
269187831Ssam			 * Don't return any channels if the EEPROM has an
270187831Ssam			 * invalid regulatory domain/country code setting.
271187831Ssam			 */
272187831Ssam			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
273187831Ssam			    "%s: invalid EEPROM contents\n",__func__);
274187831Ssam			return HAL_EEBADREG;
275187831Ssam		}
276185377Ssam
277187831Ssam		cc = getDefaultCountry(ah);
278187831Ssam		country = findCountry(cc);
279187831Ssam		if (country == AH_NULL) {
280187831Ssam			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
281187831Ssam			    "NULL Country!, cc %d\n", cc);
282187831Ssam			return HAL_EEBADCC;
283187831Ssam		}
284187831Ssam		regDmn = country->regDmnEnum;
285187831Ssam		HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: EEPROM cc %u rd 0x%x\n",
286187831Ssam		    __func__, cc, regDmn);
287185377Ssam
288187831Ssam		if (country->countryCode == CTRY_DEFAULT) {
289187831Ssam			/*
290187831Ssam			 * Check EEPROM; SKU may be for a country, single
291187831Ssam			 * domain, or multiple domains (WWR).
292187831Ssam			 */
293187831Ssam			uint16_t rdnum = getEepromRD(ah);
294187831Ssam			if ((rdnum & COUNTRY_ERD_FLAG) == 0 &&
295187831Ssam			    (findRegDmn(rdnum) != AH_NULL ||
296187831Ssam			     findRegDmnPair(rdnum) != AH_NULL)) {
297185377Ssam				regDmn = rdnum;
298187831Ssam				HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
299187831Ssam				    "%s: EEPROM rd 0x%x\n", __func__, rdnum);
300185377Ssam			}
301185377Ssam		}
302187831Ssam	} else {
303187831Ssam		country = findCountry(cc);
304187831Ssam		if (country == AH_NULL) {
305185377Ssam			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
306187831Ssam			    "unknown country, cc %d\n", cc);
307187831Ssam			return HAL_EINVAL;
308185377Ssam		}
309187831Ssam		if (regDmn == SKU_NONE)
310187831Ssam			regDmn = country->regDmnEnum;
311187831Ssam		HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: cc %u rd 0x%x\n",
312187831Ssam		    __func__, cc, regDmn);
313185377Ssam	}
314185377Ssam
315185377Ssam	/*
316187831Ssam	 * Setup per-band state.
317185377Ssam	 */
318187831Ssam	if ((regDmn & MULTI_DOMAIN_MASK) == 0) {
319187831Ssam		REG_DMN_PAIR_MAPPING *regpair = findRegDmnPair(regDmn);
320187831Ssam		if (regpair == AH_NULL) {
321187831Ssam			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
322187831Ssam			    "%s: no reg domain pair %u for country %u\n",
323187831Ssam			    __func__, regDmn, country->countryCode);
324187831Ssam			return HAL_EINVAL;
325187831Ssam		}
326187831Ssam		rd5GHz = findRegDmn(regpair->regDmn5GHz);
327187831Ssam		if (rd5GHz == AH_NULL) {
328187831Ssam			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
329187831Ssam			    "%s: no 5GHz reg domain %u for country %u\n",
330187831Ssam			    __func__, regpair->regDmn5GHz, country->countryCode);
331187831Ssam			return HAL_EINVAL;
332187831Ssam		}
333187831Ssam		rd2GHz = findRegDmn(regpair->regDmn2GHz);
334187831Ssam		if (rd2GHz == AH_NULL) {
335187831Ssam			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
336187831Ssam			    "%s: no 2GHz reg domain %u for country %u\n",
337187831Ssam			    __func__, regpair->regDmn2GHz, country->countryCode);
338187831Ssam			return HAL_EINVAL;
339187831Ssam		}
340185377Ssam	} else {
341187831Ssam		rd5GHz = rd2GHz = findRegDmn(regDmn);
342187831Ssam		if (rd2GHz == AH_NULL) {
343187831Ssam			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
344187831Ssam			    "%s: no unitary reg domain %u for country %u\n",
345187831Ssam			    __func__, regDmn, country->countryCode);
346187831Ssam			return HAL_EINVAL;
347185377Ssam		}
348185377Ssam	}
349187831Ssam	if (pcountry != AH_NULL)
350187831Ssam		*pcountry = country;
351187831Ssam	*prd2GHz = rd2GHz;
352187831Ssam	*prd5GHz = rd5GHz;
353187831Ssam	return HAL_OK;
354185377Ssam}
355185377Ssam
356185377Ssam/*
357187831Ssam * Construct the channel list for the specified regulatory config.
358185377Ssam */
359187831Ssamstatic HAL_STATUS
360187831Ssamgetchannels(struct ath_hal *ah,
361187831Ssam    struct ieee80211_channel chans[], u_int maxchans, int *nchans,
362187831Ssam    u_int modeSelect, HAL_CTRY_CODE cc, HAL_REG_DOMAIN regDmn,
363187831Ssam    HAL_BOOL enableExtendedChannels,
364187831Ssam    COUNTRY_CODE_TO_ENUM_RD **pcountry,
365187831Ssam    REG_DOMAIN **prd2GHz, REG_DOMAIN **prd5GHz)
366185377Ssam{
367185377Ssam#define CHANNEL_HALF_BW		10
368185377Ssam#define CHANNEL_QUARTER_BW	5
369187831Ssam#define	HAL_MODE_11A_ALL \
370187831Ssam	(HAL_MODE_11A | HAL_MODE_11A_TURBO | HAL_MODE_TURBO | \
371187831Ssam	 HAL_MODE_11A_QUARTER_RATE | HAL_MODE_11A_HALF_RATE)
372187831Ssam	REG_DOMAIN *rd5GHz, *rd2GHz;
373185377Ssam	u_int modesAvail;
374185377Ssam	const struct cmode *cm;
375187831Ssam	struct ieee80211_channel *ic;
376185377Ssam	int next, b;
377187831Ssam	HAL_STATUS status;
378185377Ssam
379187831Ssam	HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: cc %u regDmn 0x%x mode 0x%x%s\n",
380187831Ssam	    __func__, cc, regDmn, modeSelect,
381187831Ssam	    enableExtendedChannels ? " ecm" : "");
382185377Ssam
383187831Ssam	status = getregstate(ah, cc, regDmn, pcountry, &rd2GHz, &rd5GHz);
384187831Ssam	if (status != HAL_OK)
385187831Ssam		return status;
386185377Ssam
387187831Ssam	/* get modes that HW is capable of */
388187831Ssam	modesAvail = ath_hal_getWirelessModes(ah);
389187831Ssam	/* optimize work below if no 11a channels */
390187831Ssam	if (isChanBitMaskZero(rd5GHz->chan11a) &&
391187831Ssam	    (modesAvail & HAL_MODE_11A_ALL)) {
392185377Ssam		HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
393187831Ssam		    "%s: disallow all 11a\n", __func__);
394187831Ssam		modesAvail &= ~HAL_MODE_11A_ALL;
395185377Ssam	}
396185377Ssam
397185377Ssam	next = 0;
398187831Ssam	ic = &chans[0];
399185377Ssam	for (cm = modes; cm < &modes[N(modes)]; cm++) {
400185377Ssam		uint16_t c, c_hi, c_lo;
401185377Ssam		uint64_t *channelBM = AH_NULL;
402185377Ssam		REG_DMN_FREQ_BAND *fband = AH_NULL,*freqs;
403185377Ssam		int low_adj, hi_adj, channelSep, lastc;
404187831Ssam		uint32_t rdflags;
405187831Ssam		uint64_t dfsMask;
406187831Ssam		uint64_t pscan;
407185377Ssam
408185377Ssam		if ((cm->mode & modeSelect) == 0) {
409185377Ssam			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
410185377Ssam			    "%s: skip mode 0x%x flags 0x%x\n",
411185377Ssam			    __func__, cm->mode, cm->flags);
412185377Ssam			continue;
413185377Ssam		}
414185377Ssam		if ((cm->mode & modesAvail) == 0) {
415185377Ssam			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
416185377Ssam			    "%s: !avail mode 0x%x (0x%x) flags 0x%x\n",
417185377Ssam			    __func__, modesAvail, cm->mode, cm->flags);
418185377Ssam			continue;
419185377Ssam		}
420185377Ssam		if (!ath_hal_getChannelEdges(ah, cm->flags, &c_lo, &c_hi)) {
421185377Ssam			/* channel not supported by hardware, skip it */
422185377Ssam			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
423185377Ssam			    "%s: channels 0x%x not supported by hardware\n",
424185377Ssam			    __func__,cm->flags);
425185377Ssam			continue;
426185377Ssam		}
427185377Ssam		switch (cm->mode) {
428185377Ssam		case HAL_MODE_TURBO:
429187831Ssam		case HAL_MODE_11A_TURBO:
430187831Ssam			rdflags = rd5GHz->flags;
431187831Ssam			dfsMask = rd5GHz->dfsMask;
432187831Ssam			pscan = rd5GHz->pscan;
433187831Ssam			if (cm->mode == HAL_MODE_TURBO)
434187831Ssam				channelBM = rd5GHz->chan11a_turbo;
435187831Ssam			else
436187831Ssam				channelBM = rd5GHz->chan11a_dyn_turbo;
437185377Ssam			freqs = &regDmn5GhzTurboFreq[0];
438185377Ssam			break;
439187831Ssam		case HAL_MODE_11G_TURBO:
440187831Ssam			rdflags = rd2GHz->flags;
441187831Ssam			dfsMask = rd2GHz->dfsMask;
442187831Ssam			pscan = rd2GHz->pscan;
443187831Ssam			channelBM = rd2GHz->chan11g_turbo;
444187831Ssam			freqs = &regDmn2Ghz11gTurboFreq[0];
445187831Ssam			break;
446185377Ssam		case HAL_MODE_11A:
447185380Ssam		case HAL_MODE_11A_HALF_RATE:
448185380Ssam		case HAL_MODE_11A_QUARTER_RATE:
449185377Ssam		case HAL_MODE_11NA_HT20:
450185377Ssam		case HAL_MODE_11NA_HT40PLUS:
451185377Ssam		case HAL_MODE_11NA_HT40MINUS:
452187831Ssam			rdflags = rd5GHz->flags;
453187831Ssam			dfsMask = rd5GHz->dfsMask;
454187831Ssam			pscan = rd5GHz->pscan;
455185380Ssam			if (cm->mode == HAL_MODE_11A_HALF_RATE)
456187831Ssam				channelBM = rd5GHz->chan11a_half;
457185380Ssam			else if (cm->mode == HAL_MODE_11A_QUARTER_RATE)
458187831Ssam				channelBM = rd5GHz->chan11a_quarter;
459185380Ssam			else
460187831Ssam				channelBM = rd5GHz->chan11a;
461185377Ssam			freqs = &regDmn5GhzFreq[0];
462185377Ssam			break;
463185377Ssam		case HAL_MODE_11B:
464185377Ssam		case HAL_MODE_11G:
465185380Ssam		case HAL_MODE_11G_HALF_RATE:
466185380Ssam		case HAL_MODE_11G_QUARTER_RATE:
467185377Ssam		case HAL_MODE_11NG_HT20:
468185377Ssam		case HAL_MODE_11NG_HT40PLUS:
469185377Ssam		case HAL_MODE_11NG_HT40MINUS:
470187831Ssam			rdflags = rd2GHz->flags;
471187831Ssam			dfsMask = rd2GHz->dfsMask;
472187831Ssam			pscan = rd2GHz->pscan;
473185380Ssam			if (cm->mode == HAL_MODE_11G_HALF_RATE)
474187831Ssam				channelBM = rd2GHz->chan11g_half;
475185380Ssam			else if (cm->mode == HAL_MODE_11G_QUARTER_RATE)
476187831Ssam				channelBM = rd2GHz->chan11g_quarter;
477187831Ssam			else if (cm->mode == HAL_MODE_11B)
478187831Ssam				channelBM = rd2GHz->chan11b;
479185380Ssam			else
480187831Ssam				channelBM = rd2GHz->chan11g;
481187831Ssam			if (cm->mode == HAL_MODE_11B)
482187831Ssam				freqs = &regDmn2GhzFreq[0];
483187831Ssam			else
484187831Ssam				freqs = &regDmn2Ghz11gFreq[0];
485185377Ssam			break;
486185377Ssam		default:
487185377Ssam			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
488185377Ssam			    "%s: Unkonwn HAL mode 0x%x\n", __func__, cm->mode);
489185377Ssam			continue;
490185377Ssam		}
491185377Ssam		if (isChanBitMaskZero(channelBM))
492185377Ssam			continue;
493185377Ssam		/*
494185377Ssam		 * Setup special handling for HT40 channels; e.g.
495185377Ssam		 * 5G HT40 channels require 40Mhz channel separation.
496185377Ssam		 */
497185377Ssam		hi_adj = (cm->mode == HAL_MODE_11NA_HT40PLUS ||
498185377Ssam		    cm->mode == HAL_MODE_11NG_HT40PLUS) ? -20 : 0;
499185377Ssam		low_adj = (cm->mode == HAL_MODE_11NA_HT40MINUS ||
500185377Ssam		    cm->mode == HAL_MODE_11NG_HT40MINUS) ? 20 : 0;
501185377Ssam		channelSep = (cm->mode == HAL_MODE_11NA_HT40PLUS ||
502185377Ssam		    cm->mode == HAL_MODE_11NA_HT40MINUS) ? 40 : 0;
503185377Ssam
504185377Ssam		for (b = 0; b < 64*BMLEN; b++) {
505185377Ssam			if (!IS_BIT_SET(b, channelBM))
506185377Ssam				continue;
507185377Ssam			fband = &freqs[b];
508185377Ssam			lastc = 0;
509185377Ssam
510185377Ssam			for (c = fband->lowChannel + low_adj;
511185377Ssam			     c <= fband->highChannel + hi_adj;
512185377Ssam			     c += fband->channelSep) {
513185377Ssam				if (!(c_lo <= c && c <= c_hi)) {
514185377Ssam					HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
515185377Ssam					    "%s: c %u out of range [%u..%u]\n",
516185377Ssam					    __func__, c, c_lo, c_hi);
517185377Ssam					continue;
518185377Ssam				}
519185377Ssam				if (next >= maxchans){
520185377Ssam					HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
521185377Ssam					    "%s: too many channels for channel table\n",
522185377Ssam					    __func__);
523185377Ssam					goto done;
524185377Ssam				}
525185377Ssam				if ((fband->usePassScan & IS_ECM_CHAN) &&
526185377Ssam				    !enableExtendedChannels) {
527185377Ssam					HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
528187831Ssam					    "skip ecm channel\n");
529185377Ssam					continue;
530185377Ssam				}
531224242Sadrian#if 0
532187831Ssam				if ((fband->useDfs & dfsMask) &&
533187831Ssam				    (cm->flags & IEEE80211_CHAN_HT40)) {
534187831Ssam					/* NB: DFS and HT40 don't mix */
535185377Ssam					HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
536187831Ssam					    "skip HT40 chan, DFS required\n");
537185377Ssam					continue;
538185377Ssam				}
539224242Sadrian#endif
540185377Ssam				/*
541185377Ssam				 * Make sure that channel separation
542185377Ssam				 * meets the requirement.
543185377Ssam				 */
544185377Ssam				if (lastc && channelSep &&
545185377Ssam				    (c-lastc) < channelSep)
546185377Ssam					continue;
547185377Ssam				lastc = c;
548185377Ssam
549187831Ssam				OS_MEMZERO(ic, sizeof(*ic));
550187831Ssam				ic->ic_freq = c;
551187831Ssam				ic->ic_flags = cm->flags;
552187831Ssam				ic->ic_maxregpower = fband->powerDfs;
553187831Ssam				ath_hal_getpowerlimits(ah, ic);
554187831Ssam				ic->ic_maxantgain = fband->antennaMax;
555187831Ssam				if (fband->usePassScan & pscan)
556187831Ssam					ic->ic_flags |= IEEE80211_CHAN_PASSIVE;
557187831Ssam				if (fband->useDfs & dfsMask)
558187831Ssam					ic->ic_flags |= IEEE80211_CHAN_DFS;
559187831Ssam				if (IEEE80211_IS_CHAN_5GHZ(ic) &&
560187831Ssam				    (rdflags & DISALLOW_ADHOC_11A))
561187831Ssam					ic->ic_flags |= IEEE80211_CHAN_NOADHOC;
562187831Ssam				if (IEEE80211_IS_CHAN_TURBO(ic) &&
563187831Ssam				    (rdflags & DISALLOW_ADHOC_11A_TURB))
564187831Ssam					ic->ic_flags |= IEEE80211_CHAN_NOADHOC;
565187831Ssam				if (rdflags & NO_HOSTAP)
566187831Ssam					ic->ic_flags |= IEEE80211_CHAN_NOHOSTAP;
567187831Ssam				if (rdflags & LIMIT_FRAME_4MS)
568187831Ssam					ic->ic_flags |= IEEE80211_CHAN_4MSXMIT;
569187831Ssam				if (rdflags & NEED_NFC)
570187831Ssam					ic->ic_flags |= CHANNEL_NFCREQUIRED;
571187831Ssam
572187831Ssam				ic++, next++;
573185377Ssam			}
574185377Ssam		}
575185377Ssam	}
576185377Ssamdone:
577185377Ssam	*nchans = next;
578187831Ssam	/* NB: pcountry set above by getregstate */
579187831Ssam	if (prd2GHz != AH_NULL)
580187831Ssam		*prd2GHz = rd2GHz;
581187831Ssam	if (prd5GHz != AH_NULL)
582187831Ssam		*prd5GHz = rd5GHz;
583187831Ssam	return HAL_OK;
584187831Ssam#undef HAL_MODE_11A_ALL
585185377Ssam#undef CHANNEL_HALF_BW
586185377Ssam#undef CHANNEL_QUARTER_BW
587185377Ssam}
588185377Ssam
589185377Ssam/*
590187831Ssam * Retrieve a channel list without affecting runtime state.
591185377Ssam */
592187831SsamHAL_STATUS
593187831Ssamath_hal_getchannels(struct ath_hal *ah,
594187831Ssam    struct ieee80211_channel chans[], u_int maxchans, int *nchans,
595187831Ssam    u_int modeSelect, HAL_CTRY_CODE cc, HAL_REG_DOMAIN regDmn,
596187831Ssam    HAL_BOOL enableExtendedChannels)
597185377Ssam{
598187831Ssam	return getchannels(ah, chans, maxchans, nchans, modeSelect,
599187831Ssam	    cc, regDmn, enableExtendedChannels, AH_NULL, AH_NULL, AH_NULL);
600187831Ssam}
601185377Ssam
602187831Ssam/*
603187831Ssam * Handle frequency mapping from 900Mhz range to 2.4GHz range
604187831Ssam * for GSM radios.  This is done when we need the h/w frequency
605187831Ssam * and the channel is marked IEEE80211_CHAN_GSM.
606187831Ssam */
607187831Ssamstatic int
608187831Ssamath_hal_mapgsm(int sku, int freq)
609187831Ssam{
610187831Ssam	if (sku == SKU_XR9)
611187831Ssam		return 1520 + freq;
612187831Ssam	if (sku == SKU_GZ901)
613187831Ssam		return 1544 + freq;
614187831Ssam	if (sku == SKU_SR9)
615187831Ssam		return 3344 - freq;
616243975Sadrian	if (sku == SKU_XC900M)
617243975Sadrian		return 1517 + freq;
618225883Sadrian	HALDEBUG(AH_NULL, HAL_DEBUG_ANY,
619187831Ssam	    "%s: cannot map freq %u unknown gsm sku %u\n",
620187831Ssam	    __func__, freq, sku);
621187831Ssam	return freq;
622187831Ssam}
623185377Ssam
624187831Ssam/*
625187831Ssam * Setup the internal/private channel state given a table of
626187831Ssam * net80211 channels.  We collapse entries for the same frequency
627187831Ssam * and record the frequency for doing noise floor processing
628187831Ssam * where we don't have net80211 channel context.
629187831Ssam */
630187831Ssamstatic HAL_BOOL
631187831SsamassignPrivateChannels(struct ath_hal *ah,
632187831Ssam	struct ieee80211_channel chans[], int nchans, int sku)
633187831Ssam{
634187831Ssam	HAL_CHANNEL_INTERNAL *ic;
635187831Ssam	int i, j, next, freq;
636187831Ssam
637187831Ssam	next = 0;
638187831Ssam	for (i = 0; i < nchans; i++) {
639187831Ssam		struct ieee80211_channel *c = &chans[i];
640187831Ssam		for (j = i-1; j >= 0; j--)
641187831Ssam			if (chans[j].ic_freq == c->ic_freq) {
642187831Ssam				c->ic_devdata = chans[j].ic_devdata;
643187831Ssam				break;
644185377Ssam			}
645187831Ssam		if (j < 0) {
646187831Ssam			/* new entry, assign a private channel entry */
647187831Ssam			if (next >= N(AH_PRIVATE(ah)->ah_channels)) {
648187831Ssam				HALDEBUG(ah, HAL_DEBUG_ANY,
649188084Ssam				    "%s: too many channels, max %zu\n",
650187831Ssam				    __func__, N(AH_PRIVATE(ah)->ah_channels));
651187831Ssam				return AH_FALSE;
652187831Ssam			}
653187831Ssam			/*
654187831Ssam			 * Handle frequency mapping for 900MHz devices.
655187831Ssam			 * The hardware uses 2.4GHz frequencies that are
656187831Ssam			 * down-converted.  The 802.11 layer uses the
657187831Ssam			 * true frequencies.
658187831Ssam			 */
659187831Ssam			freq = IEEE80211_IS_CHAN_GSM(c) ?
660187831Ssam			    ath_hal_mapgsm(sku, c->ic_freq) : c->ic_freq;
661187831Ssam
662187831Ssam			HALDEBUG(ah, HAL_DEBUG_REGDOMAIN,
663187831Ssam			    "%s: private[%3u] %u/0x%x -> channel %u\n",
664187831Ssam			    __func__, next, c->ic_freq, c->ic_flags, freq);
665187831Ssam
666187831Ssam			ic = &AH_PRIVATE(ah)->ah_channels[next];
667187831Ssam			/*
668187831Ssam			 * NB: This clears privFlags which means ancillary
669187831Ssam			 *     code like ANI and IQ calibration will be
670187831Ssam			 *     restarted and re-setup any per-channel state.
671187831Ssam			 */
672187831Ssam			OS_MEMZERO(ic, sizeof(*ic));
673187831Ssam			ic->channel = freq;
674187831Ssam			c->ic_devdata = next;
675187831Ssam			next++;
676185377Ssam		}
677185377Ssam	}
678187831Ssam	AH_PRIVATE(ah)->ah_nchan = next;
679187831Ssam	HALDEBUG(ah, HAL_DEBUG_ANY, "%s: %u public, %u private channels\n",
680187831Ssam	    __func__, nchans, next);
681187831Ssam	return AH_TRUE;
682185377Ssam}
683185377Ssam
684185377Ssam/*
685187831Ssam * Setup the channel list based on the information in the EEPROM.
686185377Ssam */
687187831SsamHAL_STATUS
688187831Ssamath_hal_init_channels(struct ath_hal *ah,
689187831Ssam    struct ieee80211_channel chans[], u_int maxchans, int *nchans,
690187831Ssam    u_int modeSelect, HAL_CTRY_CODE cc, HAL_REG_DOMAIN regDmn,
691187831Ssam    HAL_BOOL enableExtendedChannels)
692185377Ssam{
693187831Ssam	COUNTRY_CODE_TO_ENUM_RD *country;
694224718Sadrian	REG_DOMAIN *rd5GHz, *rd2GHz;
695187831Ssam	HAL_STATUS status;
696185377Ssam
697187831Ssam	status = getchannels(ah, chans, maxchans, nchans, modeSelect,
698187831Ssam	    cc, regDmn, enableExtendedChannels, &country, &rd2GHz, &rd5GHz);
699187831Ssam	if (status == HAL_OK &&
700187831Ssam	    assignPrivateChannels(ah, chans, *nchans, AH_PRIVATE(ah)->ah_currentRD)) {
701187831Ssam		AH_PRIVATE(ah)->ah_rd2GHz = rd2GHz;
702187831Ssam		AH_PRIVATE(ah)->ah_rd5GHz = rd5GHz;
703187831Ssam
704187831Ssam		ah->ah_countryCode = country->countryCode;
705187831Ssam		HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: cc %u\n",
706187831Ssam		    __func__, ah->ah_countryCode);
707224718Sadrian
708224718Sadrian		/* Update current DFS domain */
709224718Sadrian		ath_hal_update_dfsdomain(ah);
710187831Ssam	} else
711187831Ssam		status = HAL_EINVAL;
712224716Sadrian
713187831Ssam	return status;
714185377Ssam}
715185377Ssam
716187831Ssam/*
717187831Ssam * Set the channel list.
718187831Ssam */
719187831SsamHAL_STATUS
720187831Ssamath_hal_set_channels(struct ath_hal *ah,
721187831Ssam    struct ieee80211_channel chans[], int nchans,
722187831Ssam    HAL_CTRY_CODE cc, HAL_REG_DOMAIN rd)
723187831Ssam{
724187831Ssam	COUNTRY_CODE_TO_ENUM_RD *country;
725187831Ssam	REG_DOMAIN *rd5GHz, *rd2GHz;
726187831Ssam	HAL_STATUS status;
727185377Ssam
728187831Ssam	switch (rd) {
729187831Ssam	case SKU_SR9:
730187831Ssam	case SKU_XR9:
731187831Ssam	case SKU_GZ901:
732243975Sadrian	case SKU_XC900M:
733187831Ssam		/*
734187831Ssam		 * Map 900MHz sku's.  The frequencies will be mapped
735187831Ssam		 * according to the sku to compensate for the down-converter.
736187831Ssam		 * We use the FCC for these sku's as the mapped channel
737187831Ssam		 * list is known compatible (will need to change if/when
738187831Ssam		 * vendors do different mapping in different locales).
739187831Ssam		 */
740187831Ssam		status = getregstate(ah, CTRY_DEFAULT, SKU_FCC,
741187831Ssam		    &country, &rd2GHz, &rd5GHz);
742187831Ssam		break;
743187831Ssam	default:
744187831Ssam		status = getregstate(ah, cc, rd,
745187831Ssam		    &country, &rd2GHz, &rd5GHz);
746187831Ssam		rd = AH_PRIVATE(ah)->ah_currentRD;
747187831Ssam		break;
748187831Ssam	}
749187831Ssam	if (status == HAL_OK && assignPrivateChannels(ah, chans, nchans, rd)) {
750187831Ssam		AH_PRIVATE(ah)->ah_rd2GHz = rd2GHz;
751187831Ssam		AH_PRIVATE(ah)->ah_rd5GHz = rd5GHz;
752185377Ssam
753187831Ssam		ah->ah_countryCode = country->countryCode;
754187831Ssam		HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: cc %u\n",
755187831Ssam		    __func__, ah->ah_countryCode);
756187831Ssam	} else
757187831Ssam		status = HAL_EINVAL;
758224718Sadrian
759224718Sadrian	if (status == HAL_OK) {
760224718Sadrian		/* Update current DFS domain */
761224718Sadrian		(void) ath_hal_update_dfsdomain(ah);
762224718Sadrian	}
763187831Ssam	return status;
764187831Ssam}
765185377Ssam
766187831Ssam#ifdef AH_DEBUG
767185377Ssam/*
768187831Ssam * Return the internal channel corresponding to a public channel.
769187831Ssam * NB: normally this routine is inline'd (see ah_internal.h)
770185377Ssam */
771187831SsamHAL_CHANNEL_INTERNAL *
772187831Ssamath_hal_checkchannel(struct ath_hal *ah, const struct ieee80211_channel *c)
773185377Ssam{
774187831Ssam	HAL_CHANNEL_INTERNAL *cc = &AH_PRIVATE(ah)->ah_channels[c->ic_devdata];
775185377Ssam
776187831Ssam	if (c->ic_devdata < AH_PRIVATE(ah)->ah_nchan &&
777187831Ssam	    (c->ic_freq == cc->channel || IEEE80211_IS_CHAN_GSM(c)))
778187831Ssam		return cc;
779187831Ssam	if (c->ic_devdata >= AH_PRIVATE(ah)->ah_nchan) {
780187831Ssam		HALDEBUG(ah, HAL_DEBUG_ANY,
781187831Ssam		    "%s: bad mapping, devdata %u nchans %u\n",
782187831Ssam		   __func__, c->ic_devdata, AH_PRIVATE(ah)->ah_nchan);
783187831Ssam		HALASSERT(c->ic_devdata < AH_PRIVATE(ah)->ah_nchan);
784185377Ssam	} else {
785187831Ssam		HALDEBUG(ah, HAL_DEBUG_ANY,
786187831Ssam		    "%s: no match for %u/0x%x devdata %u channel %u\n",
787187831Ssam		   __func__, c->ic_freq, c->ic_flags, c->ic_devdata,
788187831Ssam		   cc->channel);
789187831Ssam		HALASSERT(c->ic_freq == cc->channel || IEEE80211_IS_CHAN_GSM(c));
790185377Ssam	}
791187831Ssam	return AH_NULL;
792185377Ssam}
793187831Ssam#endif /* AH_DEBUG */
794185377Ssam
795187831Ssam#define isWwrSKU(_ah) \
796187831Ssam	((getEepromRD((_ah)) & WORLD_SKU_MASK) == WORLD_SKU_PREFIX || \
797187831Ssam	  getEepromRD(_ah) == WORLD)
798187831Ssam
799185377Ssam/*
800187831Ssam * Return the test group for the specific channel based on
801187831Ssam * the current regulatory setup.
802185377Ssam */
803187831Ssamu_int
804187831Ssamath_hal_getctl(struct ath_hal *ah, const struct ieee80211_channel *c)
805185377Ssam{
806187831Ssam	u_int ctl;
807185377Ssam
808187831Ssam	if (AH_PRIVATE(ah)->ah_rd2GHz == AH_PRIVATE(ah)->ah_rd5GHz ||
809187831Ssam	    (ah->ah_countryCode == CTRY_DEFAULT && isWwrSKU(ah)))
810187831Ssam		ctl = SD_NO_CTL;
811187831Ssam	else if (IEEE80211_IS_CHAN_2GHZ(c))
812187831Ssam		ctl = AH_PRIVATE(ah)->ah_rd2GHz->conformanceTestLimit;
813187831Ssam	else
814187831Ssam		ctl = AH_PRIVATE(ah)->ah_rd5GHz->conformanceTestLimit;
815187831Ssam	if (IEEE80211_IS_CHAN_B(c))
816187831Ssam		return ctl | CTL_11B;
817187831Ssam	if (IEEE80211_IS_CHAN_G(c))
818187831Ssam		return ctl | CTL_11G;
819187831Ssam	if (IEEE80211_IS_CHAN_108G(c))
820187831Ssam		return ctl | CTL_108G;
821187831Ssam	if (IEEE80211_IS_CHAN_TURBO(c))
822187831Ssam		return ctl | CTL_TURBO;
823187831Ssam	if (IEEE80211_IS_CHAN_A(c))
824187831Ssam		return ctl | CTL_11A;
825187831Ssam	return ctl;
826185377Ssam}
827185377Ssam
828224718Sadrian
829185377Ssam/*
830224718Sadrian * Update the current dfsDomain setting based on the given
831224718Sadrian * country code.
832224718Sadrian *
833224718Sadrian * Since FreeBSD/net80211 allows the channel set to change
834224718Sadrian * after the card has been setup (via ath_hal_init_channels())
835224718Sadrian * this function method is needed to update ah_dfsDomain.
836224718Sadrian */
837224718Sadrianvoid
838224718Sadrianath_hal_update_dfsdomain(struct ath_hal *ah)
839224718Sadrian{
840224718Sadrian	const REG_DOMAIN *rd5GHz = AH_PRIVATE(ah)->ah_rd5GHz;
841224718Sadrian	HAL_DFS_DOMAIN dfsDomain = HAL_DFS_UNINIT_DOMAIN;
842224718Sadrian
843224718Sadrian	if (rd5GHz->dfsMask & DFS_FCC3)
844224718Sadrian		dfsDomain = HAL_DFS_FCC_DOMAIN;
845224718Sadrian	if (rd5GHz->dfsMask & DFS_ETSI)
846224718Sadrian		dfsDomain = HAL_DFS_ETSI_DOMAIN;
847224718Sadrian	if (rd5GHz->dfsMask & DFS_MKK4)
848224718Sadrian		dfsDomain = HAL_DFS_MKK4_DOMAIN;
849224718Sadrian	AH_PRIVATE(ah)->ah_dfsDomain = dfsDomain;
850224718Sadrian	HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s ah_dfsDomain: %d\n",
851224718Sadrian	    __func__, AH_PRIVATE(ah)->ah_dfsDomain);
852224718Sadrian}
853224718Sadrian
854224718Sadrian
855224718Sadrian/*
856187831Ssam * Return the max allowed antenna gain and apply any regulatory
857187831Ssam * domain specific changes.
858187831Ssam *
859187831Ssam * NOTE: a negative reduction is possible in RD's that only
860187831Ssam * measure radiated power (e.g., ETSI) which would increase
861187831Ssam * that actual conducted output power (though never beyond
862187831Ssam * the calibrated target power).
863185377Ssam */
864187831Ssamu_int
865187831Ssamath_hal_getantennareduction(struct ath_hal *ah,
866187831Ssam    const struct ieee80211_channel *chan, u_int twiceGain)
867185377Ssam{
868187831Ssam	int8_t antennaMax = twiceGain - chan->ic_maxantgain*2;
869187831Ssam	return (antennaMax < 0) ? 0 : antennaMax;
870185377Ssam}
871