1185380Ssam/* 2187831Ssam * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting 3185380Ssam * Copyright (c) 2002-2008 Atheros Communications, Inc. 4185380Ssam * 5185380Ssam * Permission to use, copy, modify, and/or distribute this software for any 6185380Ssam * purpose with or without fee is hereby granted, provided that the above 7185380Ssam * copyright notice and this permission notice appear in all copies. 8185380Ssam * 9185380Ssam * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10185380Ssam * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11185380Ssam * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12185380Ssam * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13185380Ssam * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14185380Ssam * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15185380Ssam * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16185380Ssam * 17187831Ssam * $FreeBSD$ 18185380Ssam */ 19185380Ssam#include "opt_ah.h" 20185380Ssam 21185380Ssam#include "ah.h" 22185380Ssam#include "ah_internal.h" 23185380Ssam#include "ah_devid.h" 24185380Ssam 25185380Ssam#include "ah_eeprom_v14.h" 26185380Ssam 27211207Sadrian#include "ar5212/ar5212.h" /* for NF cal related declarations */ 28211207Sadrian 29185380Ssam#include "ar5416/ar5416.h" 30185380Ssam#include "ar5416/ar5416reg.h" 31185380Ssam#include "ar5416/ar5416phy.h" 32185380Ssam 33185380Ssam/* Owl specific stuff */ 34185380Ssam#define NUM_NOISEFLOOR_READINGS 6 /* 3 chains * (ctl + ext) */ 35185380Ssam 36185380Ssamstatic void ar5416StartNFCal(struct ath_hal *ah); 37187831Ssamstatic void ar5416LoadNF(struct ath_hal *ah, const struct ieee80211_channel *); 38187831Ssamstatic int16_t ar5416GetNf(struct ath_hal *, struct ieee80211_channel *); 39185380Ssam 40218068Sadrianstatic uint16_t ar5416GetDefaultNF(struct ath_hal *ah, const struct ieee80211_channel *chan); 41218068Sadrianstatic void ar5416SanitizeNF(struct ath_hal *ah, int16_t *nf); 42218068Sadrian 43185380Ssam/* 44185380Ssam * Determine if calibration is supported by device and channel flags 45185380Ssam */ 46217629Sadrian 47217629Sadrian/* 48217629Sadrian * ADC GAIN/DC offset calibration is for calibrating two ADCs that 49217629Sadrian * are acting as one by interleaving incoming symbols. This isn't 50217629Sadrian * relevant for 2.4GHz 20MHz wide modes because, as far as I can tell, 51217629Sadrian * the secondary ADC is never enabled. It is enabled however for 52217629Sadrian * 5GHz modes. 53217629Sadrian * 54217629Sadrian * It hasn't been confirmed whether doing this calibration is needed 55217629Sadrian * at all in the above modes and/or whether it's actually harmful. 56217629Sadrian * So for now, let's leave it enabled and just remember to get 57217629Sadrian * confirmation that it needs to be clarified. 58217629Sadrian * 59217629Sadrian * See US Patent No: US 7,541,952 B1: 60217629Sadrian * " Method and Apparatus for Offset and Gain Compensation for 61217629Sadrian * Analog-to-Digital Converters." 62217629Sadrian */ 63185380Ssamstatic OS_INLINE HAL_BOOL 64187831Ssamar5416IsCalSupp(struct ath_hal *ah, const struct ieee80211_channel *chan, 65187831Ssam HAL_CAL_TYPE calType) 66185380Ssam{ 67185380Ssam struct ar5416PerCal *cal = &AH5416(ah)->ah_cal; 68185380Ssam 69185380Ssam switch (calType & cal->suppCals) { 70185380Ssam case IQ_MISMATCH_CAL: 71185380Ssam /* Run IQ Mismatch for non-CCK only */ 72187831Ssam return !IEEE80211_IS_CHAN_B(chan); 73185380Ssam case ADC_GAIN_CAL: 74185380Ssam case ADC_DC_CAL: 75224510Sadrian /* 76225421Sadrian * Run ADC Gain Cal for either 5ghz any or 2ghz HT40. 77225421Sadrian * 78225421Sadrian * Don't run ADC calibrations for 5ghz fast clock mode 79225421Sadrian * in HT20 - only one ADC is used. 80224510Sadrian */ 81225421Sadrian if (IEEE80211_IS_CHAN_HT20(chan) && 82225421Sadrian (IS_5GHZ_FAST_CLOCK_EN(ah, chan))) 83225421Sadrian return AH_FALSE; 84225421Sadrian if (IEEE80211_IS_CHAN_5GHZ(chan)) 85225421Sadrian return AH_TRUE; 86219984Sadrian if (IEEE80211_IS_CHAN_HT40(chan)) 87219984Sadrian return AH_TRUE; 88219984Sadrian return AH_FALSE; 89185380Ssam } 90185380Ssam return AH_FALSE; 91185380Ssam} 92185380Ssam 93185380Ssam/* 94185380Ssam * Setup HW to collect samples used for current cal 95185380Ssam */ 96185380Ssamstatic void 97185380Ssamar5416SetupMeasurement(struct ath_hal *ah, HAL_CAL_LIST *currCal) 98185380Ssam{ 99185380Ssam /* Start calibration w/ 2^(INIT_IQCAL_LOG_COUNT_MAX+1) samples */ 100185380Ssam OS_REG_RMW_FIELD(ah, AR_PHY_TIMING_CTRL4, 101185380Ssam AR_PHY_TIMING_CTRL4_IQCAL_LOG_COUNT_MAX, 102185380Ssam currCal->calData->calCountMax); 103185380Ssam 104185380Ssam /* Select calibration to run */ 105185380Ssam switch (currCal->calData->calType) { 106185380Ssam case IQ_MISMATCH_CAL: 107185380Ssam OS_REG_WRITE(ah, AR_PHY_CALMODE, AR_PHY_CALMODE_IQ); 108185380Ssam HALDEBUG(ah, HAL_DEBUG_PERCAL, 109185380Ssam "%s: start IQ Mismatch calibration\n", __func__); 110185380Ssam break; 111185380Ssam case ADC_GAIN_CAL: 112185380Ssam OS_REG_WRITE(ah, AR_PHY_CALMODE, AR_PHY_CALMODE_ADC_GAIN); 113185380Ssam HALDEBUG(ah, HAL_DEBUG_PERCAL, 114185380Ssam "%s: start ADC Gain calibration\n", __func__); 115185380Ssam break; 116185380Ssam case ADC_DC_CAL: 117185380Ssam OS_REG_WRITE(ah, AR_PHY_CALMODE, AR_PHY_CALMODE_ADC_DC_PER); 118185380Ssam HALDEBUG(ah, HAL_DEBUG_PERCAL, 119185380Ssam "%s: start ADC DC calibration\n", __func__); 120185380Ssam break; 121185380Ssam case ADC_DC_INIT_CAL: 122185380Ssam OS_REG_WRITE(ah, AR_PHY_CALMODE, AR_PHY_CALMODE_ADC_DC_INIT); 123185380Ssam HALDEBUG(ah, HAL_DEBUG_PERCAL, 124185380Ssam "%s: start Init ADC DC calibration\n", __func__); 125185380Ssam break; 126185380Ssam } 127185380Ssam /* Kick-off cal */ 128185380Ssam OS_REG_SET_BIT(ah, AR_PHY_TIMING_CTRL4, AR_PHY_TIMING_CTRL4_DO_CAL); 129185380Ssam} 130185380Ssam 131185380Ssam/* 132185380Ssam * Initialize shared data structures and prepare a cal to be run. 133185380Ssam */ 134185380Ssamstatic void 135185380Ssamar5416ResetMeasurement(struct ath_hal *ah, HAL_CAL_LIST *currCal) 136185380Ssam{ 137185380Ssam struct ar5416PerCal *cal = &AH5416(ah)->ah_cal; 138185380Ssam 139185380Ssam /* Reset data structures shared between different calibrations */ 140185380Ssam OS_MEMZERO(cal->caldata, sizeof(cal->caldata)); 141185380Ssam cal->calSamples = 0; 142185380Ssam 143185380Ssam /* Setup HW for new calibration */ 144185380Ssam ar5416SetupMeasurement(ah, currCal); 145185380Ssam 146185380Ssam /* Change SW state to RUNNING for this calibration */ 147185380Ssam currCal->calState = CAL_RUNNING; 148185380Ssam} 149185380Ssam 150185380Ssam#if 0 151185380Ssam/* 152185380Ssam * Run non-periodic calibrations. 153185380Ssam */ 154185380Ssamstatic HAL_BOOL 155185380Ssamar5416RunInitCals(struct ath_hal *ah, int init_cal_count) 156185380Ssam{ 157185380Ssam struct ath_hal_5416 *ahp = AH5416(ah); 158185380Ssam struct ar5416PerCal *cal = &AH5416(ah)->ah_cal; 159185380Ssam HAL_CHANNEL_INTERNAL ichan; /* XXX bogus */ 160185380Ssam HAL_CAL_LIST *curCal = ahp->ah_cal_curr; 161185380Ssam HAL_BOOL isCalDone; 162185380Ssam int i; 163185380Ssam 164185380Ssam if (curCal == AH_NULL) 165185380Ssam return AH_FALSE; 166185380Ssam 167185380Ssam ichan.calValid = 0; 168185380Ssam for (i = 0; i < init_cal_count; i++) { 169185380Ssam /* Reset this Cal */ 170185380Ssam ar5416ResetMeasurement(ah, curCal); 171185380Ssam /* Poll for offset calibration complete */ 172185380Ssam if (!ath_hal_wait(ah, AR_PHY_TIMING_CTRL4, AR_PHY_TIMING_CTRL4_DO_CAL, 0)) { 173185380Ssam HALDEBUG(ah, HAL_DEBUG_ANY, 174185380Ssam "%s: Cal %d failed to finish in 100ms.\n", 175185380Ssam __func__, curCal->calData->calType); 176185380Ssam /* Re-initialize list pointers for periodic cals */ 177185380Ssam cal->cal_list = cal->cal_last = cal->cal_curr = AH_NULL; 178185380Ssam return AH_FALSE; 179185380Ssam } 180185380Ssam /* Run this cal */ 181185380Ssam ar5416DoCalibration(ah, &ichan, ahp->ah_rxchainmask, 182185380Ssam curCal, &isCalDone); 183185380Ssam if (!isCalDone) 184185380Ssam HALDEBUG(ah, HAL_DEBUG_ANY, 185185380Ssam "%s: init cal %d did not complete.\n", 186185380Ssam __func__, curCal->calData->calType); 187185380Ssam if (curCal->calNext != AH_NULL) 188185380Ssam curCal = curCal->calNext; 189185380Ssam } 190185380Ssam 191185380Ssam /* Re-initialize list pointers for periodic cals */ 192185380Ssam cal->cal_list = cal->cal_last = cal->cal_curr = AH_NULL; 193185380Ssam return AH_TRUE; 194185380Ssam} 195185380Ssam#endif 196185380Ssam 197224509Sadrian 198224509Sadrian/* 199224509Sadrian * AGC calibration for the AR5416, AR9130, AR9160, AR9280. 200224509Sadrian */ 201185380SsamHAL_BOOL 202219480Sadrianar5416InitCalHardware(struct ath_hal *ah, const struct ieee80211_channel *chan) 203185380Ssam{ 204224509Sadrian 205203882Srpaulo if (AR_SREV_MERLIN_10_OR_LATER(ah)) { 206224509Sadrian /* Disable ADC */ 207224509Sadrian OS_REG_CLR_BIT(ah, AR_PHY_ADC_CTL, 208224509Sadrian AR_PHY_ADC_CTL_OFF_PWDADC); 209203159Srpaulo 210185380Ssam /* Enable Rx Filter Cal */ 211185380Ssam OS_REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, 212185380Ssam AR_PHY_AGC_CONTROL_FLTR_CAL); 213185380Ssam } 214185380Ssam 215185380Ssam /* Calibrate the AGC */ 216185380Ssam OS_REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_CAL); 217185380Ssam 218185380Ssam /* Poll for offset calibration complete */ 219203882Srpaulo if (!ath_hal_wait(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_CAL, 0)) { 220185380Ssam HALDEBUG(ah, HAL_DEBUG_ANY, 221203882Srpaulo "%s: offset calibration did not complete in 1ms; " 222185380Ssam "noisy environment?\n", __func__); 223185380Ssam return AH_FALSE; 224185380Ssam } 225185380Ssam 226224509Sadrian if (AR_SREV_MERLIN_10_OR_LATER(ah)) { 227224509Sadrian /* Enable ADC */ 228224509Sadrian OS_REG_SET_BIT(ah, AR_PHY_ADC_CTL, 229224509Sadrian AR_PHY_ADC_CTL_OFF_PWDADC); 230224509Sadrian 231224509Sadrian /* Disable Rx Filter Cal */ 232224509Sadrian OS_REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, 233224509Sadrian AR_PHY_AGC_CONTROL_FLTR_CAL); 234224509Sadrian } 235224509Sadrian 236219480Sadrian return AH_TRUE; 237219480Sadrian} 238219480Sadrian 239219480Sadrian/* 240219480Sadrian * Initialize Calibration infrastructure. 241219480Sadrian */ 242219802Sadrian#define MAX_CAL_CHECK 32 243219480SadrianHAL_BOOL 244219480Sadrianar5416InitCal(struct ath_hal *ah, const struct ieee80211_channel *chan) 245219480Sadrian{ 246219480Sadrian struct ar5416PerCal *cal = &AH5416(ah)->ah_cal; 247219480Sadrian HAL_CHANNEL_INTERNAL *ichan; 248219480Sadrian 249219480Sadrian ichan = ath_hal_checkchannel(ah, chan); 250219480Sadrian HALASSERT(ichan != AH_NULL); 251219480Sadrian 252219480Sadrian /* Do initial chipset-specific calibration */ 253219480Sadrian if (! AH5416(ah)->ah_cal_initcal(ah, chan)) { 254224509Sadrian HALDEBUG(ah, HAL_DEBUG_ANY, 255224509Sadrian "%s: initial chipset calibration did " 256219480Sadrian "not complete in time; noisy environment?\n", __func__); 257219480Sadrian return AH_FALSE; 258219480Sadrian } 259219480Sadrian 260219480Sadrian /* If there's PA Cal, do it */ 261219480Sadrian if (AH5416(ah)->ah_cal_pacal) 262219480Sadrian AH5416(ah)->ah_cal_pacal(ah, AH_TRUE); 263219480Sadrian 264185380Ssam /* 265185380Ssam * Do NF calibration after DC offset and other CALs. 266185380Ssam * Per system engineers, noise floor value can sometimes be 20 dB 267185380Ssam * higher than normal value if DC offset and noise floor cal are 268185380Ssam * triggered at the same time. 269185380Ssam */ 270185380Ssam OS_REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NF); 271219802Sadrian 272219802Sadrian /* 273221773Sadrian * This may take a while to run; make sure subsequent 274221773Sadrian * calibration routines check that this has completed 275221773Sadrian * before reading the value and triggering a subsequent 276221773Sadrian * calibration. 277219802Sadrian */ 278211330Sadrian 279185380Ssam /* Initialize list pointers */ 280185380Ssam cal->cal_list = cal->cal_last = cal->cal_curr = AH_NULL; 281185380Ssam 282185380Ssam /* 283185380Ssam * Enable IQ, ADC Gain, ADC DC Offset Cals 284185380Ssam */ 285221868Sadrian if (AR_SREV_HOWL(ah) || AR_SREV_SOWL_10_OR_LATER(ah)) { 286185380Ssam /* Setup all non-periodic, init time only calibrations */ 287185380Ssam /* XXX: Init DC Offset not working yet */ 288185380Ssam#if 0 289185380Ssam if (ar5416IsCalSupp(ah, chan, ADC_DC_INIT_CAL)) { 290185380Ssam INIT_CAL(&cal->adcDcCalInitData); 291185380Ssam INSERT_CAL(cal, &cal->adcDcCalInitData); 292185380Ssam } 293185380Ssam /* Initialize current pointer to first element in list */ 294185380Ssam cal->cal_curr = cal->cal_list; 295185380Ssam 296185380Ssam if (cal->ah_cal_curr != AH_NULL && !ar5416RunInitCals(ah, 0)) 297185380Ssam return AH_FALSE; 298185380Ssam#endif 299185380Ssam } 300185380Ssam 301185380Ssam /* If Cals are supported, add them to list via INIT/INSERT_CAL */ 302185380Ssam if (ar5416IsCalSupp(ah, chan, ADC_GAIN_CAL)) { 303185380Ssam INIT_CAL(&cal->adcGainCalData); 304185380Ssam INSERT_CAL(cal, &cal->adcGainCalData); 305185380Ssam HALDEBUG(ah, HAL_DEBUG_PERCAL, 306185380Ssam "%s: enable ADC Gain Calibration.\n", __func__); 307185380Ssam } 308185380Ssam if (ar5416IsCalSupp(ah, chan, ADC_DC_CAL)) { 309185380Ssam INIT_CAL(&cal->adcDcCalData); 310185380Ssam INSERT_CAL(cal, &cal->adcDcCalData); 311185380Ssam HALDEBUG(ah, HAL_DEBUG_PERCAL, 312185380Ssam "%s: enable ADC DC Calibration.\n", __func__); 313185380Ssam } 314185380Ssam if (ar5416IsCalSupp(ah, chan, IQ_MISMATCH_CAL)) { 315185380Ssam INIT_CAL(&cal->iqCalData); 316185380Ssam INSERT_CAL(cal, &cal->iqCalData); 317185380Ssam HALDEBUG(ah, HAL_DEBUG_PERCAL, 318185380Ssam "%s: enable IQ Calibration.\n", __func__); 319185380Ssam } 320185380Ssam /* Initialize current pointer to first element in list */ 321185380Ssam cal->cal_curr = cal->cal_list; 322185380Ssam 323185380Ssam /* Kick off measurements for the first cal */ 324185380Ssam if (cal->cal_curr != AH_NULL) 325185380Ssam ar5416ResetMeasurement(ah, cal->cal_curr); 326185380Ssam 327185380Ssam /* Mark all calibrations on this channel as being invalid */ 328185380Ssam ichan->calValid = 0; 329185380Ssam 330185380Ssam return AH_TRUE; 331219802Sadrian#undef MAX_CAL_CHECK 332185380Ssam} 333185380Ssam 334185380Ssam/* 335185380Ssam * Entry point for upper layers to restart current cal. 336185380Ssam * Reset the calibration valid bit in channel. 337185380Ssam */ 338185380SsamHAL_BOOL 339187831Ssamar5416ResetCalValid(struct ath_hal *ah, const struct ieee80211_channel *chan) 340185380Ssam{ 341185380Ssam struct ar5416PerCal *cal = &AH5416(ah)->ah_cal; 342185380Ssam HAL_CHANNEL_INTERNAL *ichan = ath_hal_checkchannel(ah, chan); 343185380Ssam HAL_CAL_LIST *currCal = cal->cal_curr; 344185380Ssam 345185380Ssam if (!AR_SREV_SOWL_10_OR_LATER(ah)) 346185380Ssam return AH_FALSE; 347185380Ssam if (currCal == AH_NULL) 348185380Ssam return AH_FALSE; 349185380Ssam if (ichan == AH_NULL) { 350185380Ssam HALDEBUG(ah, HAL_DEBUG_ANY, 351185380Ssam "%s: invalid channel %u/0x%x; no mapping\n", 352187831Ssam __func__, chan->ic_freq, chan->ic_flags); 353185380Ssam return AH_FALSE; 354185380Ssam } 355185380Ssam /* 356185380Ssam * Expected that this calibration has run before, post-reset. 357185380Ssam * Current state should be done 358185380Ssam */ 359185380Ssam if (currCal->calState != CAL_DONE) { 360185380Ssam HALDEBUG(ah, HAL_DEBUG_ANY, 361185380Ssam "%s: Calibration state incorrect, %d\n", 362185380Ssam __func__, currCal->calState); 363185380Ssam return AH_FALSE; 364185380Ssam } 365185380Ssam 366185380Ssam /* Verify Cal is supported on this channel */ 367185380Ssam if (!ar5416IsCalSupp(ah, chan, currCal->calData->calType)) 368185380Ssam return AH_FALSE; 369185380Ssam 370185380Ssam HALDEBUG(ah, HAL_DEBUG_PERCAL, 371185380Ssam "%s: Resetting Cal %d state for channel %u/0x%x\n", 372187831Ssam __func__, currCal->calData->calType, chan->ic_freq, 373187831Ssam chan->ic_flags); 374185380Ssam 375185380Ssam /* Disable cal validity in channel */ 376185380Ssam ichan->calValid &= ~currCal->calData->calType; 377185380Ssam currCal->calState = CAL_WAITING; 378185380Ssam 379185380Ssam return AH_TRUE; 380185380Ssam} 381185380Ssam 382185380Ssam/* 383185380Ssam * Recalibrate the lower PHY chips to account for temperature/environment 384185380Ssam * changes. 385185380Ssam */ 386185380Ssamstatic void 387185380Ssamar5416DoCalibration(struct ath_hal *ah, HAL_CHANNEL_INTERNAL *ichan, 388185380Ssam uint8_t rxchainmask, HAL_CAL_LIST *currCal, HAL_BOOL *isCalDone) 389185380Ssam{ 390185380Ssam struct ar5416PerCal *cal = &AH5416(ah)->ah_cal; 391185380Ssam 392185380Ssam /* Cal is assumed not done until explicitly set below */ 393185380Ssam *isCalDone = AH_FALSE; 394185380Ssam 395185380Ssam HALDEBUG(ah, HAL_DEBUG_PERCAL, 396185380Ssam "%s: %s Calibration, state %d, calValid 0x%x\n", 397185380Ssam __func__, currCal->calData->calName, currCal->calState, 398185380Ssam ichan->calValid); 399185380Ssam 400185380Ssam /* Calibration in progress. */ 401185380Ssam if (currCal->calState == CAL_RUNNING) { 402185380Ssam /* Check to see if it has finished. */ 403185380Ssam if (!(OS_REG_READ(ah, AR_PHY_TIMING_CTRL4) & AR_PHY_TIMING_CTRL4_DO_CAL)) { 404185380Ssam HALDEBUG(ah, HAL_DEBUG_PERCAL, 405185380Ssam "%s: sample %d of %d finished\n", 406185380Ssam __func__, cal->calSamples, 407185380Ssam currCal->calData->calNumSamples); 408185380Ssam /* 409185380Ssam * Collect measurements for active chains. 410185380Ssam */ 411185380Ssam currCal->calData->calCollect(ah); 412185380Ssam if (++cal->calSamples >= currCal->calData->calNumSamples) { 413185380Ssam int i, numChains = 0; 414185380Ssam for (i = 0; i < AR5416_MAX_CHAINS; i++) { 415185380Ssam if (rxchainmask & (1 << i)) 416185380Ssam numChains++; 417185380Ssam } 418185380Ssam /* 419185380Ssam * Process accumulated data 420185380Ssam */ 421185380Ssam currCal->calData->calPostProc(ah, numChains); 422185380Ssam 423185380Ssam /* Calibration has finished. */ 424185380Ssam ichan->calValid |= currCal->calData->calType; 425185380Ssam currCal->calState = CAL_DONE; 426185380Ssam *isCalDone = AH_TRUE; 427185380Ssam } else { 428185380Ssam /* 429185380Ssam * Set-up to collect of another sub-sample. 430185380Ssam */ 431185380Ssam ar5416SetupMeasurement(ah, currCal); 432185380Ssam } 433185380Ssam } 434185380Ssam } else if (!(ichan->calValid & currCal->calData->calType)) { 435185380Ssam /* If current cal is marked invalid in channel, kick it off */ 436185380Ssam ar5416ResetMeasurement(ah, currCal); 437185380Ssam } 438185380Ssam} 439185380Ssam 440185380Ssam/* 441185380Ssam * Internal interface to schedule periodic calibration work. 442185380Ssam */ 443185380SsamHAL_BOOL 444187831Ssamar5416PerCalibrationN(struct ath_hal *ah, struct ieee80211_channel *chan, 445185380Ssam u_int rxchainmask, HAL_BOOL longcal, HAL_BOOL *isCalDone) 446185380Ssam{ 447185380Ssam struct ar5416PerCal *cal = &AH5416(ah)->ah_cal; 448185380Ssam HAL_CAL_LIST *currCal = cal->cal_curr; 449185380Ssam HAL_CHANNEL_INTERNAL *ichan; 450221779Sadrian int r; 451185380Ssam 452188084Ssam OS_MARK(ah, AH_MARK_PERCAL, chan->ic_freq); 453185380Ssam 454185380Ssam *isCalDone = AH_TRUE; 455185380Ssam 456211208Sadrian /* 457211208Sadrian * Since ath_hal calls the PerCal method with rxchainmask=0x1; 458211208Sadrian * override it with the current chainmask. The upper levels currently 459211208Sadrian * doesn't know about the chainmask. 460211208Sadrian */ 461211208Sadrian rxchainmask = AH5416(ah)->ah_rx_chainmask; 462211208Sadrian 463185380Ssam /* Invalid channel check */ 464185380Ssam ichan = ath_hal_checkchannel(ah, chan); 465185380Ssam if (ichan == AH_NULL) { 466185380Ssam HALDEBUG(ah, HAL_DEBUG_ANY, 467185380Ssam "%s: invalid channel %u/0x%x; no mapping\n", 468187831Ssam __func__, chan->ic_freq, chan->ic_flags); 469185380Ssam return AH_FALSE; 470185380Ssam } 471185380Ssam 472185380Ssam /* 473185380Ssam * For given calibration: 474185380Ssam * 1. Call generic cal routine 475185380Ssam * 2. When this cal is done (isCalDone) if we have more cals waiting 476185380Ssam * (eg after reset), mask this to upper layers by not propagating 477185380Ssam * isCalDone if it is set to TRUE. 478185380Ssam * Instead, change isCalDone to FALSE and setup the waiting cal(s) 479185380Ssam * to be run. 480185380Ssam */ 481185380Ssam if (currCal != AH_NULL && 482185380Ssam (currCal->calState == CAL_RUNNING || 483185380Ssam currCal->calState == CAL_WAITING)) { 484185380Ssam ar5416DoCalibration(ah, ichan, rxchainmask, currCal, isCalDone); 485185380Ssam if (*isCalDone == AH_TRUE) { 486185380Ssam cal->cal_curr = currCal = currCal->calNext; 487185380Ssam if (currCal->calState == CAL_WAITING) { 488185380Ssam *isCalDone = AH_FALSE; 489185380Ssam ar5416ResetMeasurement(ah, currCal); 490185380Ssam } 491185380Ssam } 492185380Ssam } 493185380Ssam 494185380Ssam /* Do NF cal only at longer intervals */ 495185380Ssam if (longcal) { 496219480Sadrian /* Do PA calibration if the chipset supports */ 497219480Sadrian if (AH5416(ah)->ah_cal_pacal) 498219480Sadrian AH5416(ah)->ah_cal_pacal(ah, AH_FALSE); 499219480Sadrian 500221837Sadrian /* Do open-loop temperature compensation if the chipset needs it */ 501221837Sadrian if (ath_hal_eepromGetFlag(ah, AR_EEP_OL_PWRCTRL)) 502221837Sadrian AH5416(ah)->ah_olcTempCompensation(ah); 503219393Sadrian 504185380Ssam /* 505185380Ssam * Get the value from the previous NF cal 506185380Ssam * and update the history buffer. 507185380Ssam */ 508221779Sadrian r = ar5416GetNf(ah, chan); 509221944Sadrian if (r == 0 || r == -1) { 510221779Sadrian /* NF calibration result isn't valid */ 511221779Sadrian HALDEBUG(ah, HAL_DEBUG_UNMASKABLE, "%s: NF calibration" 512221779Sadrian " didn't finish; delaying CCA\n", __func__); 513221779Sadrian } else { 514221779Sadrian /* 515221779Sadrian * NF calibration result is valid. 516221779Sadrian * 517221779Sadrian * Load the NF from history buffer of the current channel. 518221779Sadrian * NF is slow time-variant, so it is OK to use a 519221779Sadrian * historical value. 520221779Sadrian */ 521221779Sadrian ar5416LoadNF(ah, AH_PRIVATE(ah)->ah_curchan); 522185380Ssam 523221779Sadrian /* start NF calibration, without updating BB NF register*/ 524221779Sadrian ar5416StartNFCal(ah); 525221779Sadrian } 526185380Ssam } 527185380Ssam return AH_TRUE; 528185380Ssam} 529185380Ssam 530185380Ssam/* 531185380Ssam * Recalibrate the lower PHY chips to account for temperature/environment 532185380Ssam * changes. 533185380Ssam */ 534185380SsamHAL_BOOL 535187831Ssamar5416PerCalibration(struct ath_hal *ah, struct ieee80211_channel *chan, 536187831Ssam HAL_BOOL *isIQdone) 537185380Ssam{ 538185380Ssam struct ath_hal_5416 *ahp = AH5416(ah); 539185380Ssam struct ar5416PerCal *cal = &AH5416(ah)->ah_cal; 540185380Ssam HAL_CAL_LIST *curCal = cal->cal_curr; 541185380Ssam 542185380Ssam if (curCal != AH_NULL && curCal->calData->calType == IQ_MISMATCH_CAL) { 543185380Ssam return ar5416PerCalibrationN(ah, chan, ahp->ah_rx_chainmask, 544185380Ssam AH_TRUE, isIQdone); 545185380Ssam } else { 546185380Ssam HAL_BOOL isCalDone; 547185380Ssam 548185380Ssam *isIQdone = AH_FALSE; 549185380Ssam return ar5416PerCalibrationN(ah, chan, ahp->ah_rx_chainmask, 550185380Ssam AH_TRUE, &isCalDone); 551185380Ssam } 552185380Ssam} 553185380Ssam 554185380Ssamstatic HAL_BOOL 555185380Ssamar5416GetEepromNoiseFloorThresh(struct ath_hal *ah, 556187831Ssam const struct ieee80211_channel *chan, int16_t *nft) 557185380Ssam{ 558187831Ssam if (IEEE80211_IS_CHAN_5GHZ(chan)) { 559185380Ssam ath_hal_eepromGet(ah, AR_EEP_NFTHRESH_5, nft); 560187831Ssam return AH_TRUE; 561187831Ssam } 562187831Ssam if (IEEE80211_IS_CHAN_2GHZ(chan)) { 563185380Ssam ath_hal_eepromGet(ah, AR_EEP_NFTHRESH_2, nft); 564187831Ssam return AH_TRUE; 565185380Ssam } 566187831Ssam HALDEBUG(ah, HAL_DEBUG_ANY, "%s: invalid channel flags 0x%x\n", 567187831Ssam __func__, chan->ic_flags); 568187831Ssam return AH_FALSE; 569185380Ssam} 570185380Ssam 571185380Ssamstatic void 572185380Ssamar5416StartNFCal(struct ath_hal *ah) 573185380Ssam{ 574185380Ssam OS_REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_ENABLE_NF); 575185380Ssam OS_REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NO_UPDATE_NF); 576185380Ssam OS_REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NF); 577185380Ssam} 578185380Ssam 579185380Ssamstatic void 580187831Ssamar5416LoadNF(struct ath_hal *ah, const struct ieee80211_channel *chan) 581185380Ssam{ 582185380Ssam static const uint32_t ar5416_cca_regs[] = { 583185380Ssam AR_PHY_CCA, 584185380Ssam AR_PHY_CH1_CCA, 585185380Ssam AR_PHY_CH2_CCA, 586185380Ssam AR_PHY_EXT_CCA, 587185380Ssam AR_PHY_CH1_EXT_CCA, 588185380Ssam AR_PHY_CH2_EXT_CCA 589185380Ssam }; 590185380Ssam struct ar5212NfCalHist *h; 591211211Sadrian int i; 592185380Ssam int32_t val; 593185380Ssam uint8_t chainmask; 594218068Sadrian int16_t default_nf = ar5416GetDefaultNF(ah, chan); 595185380Ssam 596185380Ssam /* 597185380Ssam * Force NF calibration for all chains. 598185380Ssam */ 599185380Ssam if (AR_SREV_KITE(ah)) { 600185380Ssam /* Kite has only one chain */ 601185380Ssam chainmask = 0x9; 602222315Sadrian } else if (AR_SREV_MERLIN(ah) || AR_SREV_KIWI(ah)) { 603222315Sadrian /* Merlin/Kiwi has only two chains */ 604185380Ssam chainmask = 0x1B; 605185380Ssam } else { 606185380Ssam chainmask = 0x3F; 607185380Ssam } 608185380Ssam 609185380Ssam /* 610185380Ssam * Write filtered NF values into maxCCApwr register parameter 611185380Ssam * so we can load below. 612185380Ssam */ 613185380Ssam h = AH5416(ah)->ah_cal.nfCalHist; 614218068Sadrian HALDEBUG(ah, HAL_DEBUG_NFCAL, "CCA: "); 615218068Sadrian for (i = 0; i < AR5416_NUM_NF_READINGS; i ++) { 616218069Sadrian 617221488Sadrian /* Don't write to EXT radio CCA registers unless in HT/40 mode */ 618218069Sadrian /* XXX this check should really be cleaner! */ 619221488Sadrian if (i > 2 && !IEEE80211_IS_CHAN_HT40(chan)) 620218069Sadrian continue; 621218069Sadrian 622185380Ssam if (chainmask & (1 << i)) { 623218068Sadrian int16_t nf_val; 624218068Sadrian 625218068Sadrian if (h) 626218068Sadrian nf_val = h[i].privNF; 627218068Sadrian else 628218068Sadrian nf_val = default_nf; 629218068Sadrian 630185380Ssam val = OS_REG_READ(ah, ar5416_cca_regs[i]); 631185380Ssam val &= 0xFFFFFE00; 632218068Sadrian val |= (((uint32_t) nf_val << 1) & 0x1ff); 633218068Sadrian HALDEBUG(ah, HAL_DEBUG_NFCAL, "[%d: %d]", i, nf_val); 634185380Ssam OS_REG_WRITE(ah, ar5416_cca_regs[i], val); 635185380Ssam } 636218068Sadrian } 637218068Sadrian HALDEBUG(ah, HAL_DEBUG_NFCAL, "\n"); 638185380Ssam 639185380Ssam /* Load software filtered NF value into baseband internal minCCApwr variable. */ 640185380Ssam OS_REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_ENABLE_NF); 641185380Ssam OS_REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NO_UPDATE_NF); 642185380Ssam OS_REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NF); 643185380Ssam 644211214Sadrian /* Wait for load to complete, should be fast, a few 10s of us. */ 645211211Sadrian if (! ar5212WaitNFCalComplete(ah, 1000)) { 646211211Sadrian /* 647211211Sadrian * We timed out waiting for the noisefloor to load, probably due to an 648211211Sadrian * in-progress rx. Simply return here and allow the load plenty of time 649211211Sadrian * to complete before the next calibration interval. We need to avoid 650211211Sadrian * trying to load -50 (which happens below) while the previous load is 651211211Sadrian * still in progress as this can cause rx deafness. Instead by returning 652211211Sadrian * here, the baseband nf cal will just be capped by our present 653211211Sadrian * noisefloor until the next calibration timer. 654211211Sadrian */ 655221778Sadrian HALDEBUG(ah, HAL_DEBUG_UNMASKABLE, "Timeout while waiting for " 656221778Sadrian "nf to load: AR_PHY_AGC_CONTROL=0x%x\n", 657211211Sadrian OS_REG_READ(ah, AR_PHY_AGC_CONTROL)); 658211214Sadrian return; 659185380Ssam } 660185380Ssam 661185380Ssam /* 662185380Ssam * Restore maxCCAPower register parameter again so that we're not capped 663185380Ssam * by the median we just loaded. This will be initial (and max) value 664185380Ssam * of next noise floor calibration the baseband does. 665185380Ssam */ 666185380Ssam for (i = 0; i < AR5416_NUM_NF_READINGS; i ++) 667221488Sadrian 668221488Sadrian /* Don't write to EXT radio CCA registers unless in HT/40 mode */ 669221488Sadrian /* XXX this check should really be cleaner! */ 670221488Sadrian if (i > 2 && !IEEE80211_IS_CHAN_HT40(chan)) 671221488Sadrian continue; 672221488Sadrian 673185380Ssam if (chainmask & (1 << i)) { 674185380Ssam val = OS_REG_READ(ah, ar5416_cca_regs[i]); 675185380Ssam val &= 0xFFFFFE00; 676185380Ssam val |= (((uint32_t)(-50) << 1) & 0x1ff); 677185380Ssam OS_REG_WRITE(ah, ar5416_cca_regs[i], val); 678185380Ssam } 679185380Ssam} 680185380Ssam 681218068Sadrian/* 682218068Sadrian * This just initialises the "good" values for AR5416 which 683218068Sadrian * may not be right; it'lll be overridden by ar5416SanitizeNF() 684218068Sadrian * to nominal values. 685218068Sadrian */ 686185380Ssamvoid 687203882Srpauloar5416InitNfHistBuff(struct ar5212NfCalHist *h) 688185380Ssam{ 689185380Ssam int i, j; 690185380Ssam 691185380Ssam for (i = 0; i < AR5416_NUM_NF_READINGS; i ++) { 692185380Ssam h[i].currIndex = 0; 693185380Ssam h[i].privNF = AR5416_CCA_MAX_GOOD_VALUE; 694185380Ssam h[i].invalidNFcount = AR512_NF_CAL_HIST_MAX; 695185380Ssam for (j = 0; j < AR512_NF_CAL_HIST_MAX; j ++) 696185380Ssam h[i].nfCalBuffer[j] = AR5416_CCA_MAX_GOOD_VALUE; 697185380Ssam } 698185380Ssam} 699185380Ssam 700185380Ssam/* 701185380Ssam * Update the noise floor buffer as a ring buffer 702185380Ssam */ 703185380Ssamstatic void 704221488Sadrianar5416UpdateNFHistBuff(struct ath_hal *ah, struct ar5212NfCalHist *h, 705221488Sadrian int16_t *nfarray) 706185380Ssam{ 707185380Ssam int i; 708185380Ssam 709221488Sadrian /* XXX TODO: don't record nfarray[] entries for inactive chains */ 710185380Ssam for (i = 0; i < AR5416_NUM_NF_READINGS; i ++) { 711185380Ssam h[i].nfCalBuffer[h[i].currIndex] = nfarray[i]; 712185380Ssam 713185380Ssam if (++h[i].currIndex >= AR512_NF_CAL_HIST_MAX) 714185380Ssam h[i].currIndex = 0; 715185380Ssam if (h[i].invalidNFcount > 0) { 716185380Ssam if (nfarray[i] < AR5416_CCA_MIN_BAD_VALUE || 717185380Ssam nfarray[i] > AR5416_CCA_MAX_HIGH_VALUE) { 718185380Ssam h[i].invalidNFcount = AR512_NF_CAL_HIST_MAX; 719185380Ssam } else { 720185380Ssam h[i].invalidNFcount--; 721185380Ssam h[i].privNF = nfarray[i]; 722185380Ssam } 723185380Ssam } else { 724185380Ssam h[i].privNF = ar5212GetNfHistMid(h[i].nfCalBuffer); 725185380Ssam } 726185380Ssam } 727185380Ssam} 728185380Ssam 729218068Sadrianstatic uint16_t 730218068Sadrianar5416GetDefaultNF(struct ath_hal *ah, const struct ieee80211_channel *chan) 731218068Sadrian{ 732218068Sadrian struct ar5416NfLimits *limit; 733218068Sadrian 734218068Sadrian if (!chan || IEEE80211_IS_CHAN_2GHZ(chan)) 735218068Sadrian limit = &AH5416(ah)->nf_2g; 736218068Sadrian else 737218068Sadrian limit = &AH5416(ah)->nf_5g; 738218068Sadrian 739218068Sadrian return limit->nominal; 740218068Sadrian} 741218068Sadrian 742218068Sadrianstatic void 743218068Sadrianar5416SanitizeNF(struct ath_hal *ah, int16_t *nf) 744218068Sadrian{ 745218068Sadrian 746218068Sadrian struct ar5416NfLimits *limit; 747218068Sadrian int i; 748218068Sadrian 749218068Sadrian if (IEEE80211_IS_CHAN_2GHZ(AH_PRIVATE(ah)->ah_curchan)) 750218068Sadrian limit = &AH5416(ah)->nf_2g; 751218068Sadrian else 752218068Sadrian limit = &AH5416(ah)->nf_5g; 753218068Sadrian 754218068Sadrian for (i = 0; i < AR5416_NUM_NF_READINGS; i++) { 755218068Sadrian if (!nf[i]) 756218068Sadrian continue; 757218068Sadrian 758218068Sadrian if (nf[i] > limit->max) { 759218068Sadrian HALDEBUG(ah, HAL_DEBUG_NFCAL, 760218068Sadrian "NF[%d] (%d) > MAX (%d), correcting to MAX\n", 761218068Sadrian i, nf[i], limit->max); 762218068Sadrian nf[i] = limit->max; 763218068Sadrian } else if (nf[i] < limit->min) { 764218068Sadrian HALDEBUG(ah, HAL_DEBUG_NFCAL, 765218068Sadrian "NF[%d] (%d) < MIN (%d), correcting to NOM\n", 766218068Sadrian i, nf[i], limit->min); 767218068Sadrian nf[i] = limit->nominal; 768218068Sadrian } 769218068Sadrian } 770218068Sadrian} 771218068Sadrian 772218068Sadrian 773185380Ssam/* 774185380Ssam * Read the NF and check it against the noise floor threshhold 775221779Sadrian * 776221779Sadrian * Return 0 if the NF calibration hadn't finished, 0 if it was 777221779Sadrian * invalid, or > 0 for a valid NF reading. 778185380Ssam */ 779185380Ssamstatic int16_t 780187831Ssamar5416GetNf(struct ath_hal *ah, struct ieee80211_channel *chan) 781185380Ssam{ 782185380Ssam int16_t nf, nfThresh; 783220442Sadrian int i; 784221779Sadrian int retval = 0; 785185380Ssam 786211210Sadrian if (ar5212IsNFCalInProgress(ah)) { 787185380Ssam HALDEBUG(ah, HAL_DEBUG_ANY, 788185380Ssam "%s: NF didn't complete in calibration window\n", __func__); 789185380Ssam nf = 0; 790221779Sadrian retval = -1; /* NF didn't finish */ 791185380Ssam } else { 792185380Ssam /* Finished NF cal, check against threshold */ 793185380Ssam int16_t nfarray[NUM_NOISEFLOOR_READINGS] = { 0 }; 794187831Ssam HAL_CHANNEL_INTERNAL *ichan = ath_hal_checkchannel(ah, chan); 795185380Ssam 796185380Ssam /* TODO - enhance for multiple chains and ext ch */ 797185380Ssam ath_hal_getNoiseFloor(ah, nfarray); 798185380Ssam nf = nfarray[0]; 799218068Sadrian ar5416SanitizeNF(ah, nfarray); 800185380Ssam if (ar5416GetEepromNoiseFloorThresh(ah, chan, &nfThresh)) { 801185380Ssam if (nf > nfThresh) { 802221779Sadrian HALDEBUG(ah, HAL_DEBUG_UNMASKABLE, 803185380Ssam "%s: noise floor failed detected; " 804185380Ssam "detected %d, threshold %d\n", __func__, 805185380Ssam nf, nfThresh); 806185380Ssam /* 807185380Ssam * NB: Don't discriminate 2.4 vs 5Ghz, if this 808185380Ssam * happens it indicates a problem regardless 809185380Ssam * of the band. 810185380Ssam */ 811187831Ssam chan->ic_state |= IEEE80211_CHANSTATE_CWINT; 812185380Ssam nf = 0; 813221779Sadrian retval = 0; 814185380Ssam } 815185380Ssam } else { 816185380Ssam nf = 0; 817221779Sadrian retval = 0; 818185380Ssam } 819220442Sadrian /* Update MIMO channel statistics, regardless of validity or not (for now) */ 820220442Sadrian for (i = 0; i < 3; i++) { 821220442Sadrian ichan->noiseFloorCtl[i] = nfarray[i]; 822220442Sadrian ichan->noiseFloorExt[i] = nfarray[i + 3]; 823220442Sadrian } 824220442Sadrian ichan->privFlags |= CHANNEL_MIMO_NF_VALID; 825220442Sadrian 826221488Sadrian ar5416UpdateNFHistBuff(ah, AH5416(ah)->ah_cal.nfCalHist, nfarray); 827187831Ssam ichan->rawNoiseFloor = nf; 828221779Sadrian retval = nf; 829185380Ssam } 830221779Sadrian return retval; 831185380Ssam} 832