blob: 452392eba2a0c116022795ddd4344a6e119a095e [file] [log] [blame]
/*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
/*
* Notifications and licenses are retained for attribution purposes only.
*/
/*
* Copyright (c) 2002-2006 Sam Leffler, Errno Consulting
* Copyright (c) 2005-2006 Atheros Communications, Inc.
* Copyright (c) 2010, Atheros Communications Inc.
*
* Redistribution and use in source and binary forms are permitted
* provided that the following conditions are met:
* 1. The materials contained herein are unmodified and are used
* unmodified.
* 2. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following NO
* ''WARRANTY'' disclaimer below (''Disclaimer''), without
* modification.
* 3. Redistributions in binary form must reproduce at minimum a
* disclaimer similar to the Disclaimer below and any redistribution
* must be conditioned upon including a substantially similar
* Disclaimer requirement for further binary redistribution.
* 4. Neither the names of the above-listed copyright holders nor the
* names of any contributors may be used to endorse or promote
* product derived from this software without specific prior written
* permission.
*
* NO WARRANTY
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT,
* MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
* FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGES.
*/
#include <adf_os_types.h>
#include "regdomain.h"
#include "regdomain_common.h"
#include "wma.h"
#define N(a) (sizeof(a)/sizeof(a[0]))
/*
* By default, the regdomain tables reference the common tables
* from regdomain_common.h. These default tables can be replaced
* by calls to populate_regdomain_tables functions.
*/
HAL_REG_DMN_TABLES ol_regdmn_Rdt = {
ahCmnRegDomainPairs, /* regDomainPairs */
ahCmnAllCountries, /* allCountries */
ahCmnRegDomains, /* allRegDomains */
N(ahCmnRegDomainPairs), /* regDomainPairsCt */
N(ahCmnAllCountries), /* allCountriesCt */
N(ahCmnRegDomains), /* allRegDomainCt */
};
static u_int16_t get_eeprom_rd(u_int16_t rd)
{
return rd & ~WORLDWIDE_ROAMING_FLAG;
}
/*
* Return whether or not the regulatory domain/country in EEPROM
* is acceptable.
*/
static bool regmn_is_eeprom_valid(u_int16_t rd)
{
int32_t i;
if (rd & COUNTRY_ERD_FLAG) {
u_int16_t cc = rd & ~COUNTRY_ERD_FLAG;
for (i = 0; i < ol_regdmn_Rdt.allCountriesCt; i++)
if (ol_regdmn_Rdt.allCountries[i].countryCode == cc)
return true;
} else {
for (i = 0; i < ol_regdmn_Rdt.regDomainPairsCt; i++)
if (ol_regdmn_Rdt.regDomainPairs[i].regDmnEnum == rd)
return true;
}
/* TODO: Bring it under debug level */
adf_os_print("%s: invalid regulatory domain/country code 0x%x\n",
__func__, rd);
return false;
}
/*
* Find the pointer to the country element in the country table
* corresponding to the country code
*/
static const COUNTRY_CODE_TO_ENUM_RD *find_country(u_int16_t country_code)
{
int32_t i;
for (i = 0; i < ol_regdmn_Rdt.allCountriesCt; i++) {
if (ol_regdmn_Rdt.allCountries[i].countryCode == country_code)
return &ol_regdmn_Rdt.allCountries[i];
}
return NULL; /* Not found */
}
static u_int16_t regdmn_get_default_country(u_int16_t rd)
{
int32_t i;
if (rd & COUNTRY_ERD_FLAG) {
const COUNTRY_CODE_TO_ENUM_RD *country = NULL;
u_int16_t cc = rd & ~COUNTRY_ERD_FLAG;
country = find_country(cc);
if (country)
return cc;
}
/*
* Check reg domains that have only one country
*/
for (i = 0; i < ol_regdmn_Rdt.regDomainPairsCt; i++) {
if (ol_regdmn_Rdt.regDomainPairs[i].regDmnEnum == rd) {
if (ol_regdmn_Rdt.regDomainPairs[i].singleCC != 0)
return ol_regdmn_Rdt.regDomainPairs[i].singleCC;
else
i = ol_regdmn_Rdt.regDomainPairsCt;
}
}
return CTRY_DEFAULT;
}
static const REG_DMN_PAIR_MAPPING *get_regdmn_pair(u_int16_t reg_dmn)
{
int32_t i;
for (i = 0; i < ol_regdmn_Rdt.regDomainPairsCt; i++) {
if (ol_regdmn_Rdt.regDomainPairs[i].regDmnEnum == reg_dmn)
return &ol_regdmn_Rdt.regDomainPairs[i];
}
return NULL;
}
static const REG_DOMAIN *get_regdmn(u_int16_t reg_dmn)
{
int32_t i;
for (i = 0; i < ol_regdmn_Rdt.regDomainsCt; i++) {
if (ol_regdmn_Rdt.regDomains[i].regDmnEnum == reg_dmn)
return &ol_regdmn_Rdt.regDomains[i];
}
return NULL;
}
static const COUNTRY_CODE_TO_ENUM_RD *get_country_from_rd(u_int16_t regdmn)
{
int32_t i;
for (i = 0; i < ol_regdmn_Rdt.allCountriesCt; i++) {
if (ol_regdmn_Rdt.allCountries[i].regDmnEnum == regdmn)
return &ol_regdmn_Rdt.allCountries[i];
}
return NULL; /* Not found */
}
/*
* Returns country string for the given regulatory domain.
*/
int32_t regdmn_get_country_alpha2(u_int16_t rd, u_int8_t *alpha2)
{
u_int16_t country_code;
u_int16_t regdmn;
const COUNTRY_CODE_TO_ENUM_RD *country = NULL;
const REG_DMN_PAIR_MAPPING *regpair = NULL;
regdmn = get_eeprom_rd(rd);
if (!regmn_is_eeprom_valid(rd))
return -EINVAL;
country_code = regdmn_get_default_country(regdmn);
if (country_code == CTRY_DEFAULT && regdmn == CTRY_DEFAULT) {
/* Set to CTRY_UNITED_STATES for testing */
country_code = CTRY_UNITED_STATES;
}
if (country_code != CTRY_DEFAULT) {
country = find_country(country_code);
if (!country) {
/* TODO: Bring it under debug level */
adf_os_print(KERN_ERR "Not a valid country code\n");
return -EINVAL;
}
regdmn = country->regDmnEnum;
}
regpair = get_regdmn_pair(regdmn);
if (!regpair) {
/* TODO: Bring it under debug level */
adf_os_print(KERN_ERR "No regpair is found, can not proceeed\n");
return -EINVAL;
}
if (!country)
country = get_country_from_rd(regdmn);
if (country) {
alpha2[0] = country->isoName[0];
alpha2[1] = country->isoName[1];
} else {
alpha2[0] = '0';
alpha2[1] = '0';
}
return 0;
}
/*
* Returns regulatory domain for given country string
*/
int32_t regdmn_get_regdmn_for_country(u_int8_t *alpha2)
{
u_int8_t i;
for (i = 0; i < ol_regdmn_Rdt.allCountriesCt; i++) {
if ((ol_regdmn_Rdt.allCountries[i].isoName[0] == alpha2[0]) &&
(ol_regdmn_Rdt.allCountries[i].isoName[1] == alpha2[1]))
return ol_regdmn_Rdt.allCountries[i].regDmnEnum;
}
return -1;
}
/*
* Test to see if the bitmask array is all zeros
*/
static bool
isChanBitMaskZero(const u_int64_t *bitmask)
{
int i;
for (i = 0; i < BMLEN; i++) {
if (bitmask[i] != 0)
return false;
}
return true;
}
/*
* Return the mask of available modes based on the hardware
* capabilities and the specified country code and reg domain.
*/
u_int32_t regdmn_getwmodesnreg(u_int32_t modesAvail,
const COUNTRY_CODE_TO_ENUM_RD *country,
const REG_DOMAIN *rd5GHz)
{
/* Check country regulations for allowed modes */
if ((modesAvail & (REGDMN_MODE_11A_TURBO|REGDMN_MODE_TURBO)) &&
(!country->allow11aTurbo))
modesAvail &= ~(REGDMN_MODE_11A_TURBO | REGDMN_MODE_TURBO);
if ((modesAvail & REGDMN_MODE_11G_TURBO) &&
(!country->allow11gTurbo))
modesAvail &= ~REGDMN_MODE_11G_TURBO;
if ((modesAvail & REGDMN_MODE_11G) &&
(!country->allow11g))
modesAvail &= ~REGDMN_MODE_11G;
if ((modesAvail & REGDMN_MODE_11A) &&
(isChanBitMaskZero(rd5GHz->chan11a)))
modesAvail &= ~REGDMN_MODE_11A;
if ((modesAvail & REGDMN_MODE_11NG_HT20) &&
(!country->allow11ng20))
modesAvail &= ~REGDMN_MODE_11NG_HT20;
if ((modesAvail & REGDMN_MODE_11NA_HT20) &&
(!country->allow11na20))
modesAvail &= ~REGDMN_MODE_11NA_HT20;
if ((modesAvail & REGDMN_MODE_11NG_HT40PLUS) &&
(!country->allow11ng40))
modesAvail &= ~REGDMN_MODE_11NG_HT40PLUS;
if ((modesAvail & REGDMN_MODE_11NG_HT40MINUS) &&
(!country->allow11ng40))
modesAvail &= ~REGDMN_MODE_11NG_HT40MINUS;
if ((modesAvail & REGDMN_MODE_11NA_HT40PLUS) &&
(!country->allow11na40))
modesAvail &= ~REGDMN_MODE_11NA_HT40PLUS;
if ((modesAvail & REGDMN_MODE_11NA_HT40MINUS) &&
(!country->allow11na40))
modesAvail &= ~REGDMN_MODE_11NA_HT40MINUS;
if ((modesAvail & REGDMN_MODE_11AC_VHT20) &&
(!country->allow11na20))
modesAvail &= ~REGDMN_MODE_11AC_VHT20;
if ((modesAvail & REGDMN_MODE_11AC_VHT40PLUS) &&
(!country->allow11na40))
modesAvail &= ~REGDMN_MODE_11AC_VHT40PLUS;
if ((modesAvail & REGDMN_MODE_11AC_VHT40MINUS) &&
(!country->allow11na40))
modesAvail &= ~REGDMN_MODE_11AC_VHT40MINUS;
if ((modesAvail & REGDMN_MODE_11AC_VHT80) &&
(!country->allow11na80))
modesAvail &= ~REGDMN_MODE_11AC_VHT80;
if ((modesAvail & REGDMN_MODE_11AC_VHT20_2G) &&
(!country->allow11ng20))
modesAvail &= ~REGDMN_MODE_11AC_VHT20_2G;
return modesAvail;
}
void regdmn_get_ctl_info(u_int32_t regdmn, u_int32_t modesAvail, u_int32_t modeSelect)
{
const REG_DMN_PAIR_MAPPING *regpair = NULL;
const REG_DOMAIN *regdomain2G = NULL;
const REG_DOMAIN *regdomain5G = NULL;
int8_t ctl_2g, ctl_5g, ctl;
const REG_DOMAIN *rd = NULL;
const struct cmode *cm;
u_int16_t country_code;
const COUNTRY_CODE_TO_ENUM_RD *country;
country_code = regdmn_get_default_country(regdmn);
if (country_code == CTRY_DEFAULT && regdmn == CTRY_DEFAULT)
country_code = CTRY_UNITED_STATES;
country = find_country(country_code);
if (country != NULL)
regdmn = country->regDmnEnum;
/* get regulatory domain pair */
regpair = get_regdmn_pair(regdmn);
if (!regpair) {
adf_os_print(KERN_ERR "Failed to get regdmn pair");
return;
}
regdomain2G = get_regdmn(regpair->regDmn2GHz);
if (!regdomain2G) {
adf_os_print(KERN_ERR "Failed to get regdmn 2G");
return;
}
regdomain5G = get_regdmn(regpair->regDmn5GHz);
if (!regdomain5G) {
adf_os_print(KERN_ERR "Failed to get regdmn 5G");
return;
}
/* find first nible of CTL */
ctl_2g = regdomain2G->conformance_test_limit;
ctl_5g = regdomain5G->conformance_test_limit;
/* find second nible of CTL */
if (country != NULL)
modesAvail = regdmn_getwmodesnreg(modesAvail, country, regdomain5G);
for (cm = modes; cm < &modes[N(modes)]; cm++) {
if ((cm->mode & modeSelect) == 0)
continue;
if ((cm->mode & modesAvail) == 0)
continue;
switch (cm->mode) {
case REGDMN_MODE_TURBO:
rd = regdomain5G;
ctl = rd->conformance_test_limit | CTL_TURBO;
break;
case REGDMN_MODE_11A:
case REGDMN_MODE_11NA_HT20:
case REGDMN_MODE_11NA_HT40PLUS:
case REGDMN_MODE_11NA_HT40MINUS:
case REGDMN_MODE_11AC_VHT20:
case REGDMN_MODE_11AC_VHT40PLUS:
case REGDMN_MODE_11AC_VHT40MINUS:
case REGDMN_MODE_11AC_VHT80:
rd = regdomain5G;
ctl = rd->conformance_test_limit;
break;
case REGDMN_MODE_11B:
rd = regdomain2G;
ctl = rd->conformance_test_limit | CTL_11B;
break;
case REGDMN_MODE_11G:
case REGDMN_MODE_11NG_HT20:
case REGDMN_MODE_11NG_HT40PLUS:
case REGDMN_MODE_11NG_HT40MINUS:
case REGDMN_MODE_11AC_VHT20_2G:
case REGDMN_MODE_11AC_VHT40_2G:
case REGDMN_MODE_11AC_VHT80_2G:
rd = regdomain2G;
ctl = rd->conformance_test_limit | CTL_11G;
break;
case REGDMN_MODE_11G_TURBO:
rd = regdomain2G;
ctl = rd->conformance_test_limit | CTL_108G;
break;
case REGDMN_MODE_11A_TURBO:
rd = regdomain5G;
ctl = rd->conformance_test_limit | CTL_108G;
break;
default:
adf_os_print(KERN_ERR "%s: Unkonwn HAL mode 0x%x\n",
__func__, cm->mode);
continue;
}
if (rd == regdomain2G)
ctl_2g = ctl;
if (rd == regdomain5G)
ctl_5g = ctl;
}
wma_send_regdomain_info(regdmn, regpair->regDmn2GHz,
regpair->regDmn5GHz, ctl_2g, ctl_5g);
}