/*
* Copyright (c) 2008-2010 Atheros Communications 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.
*/
#include "hw.h"
#include "hw-ops.h"
#include "ar9002_phy.h"
#define AR9285_CLCAL_REDO_THRESH 1
static void ar9002_hw_setup_calibration(struct ath_hw *ah,
struct ath9k_cal_list *currCal)
{
struct ath_common *common = ath9k_hw_common(ah);
REG_RMW_FIELD(ah, AR_PHY_TIMING_CTRL4(0),
AR_PHY_TIMING_CTRL4_IQCAL_LOG_COUNT_MAX,
currCal->calData->calCountMax);
switch (currCal->calData->calType) {
case IQ_MISMATCH_CAL:
REG_WRITE(ah, AR_PHY_CALMODE, AR_PHY_CALMODE_IQ);
ath_print(common, ATH_DBG_CALIBRATE,
"starting IQ Mismatch Calibration\n");
break;
case ADC_GAIN_CAL:
REG_WRITE(ah, AR_PHY_CALMODE, AR_PHY_CALMODE_ADC_GAIN);
ath_print(common, ATH_DBG_CALIBRATE,
"starting ADC Gain Calibration\n");
break;
case ADC_DC_CAL:
REG_WRITE(ah, AR_PHY_CALMODE, AR_PHY_CALMODE_ADC_DC_PER);
ath_print(common, ATH_DBG_CALIBRATE,
"starting ADC DC Calibration\n");
break;
case ADC_DC_INIT_CAL:
REG_WRITE(ah, AR_PHY_CALMODE, AR_PHY_CALMODE_ADC_DC_INIT);
ath_print(common, ATH_DBG_CALIBRATE,
"starting Init ADC DC Calibration\n");
break;
case TEMP_COMP_CAL:
break; /* Not supported */
}
REG_SET_BIT(ah, AR_PHY_TIMING_CTRL4(0),
AR_PHY_TIMING_CTRL4_DO_CAL);
}
static bool ar9002_hw_per_calibration(struct ath_hw *ah,
struct ath9k_channel *ichan,
u8 rxchainmask,
struct ath9k_cal_list *currCal)
{
struct ath9k_hw_cal_data *caldata = ah->caldata;
bool iscaldone = false;
if (currCal->calState == CAL_RUNNING) {
if (!(REG_READ(ah, AR_PHY_TIMING_CTRL4(0)) &
AR_PHY_TIMING_CTRL4_DO_CAL)) {
currCal->calData->calCollect(ah);
ah->cal_samples++;
if (ah->cal_samples >=
currCal->calData->calNumSamples) {
int i, numChains = 0;
for (i = 0; i < AR5416_MAX_CHAINS; i++) {
if (rxchainmask & (1 << i))
numChains++;
}
currCal->calData->calPostProc(ah, numChains);
caldata->CalValid |= currCal->calData->calType;
currCal->calState = CAL_DONE;
iscaldone = true;
} else {
ar9002_hw_setup_calibration(ah, currCal);
}
}
} else if (!(caldata->CalValid & currCal->calData->calType)) {
ath9k_hw_reset_calibration(ah, currCal);
}
return iscaldone;
}
/* Assumes you are talking about the currently configured channel */
static bool ar9002_hw_iscal_supported(struct ath_hw *ah,
enum ath9k_cal_types calType)
{
struct ieee80211_conf *conf = &ath9k_hw_common(ah)->hw->conf;
switch (calType & ah->supp_cals) {
case IQ_MISMATCH_CAL: /* Both 2 GHz and 5 GHz support OFDM */
return true;
case ADC_GAIN_CAL:
case ADC_DC_CAL:
if (!(conf->channel->band == IEEE80211_BAND_2GHZ &&
conf_is_ht20(conf)))
return true;
break;
}
return false;
}
static void ar9002_hw_iqcal_collect(struct ath_hw *ah)
{
int i;
for (i = 0; i < AR5416_MAX_CHAINS; i++) {
ah->totalPowerMeasI[i] +=
REG_READ(ah, AR_PHY_CAL_MEAS_0(i));
ah->totalPowerMeasQ[i] +=
REG_READ(ah, AR_PHY_CAL_MEAS_1(i));
ah->totalIqCorrMeas[i] +=
(int32_t) REG_READ(ah, AR_PHY_CAL_MEAS_2(i));
ath_print(ath9k_hw_common(ah), ATH_DBG_CALIBRATE,
"%d: Chn %d pmi=0x%08x;pmq=0x%08x;iqcm=0x%08x;\n",
ah->cal_samples, i, ah->totalPowerMeasI[i],
ah