root/drivers/net/wireless/ath/ath9k/link.c
/*
 * Copyright (c) 2012 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.
 */

#include "ath9k.h"

/*
 * TX polling - checks if the TX engine is stuck somewhere
 * and issues a chip reset if so.
 */
static bool ath_tx_complete_check(struct ath_softc *sc)
{
        struct ath_txq *txq;
        int i;

        if (sc->tx99_state)
                return true;

        for (i = 0; i < IEEE80211_NUM_ACS; i++) {
                txq = sc->tx.txq_map[i];

                ath_txq_lock(sc, txq);
                if (txq->axq_depth) {
                        if (txq->axq_tx_inprogress) {
                                ath_txq_unlock(sc, txq);
                                goto reset;
                        }

                        txq->axq_tx_inprogress = true;
                }
                ath_txq_unlock(sc, txq);
        }

        return true;

reset:
        ath_dbg(ath9k_hw_common(sc->sc_ah), RESET,
                "tx hung, resetting the chip\n");
        ath9k_queue_reset(sc, RESET_TYPE_TX_HANG);
        return false;
}

#define RX_INACTIVE_CHECK_INTERVAL (4 * MSEC_PER_SEC)

static bool ath_hw_rx_inactive_check(struct ath_softc *sc)
{
        struct ath_common *common = ath9k_hw_common(sc->sc_ah);
        u32 interval, count;

        interval = jiffies_to_msecs(jiffies - sc->rx_active_check_time);
        count = sc->rx_active_count;

        if (interval < RX_INACTIVE_CHECK_INTERVAL)
                return true; /* too soon to check */

        sc->rx_active_count = 0;
        sc->rx_active_check_time = jiffies;

        /* Need at least one interrupt per second, and we should only react if
         * we are within a factor two of the expected interval
         */
        if (interval > RX_INACTIVE_CHECK_INTERVAL * 2 ||
            count >= interval / MSEC_PER_SEC)
                return true;

        ath_dbg(common, RESET,
                "RX inactivity detected. Schedule chip reset\n");
        ath9k_queue_reset(sc, RESET_TYPE_RX_INACTIVE);

        return false;
}

void ath_hw_check_work(struct work_struct *work)
{
        struct ath_softc *sc = container_of(work, struct ath_softc,
                                            hw_check_work.work);

        if (!ath_hw_check(sc) || !ath_tx_complete_check(sc) ||
            !ath_hw_rx_inactive_check(sc))
                return;

        ieee80211_queue_delayed_work(sc->hw, &sc->hw_check_work,
                                     msecs_to_jiffies(ATH_HW_CHECK_POLL_INT));
}

/*
 * Checks if the BB/MAC is hung.
 */
bool ath_hw_check(struct ath_softc *sc)
{
        struct ath_common *common = ath9k_hw_common(sc->sc_ah);
        enum ath_reset_type type;
        bool is_alive;

        ath9k_ps_wakeup(sc);

        is_alive = ath9k_hw_check_alive(sc->sc_ah);

        if (!is_alive) {
                ath_dbg(common, RESET,
                        "HW hang detected, schedule chip reset\n");
                type = RESET_TYPE_MAC_HANG;
                ath9k_queue_reset(sc, type);
        }

        ath9k_ps_restore(sc);

        return is_alive;
}

/*
 * PLL-WAR for AR9485/AR9340
 */
static bool ath_hw_pll_rx_hang_check(struct ath_softc *sc, u32 pll_sqsum)
{
        static int count;
        struct ath_common *common = ath9k_hw_common(sc->sc_ah);

        if (pll_sqsum >= 0x40000) {
                count++;
                if (count == 3) {
                        ath_dbg(common, RESET, "PLL WAR, resetting the chip\n");
                        ath9k_queue_reset(sc, RESET_TYPE_PLL_HANG);
                        count = 0;
                        return true;
                }
        } else {
                count = 0;
        }

        return false;
}

void ath_hw_pll_work(struct work_struct *work)
{
        u32 pll_sqsum;
        struct ath_softc *sc = container_of(work, struct ath_softc,
                                            hw_pll_work.work);
        struct ath_common *common = ath9k_hw_common(sc->sc_ah);
        /*
         * ensure that the PLL WAR is executed only
         * after the STA is associated (or) if the
         * beaconing had started in interfaces that
         * uses beacons.
         */
        if (!test_bit(ATH_OP_BEACONS, &common->op_flags))
                return;

        if (sc->tx99_state)
                return;

        ath9k_ps_wakeup(sc);
        pll_sqsum = ar9003_get_pll_sqsum_dvc(sc->sc_ah);
        ath9k_ps_restore(sc);
        if (ath_hw_pll_rx_hang_check(sc, pll_sqsum))
                return;

        ieee80211_queue_delayed_work(sc->hw, &sc->hw_pll_work,
                                     msecs_to_jiffies(ATH_PLL_WORK_INTERVAL));
}

/*
 * PA Pre-distortion.
 */
static void ath_paprd_activate(struct ath_softc *sc)
{
        struct ath_hw *ah = sc->sc_ah;
        struct ath_common *common = ath9k_hw_common(ah);
        struct ath9k_hw_cal_data *caldata = ah->caldata;
        int chain;

        if (!caldata || !test_bit(PAPRD_DONE, &caldata->cal_flags)) {
                ath_dbg(common, CALIBRATE, "Failed to activate PAPRD\n");
                return;
        }

        ar9003_paprd_enable(ah, false);
        for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) {
                if (!(ah->txchainmask & BIT(chain)))
                        continue;

                ar9003_paprd_populate_single_table(ah, caldata, chain);
        }

        ath_dbg(common, CALIBRATE, "Activating PAPRD\n");
        ar9003_paprd_enable(ah, true);
}

static bool ath_paprd_send_frame(struct ath_softc *sc, struct sk_buff *skb, int chain)
{
        struct ieee80211_hw *hw = sc->hw;
        struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
        struct ath_hw *ah = sc->sc_ah;
        struct ath_common *common = ath9k_hw_common(ah);
        struct ath_tx_control txctl;
        unsigned long time_left;

        memset(&txctl, 0, sizeof(txctl));
        txctl.txq = sc->tx.txq_map[IEEE80211_AC_BE];

        memset(tx_info, 0, sizeof(*tx_info));
        tx_info->band = sc->cur_chandef.chan->band;
        tx_info->flags |= IEEE80211_TX_CTL_NO_ACK;
        tx_info->control.rates[0].idx = 0;
        tx_info->control.rates[0].count = 1;
        tx_info->control.rates[0].flags = IEEE80211_TX_RC_MCS;
        tx_info->control.rates[1].idx = -1;

        init_completion(&sc->paprd_complete);
        txctl.paprd = BIT(chain);

        if (ath_tx_start(hw, skb, &txctl) != 0) {
                ath_dbg(common, CALIBRATE, "PAPRD TX failed\n");
                dev_kfree_skb_any(skb);
                return false;
        }

        time_left = wait_for_completion_timeout(&sc->paprd_complete,
                        msecs_to_jiffies(ATH_PAPRD_TIMEOUT));

        if (!time_left)
                ath_dbg(common, CALIBRATE,
                        "Timeout waiting for paprd training on TX chain %d\n",
                        chain);

        return !!time_left;
}

void ath_paprd_calibrate(struct work_struct *work)
{
        struct ath_softc *sc = container_of(work, struct ath_softc, paprd_work);
        struct ieee80211_hw *hw = sc->hw;
        struct ath_hw *ah = sc->sc_ah;
        struct ieee80211_hdr *hdr;
        struct sk_buff *skb = NULL;
        struct ath9k_hw_cal_data *caldata = ah->caldata;
        struct ath_common *common = ath9k_hw_common(ah);
        int ftype;
        int chain_ok = 0;
        int chain;
        int len = 1800;
        int ret;

        if (!caldata ||
            !test_bit(PAPRD_PACKET_SENT, &caldata->cal_flags) ||
            test_bit(PAPRD_DONE, &caldata->cal_flags)) {
                ath_dbg(common, CALIBRATE, "Skipping PAPRD calibration\n");
                return;
        }

        ath9k_ps_wakeup(sc);

        if (ar9003_paprd_init_table(ah) < 0)
                goto fail_paprd;

        skb = alloc_skb(len, GFP_KERNEL);
        if (!skb)
                goto fail_paprd;

        skb_put(skb, len);
        memset(skb->data, 0, len);
        hdr = (struct ieee80211_hdr *)skb->data;
        ftype = IEEE80211_FTYPE_DATA | IEEE80211_STYPE_NULLFUNC;
        hdr->frame_control = cpu_to_le16(ftype);
        hdr->duration_id = cpu_to_le16(10);
        memcpy(hdr->addr1, hw->wiphy->perm_addr, ETH_ALEN);
        memcpy(hdr->addr2, hw->wiphy->perm_addr, ETH_ALEN);
        memcpy(hdr->addr3, hw->wiphy->perm_addr, ETH_ALEN);

        for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) {
                if (!(ah->txchainmask & BIT(chain)))
                        continue;

                chain_ok = 0;
                ar9003_paprd_setup_gain_table(ah, chain);

                ath_dbg(common, CALIBRATE,
                        "Sending PAPRD training frame on chain %d\n", chain);
                if (!ath_paprd_send_frame(sc, skb, chain))
                        goto fail_paprd;

                if (!ar9003_paprd_is_done(ah)) {
                        ath_dbg(common, CALIBRATE,
                                "PAPRD not yet done on chain %d\n", chain);
                        break;
                }

                ret = ar9003_paprd_create_curve(ah, caldata, chain);
                if (ret == -EINPROGRESS) {
                        ath_dbg(common, CALIBRATE,
                                "PAPRD curve on chain %d needs to be re-trained\n",
                                chain);
                        break;
                } else if (ret) {
                        ath_dbg(common, CALIBRATE,
                                "PAPRD create curve failed on chain %d\n",
                                chain);
                        break;
                }

                chain_ok = 1;
        }
        kfree_skb(skb);

        if (chain_ok) {
                set_bit(PAPRD_DONE, &caldata->cal_flags);
                ath_paprd_activate(sc);
        }

fail_paprd:
        ath9k_ps_restore(sc);
}

/*
 *  ANI performs periodic noise floor calibration
 *  that is used to adjust and optimize the chip performance.  This
 *  takes environmental changes (location, temperature) into account.
 *  When the task is complete, it reschedules itself depending on the
 *  appropriate interval that was calculated.
 */
void ath_ani_calibrate(struct timer_list *t)
{
        struct ath_common *common = timer_container_of(common, t, ani.timer);
        struct ath_softc *sc = common->priv;
        struct ath_hw *ah = sc->sc_ah;
        bool longcal = false;
        bool shortcal = false;
        bool aniflag = false;
        unsigned int timestamp = jiffies_to_msecs(jiffies);
        u32 cal_interval, short_cal_interval, long_cal_interval;
        unsigned long flags;

        if (ah->caldata && test_bit(NFCAL_INTF, &ah->caldata->cal_flags))
                long_cal_interval = ATH_LONG_CALINTERVAL_INT;
        else
                long_cal_interval = ATH_LONG_CALINTERVAL;

        short_cal_interval = (ah->opmode == NL80211_IFTYPE_AP) ?
                ATH_AP_SHORT_CALINTERVAL : ATH_STA_SHORT_CALINTERVAL;

        /* Only calibrate if awake */
        if (sc->sc_ah->power_mode != ATH9K_PM_AWAKE) {
                if (++ah->ani_skip_count >= ATH_ANI_MAX_SKIP_COUNT) {
                        spin_lock_irqsave(&sc->sc_pm_lock, flags);
                        sc->ps_flags |= PS_WAIT_FOR_ANI;
                        spin_unlock_irqrestore(&sc->sc_pm_lock, flags);
                }
                goto set_timer;
        }
        ah->ani_skip_count = 0;
        spin_lock_irqsave(&sc->sc_pm_lock, flags);
        sc->ps_flags &= ~PS_WAIT_FOR_ANI;
        spin_unlock_irqrestore(&sc->sc_pm_lock, flags);

        ath9k_ps_wakeup(sc);

        /* Long calibration runs independently of short calibration. */
        if ((timestamp - common->ani.longcal_timer) >= long_cal_interval) {
                longcal = true;
                common->ani.longcal_timer = timestamp;
        }

        /* Short calibration applies only while caldone is false */
        if (!common->ani.caldone) {
                if ((timestamp - common->ani.shortcal_timer) >= short_cal_interval) {
                        shortcal = true;
                        common->ani.shortcal_timer = timestamp;
                        common->ani.resetcal_timer = timestamp;
                }
        } else {
                if ((timestamp - common->ani.resetcal_timer) >=
                    ATH_RESTART_CALINTERVAL) {
                        common->ani.caldone = ath9k_hw_reset_calvalid(ah);
                        if (common->ani.caldone)
                                common->ani.resetcal_timer = timestamp;
                }
        }

        /* Verify whether we must check ANI */
        if ((timestamp - common->ani.checkani_timer) >= ah->config.ani_poll_interval) {
                aniflag = true;
                common->ani.checkani_timer = timestamp;
        }

        /* Call ANI routine if necessary */
        if (aniflag) {
                spin_lock_irqsave(&common->cc_lock, flags);
                ath9k_hw_ani_monitor(ah, ah->curchan);
                ath_update_survey_stats(sc);
                spin_unlock_irqrestore(&common->cc_lock, flags);
        }

        /* Perform calibration if necessary */
        if (longcal || shortcal) {
                int ret = ath9k_hw_calibrate(ah, ah->curchan, ah->rxchainmask,
                                             longcal);
                if (ret < 0) {
                        common->ani.caldone = 0;
                        ath9k_queue_reset(sc, RESET_TYPE_CALIBRATION);
                        return;
                }

                common->ani.caldone = ret;
        }

        ath_dbg(common, ANI,
                "Calibration @%lu finished: %s %s %s, caldone: %s\n",
                jiffies,
                longcal ? "long" : "", shortcal ? "short" : "",
                aniflag ? "ani" : "", common->ani.caldone ? "true" : "false");

        ath9k_ps_restore(sc);

set_timer:
        /*
        * Set timer interval based on previous results.
        * The interval must be the shortest necessary to satisfy ANI,
        * short calibration and long calibration.
        */
        cal_interval = ATH_LONG_CALINTERVAL;
        cal_interval = min(cal_interval, (u32)ah->config.ani_poll_interval);
        if (!common->ani.caldone)
                cal_interval = min(cal_interval, (u32)short_cal_interval);

        mod_timer(&common->ani.timer, jiffies + msecs_to_jiffies(cal_interval));

        if (ar9003_is_paprd_enabled(ah) && ah->caldata) {
                if (!test_bit(PAPRD_DONE, &ah->caldata->cal_flags)) {
                        ieee80211_queue_work(sc->hw, &sc->paprd_work);
                } else if (!ah->paprd_table_write_done) {
                        ath9k_ps_wakeup(sc);
                        ath_paprd_activate(sc);
                        ath9k_ps_restore(sc);
                }
        }
}

void ath_start_ani(struct ath_softc *sc)
{
        struct ath_hw *ah = sc->sc_ah;
        struct ath_common *common = ath9k_hw_common(ah);
        unsigned long timestamp = jiffies_to_msecs(jiffies);

        if (common->disable_ani ||
            !test_bit(ATH_OP_ANI_RUN, &common->op_flags) ||
            sc->cur_chan->offchannel)
                return;

        common->ani.longcal_timer = timestamp;
        common->ani.shortcal_timer = timestamp;
        common->ani.checkani_timer = timestamp;

        ath_dbg(common, ANI, "Starting ANI\n");
        mod_timer(&common->ani.timer,
                  jiffies + msecs_to_jiffies((u32)ah->config.ani_poll_interval));
}

void ath_stop_ani(struct ath_softc *sc)
{
        struct ath_common *common = ath9k_hw_common(sc->sc_ah);

        ath_dbg(common, ANI, "Stopping ANI\n");
        timer_delete_sync(&common->ani.timer);
}

void ath_check_ani(struct ath_softc *sc)
{
        struct ath_hw *ah = sc->sc_ah;
        struct ath_common *common = ath9k_hw_common(sc->sc_ah);
        struct ath_beacon_config *cur_conf = &sc->cur_chan->beacon;

        /*
         * Check for the various conditions in which ANI has to
         * be stopped.
         */
        if (ah->opmode == NL80211_IFTYPE_ADHOC) {
                if (!cur_conf->enable_beacon)
                        goto stop_ani;
        } else if (ah->opmode == NL80211_IFTYPE_AP) {
                if (!cur_conf->enable_beacon) {
                        /*
                         * Disable ANI only when there are no
                         * associated stations.
                         */
                        if (!test_bit(ATH_OP_PRIM_STA_VIF, &common->op_flags))
                                goto stop_ani;
                }
        } else if (ah->opmode == NL80211_IFTYPE_STATION) {
                if (!test_bit(ATH_OP_PRIM_STA_VIF, &common->op_flags))
                        goto stop_ani;
        }

        if (!test_bit(ATH_OP_ANI_RUN, &common->op_flags)) {
                set_bit(ATH_OP_ANI_RUN, &common->op_flags);
                ath_start_ani(sc);
        }

        return;

stop_ani:
        clear_bit(ATH_OP_ANI_RUN, &common->op_flags);
        ath_stop_ani(sc);
}

void ath_update_survey_nf(struct ath_softc *sc, int channel)
{
        struct ath_hw *ah = sc->sc_ah;
        struct ath9k_channel *chan = &ah->channels[channel];
        struct survey_info *survey = &sc->survey[channel];

        if (chan->noisefloor) {
                survey->filled |= SURVEY_INFO_NOISE_DBM;
                survey->noise = ath9k_hw_getchan_noise(ah, chan,
                                                       chan->noisefloor);
        }
}

/*
 * Updates the survey statistics and returns the busy time since last
 * update in %, if the measurement duration was long enough for the
 * result to be useful, -1 otherwise.
 */
int ath_update_survey_stats(struct ath_softc *sc)
{
        struct ath_hw *ah = sc->sc_ah;
        struct ath_common *common = ath9k_hw_common(ah);
        int pos = ah->curchan - &ah->channels[0];
        struct survey_info *survey = &sc->survey[pos];
        struct ath_cycle_counters *cc = &common->cc_survey;
        unsigned int div = common->clockrate * 1000;
        int ret = 0;

        if (!ah->curchan)
                return -1;

        if (ah->power_mode == ATH9K_PM_AWAKE)
                ath_hw_cycle_counters_update(common);

        if (cc->cycles > 0) {
                survey->filled |= SURVEY_INFO_TIME |
                        SURVEY_INFO_TIME_BUSY |
                        SURVEY_INFO_TIME_RX |
                        SURVEY_INFO_TIME_TX;
                survey->time += cc->cycles / div;
                survey->time_busy += cc->rx_busy / div;
                survey->time_rx += cc->rx_frame / div;
                survey->time_tx += cc->tx_frame / div;
        }

        if (cc->cycles < div)
                return -1;

        if (cc->cycles > 0)
                ret = cc->rx_busy * 100 / cc->cycles;

        memset(cc, 0, sizeof(*cc));

        ath_update_survey_nf(sc, pos);

        return ret;
}