Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / ipxe / src / drivers / net / ath / ath9k / ath9k_main.c
diff --git a/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_main.c b/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_main.c
new file mode 100644 (file)
index 0000000..0a17b9b
--- /dev/null
@@ -0,0 +1,916 @@
+/*
+ * Copyright (c) 2008-2011 Atheros Communications Inc.
+ *
+ * Modified for iPXE by Scott K Logan <logans@cottsay.net> July 2011
+ * Original from Linux kernel 3.0.1
+ *
+ * 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 <ipxe/io.h>
+
+#include "ath9k.h"
+
+static void ath9k_bss_info_changed(struct net80211_device *dev, u32 changed);
+
+int ath9k_setpower(struct ath_softc *sc, enum ath9k_power_mode mode)
+{
+       int ret;
+
+       ret = ath9k_hw_setpower(sc->sc_ah, mode);
+
+       return ret;
+}
+
+static void ath_start_ani(struct ath_common *common)
+{
+       struct ath_hw *ah = common->ah;
+       unsigned long timestamp = ( currticks() * 1000 ) / TICKS_PER_SEC;
+       struct ath_softc *sc = (struct ath_softc *) common->priv;
+
+       if (!(sc->sc_flags & SC_OP_ANI_RUN))
+               return;
+
+       if (sc->sc_flags & SC_OP_OFFCHANNEL)
+               return;
+
+       common->ani.longcal_timer = timestamp;
+       common->ani.shortcal_timer = timestamp;
+       common->ani.checkani_timer = timestamp;
+
+       common->ani.timer = timestamp + ah->config.ani_poll_interval;
+}
+
+static 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 = 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.
+ */
+static 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_CHANNEL_TIME |
+                       SURVEY_INFO_CHANNEL_TIME_BUSY |
+                       SURVEY_INFO_CHANNEL_TIME_RX |
+                       SURVEY_INFO_CHANNEL_TIME_TX;
+               survey->channel_time += cc->cycles / div;
+               survey->channel_time_busy += cc->rx_busy / div;
+               survey->channel_time_rx += cc->rx_frame / div;
+               survey->channel_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;
+}
+
+/*
+ * Set/change channels.  If the channel is really being changed, it's done
+ * by reseting the chip.  To accomplish this we must first cleanup any pending
+ * DMA, then restart stuff.
+*/
+int ath_set_channel(struct ath_softc *sc, struct net80211_device *dev,
+                   struct ath9k_channel *hchan)
+{
+       struct ath_hw *ah = sc->sc_ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       int fastcc __unused = 1, stopped __unused;
+       struct net80211_channel *channel = dev->channels + dev->channel;
+       struct ath9k_hw_cal_data *caldata = NULL;
+       int r;
+
+       if (sc->sc_flags & SC_OP_INVALID)
+               return -EIO;
+
+       sc->hw_busy_count = 0;
+
+       common->ani.timer = 0;
+       sc->tx_complete_work_timer = 0;
+       sc->hw_pll_work_timer = 0;
+
+       /*
+        * This is only performed if the channel settings have
+        * actually changed.
+        *
+        * To switch channels clear any pending DMA operations;
+        * wait long enough for the RX fifo to drain, reset the
+        * hardware at the new frequency, and then re-enable
+        * the relevant bits of the h/w.
+        */
+       ath9k_hw_disable_interrupts(ah);
+       stopped = ath_drain_all_txq(sc, 0);
+
+       if (!ath_stoprecv(sc))
+               stopped = 0;
+
+       if (!ath9k_hw_check_alive(ah))
+               stopped = 0;
+
+       /* XXX: do not flush receive queue here. We don't want
+        * to flush data frames already in queue because of
+        * changing channel. */
+
+       if (!(sc->sc_flags & SC_OP_OFFCHANNEL))
+               caldata = &sc->caldata;
+
+       DBG2("ath9k: "
+               "(%d MHz) -> (%d MHz)\n",
+               sc->sc_ah->curchan->channel,
+               channel->center_freq);
+
+       r = ath9k_hw_reset(ah, hchan, caldata, fastcc);
+       if (r) {
+               DBG("ath9k: "
+                       "Unable to reset channel (%d MHz), reset status %d\n",
+                       channel->center_freq, r);
+               goto ps_restore;
+       }
+
+       if (ath_startrecv(sc) != 0) {
+               DBG("ath9k: Unable to restart recv logic\n");
+               r = -EIO;
+               goto ps_restore;
+       }
+
+       ath9k_cmn_update_txpow(ah, sc->curtxpow,
+                              sc->config.txpowlimit, &sc->curtxpow);
+       ath9k_hw_set_interrupts(ah, ah->imask);
+
+       if (!(sc->sc_flags & (SC_OP_OFFCHANNEL))) {
+               sc->tx_complete_work(sc);
+               sc->hw_pll_work_timer = (currticks() * 1000 ) / TICKS_PER_SEC + 500;
+               ath_start_ani(common);
+       }
+
+ ps_restore:
+       return r;
+}
+
+/*
+ *  This routine performs the periodic noise floor calibration function
+ *  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 ath_softc *sc)
+{
+       struct ath_hw *ah = sc->sc_ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       int longcal = 0;
+       int shortcal = 0;
+       int aniflag = 0;
+       unsigned int timestamp = (currticks() * 1000 ) / TICKS_PER_SEC;
+       u32 cal_interval, short_cal_interval, long_cal_interval;
+
+       if (ah->caldata && ah->caldata->nfcal_interference)
+               long_cal_interval = ATH_LONG_CALINTERVAL_INT;
+       else
+               long_cal_interval = ATH_LONG_CALINTERVAL;
+
+       short_cal_interval = ATH_STA_SHORT_CALINTERVAL;
+
+       /* Only calibrate if awake */
+       if (sc->sc_ah->power_mode != ATH9K_PM_AWAKE)
+               goto set_timer;
+
+       /* Long calibration runs independently of short calibration. */
+       if ((timestamp - common->ani.longcal_timer) >= long_cal_interval) {
+               longcal = 1;
+               DBG2("ath9k: longcal @%d\n", timestamp);
+               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 = 1;
+                       DBG2("ath9k: "
+                               "shortcal @%d\n", timestamp);
+                       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 = 1;
+               common->ani.checkani_timer = timestamp;
+       }
+
+       /* Skip all processing if there's nothing to do. */
+       if (longcal || shortcal || aniflag) {
+               /* Call ANI routine if necessary */
+               if (aniflag) {
+                       ath9k_hw_ani_monitor(ah, ah->curchan);
+                       ath_update_survey_stats(sc);
+               }
+
+               /* Perform calibration if necessary */
+               if (longcal || shortcal) {
+                       common->ani.caldone =
+                               ath9k_hw_calibrate(ah,
+                                                  ah->curchan,
+                                                  common->rx_chainmask,
+                                                  longcal);
+               }
+       }
+
+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;
+       if (sc->sc_ah->config.enable_ani)
+               cal_interval = min(cal_interval,
+                                  (u32)ah->config.ani_poll_interval);
+       if (!common->ani.caldone)
+               cal_interval = min(cal_interval, (u32)short_cal_interval);
+
+       common->ani.timer = timestamp + cal_interval;
+}
+
+void ath_hw_check(struct ath_softc *sc)
+{
+       int busy;
+
+       if (ath9k_hw_check_alive(sc->sc_ah))
+               goto out;
+
+       busy = ath_update_survey_stats(sc);
+
+       DBG("ath9k: Possible baseband hang, "
+               "busy=%d (try %d)\n", busy, sc->hw_busy_count + 1);
+       if (busy >= 99) {
+               if (++sc->hw_busy_count >= 3)
+                       ath_reset(sc, 1);
+       } else if (busy >= 0)
+               sc->hw_busy_count = 0;
+
+out:
+       return;
+}
+
+static void ath_hw_pll_rx_hang_check(struct ath_softc *sc, u32 pll_sqsum)
+{
+       static int count;
+
+       if (pll_sqsum >= 0x40000) {
+               count++;
+               if (count == 3) {
+                       /* Rx is hung for more than 500ms. Reset it */
+                       DBG("ath9k: "
+                               "Possible RX hang, resetting");
+                       ath_reset(sc, 1);
+                       count = 0;
+               }
+       } else
+               count = 0;
+}
+
+void ath_hw_pll_work(struct ath_softc *sc)
+{
+       u32 pll_sqsum;
+
+       if (AR_SREV_9485(sc->sc_ah)) {
+               pll_sqsum = ar9003_get_pll_sqsum_dvc(sc->sc_ah);
+
+               ath_hw_pll_rx_hang_check(sc, pll_sqsum);
+
+               sc->hw_pll_work_timer = (currticks() * 1000 ) / TICKS_PER_SEC + 200;
+       }
+}
+
+
+void ath9k_tasklet(struct ath_softc *sc)
+{
+       struct ath_hw *ah = sc->sc_ah;
+
+       u32 status = sc->intrstatus;
+       u32 rxmask;
+
+       if ((status & ATH9K_INT_FATAL) ||
+           (status & ATH9K_INT_BB_WATCHDOG)) {
+               ath_reset(sc, 1);
+               return;
+       }
+
+       rxmask = (ATH9K_INT_RX | ATH9K_INT_RXEOL | ATH9K_INT_RXORN);
+
+       if (status & rxmask) {
+               ath_rx_tasklet(sc, 0, 0);
+       }
+
+       if (status & ATH9K_INT_TX) {
+               ath_tx_tasklet(sc);
+       }
+
+       /* re-enable hardware interrupt */
+       ath9k_hw_enable_interrupts(ah);
+}
+
+void ath_isr(struct net80211_device *dev)
+{
+#define SCHED_INTR (                           \
+               ATH9K_INT_FATAL |               \
+               ATH9K_INT_BB_WATCHDOG |         \
+               ATH9K_INT_RXORN |               \
+               ATH9K_INT_RXEOL |               \
+               ATH9K_INT_RX |                  \
+               ATH9K_INT_RXLP |                \
+               ATH9K_INT_RXHP |                \
+               ATH9K_INT_TX |                  \
+               ATH9K_INT_BMISS |               \
+               ATH9K_INT_CST |                 \
+               ATH9K_INT_TSFOOR |              \
+               ATH9K_INT_GENTIMER)
+
+       struct ath_softc *sc = dev->priv;
+       struct ath_hw *ah = sc->sc_ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       enum ath9k_int status;
+       unsigned long timestamp = (currticks() * 1000 ) / TICKS_PER_SEC;
+       int sched = 0;
+
+       /*
+        * The hardware is not ready/present, don't
+        * touch anything. Note this can happen early
+        * on if the IRQ is shared.
+        */
+       if (sc->sc_flags & SC_OP_INVALID)
+               return;
+
+
+       /* Check calibration */
+       if(timestamp >= (unsigned int)common->ani.timer && common->ani.timer)
+               ath_ani_calibrate(sc);
+
+       /* Check tx_complete_work */
+       if(timestamp >= (unsigned int)sc->tx_complete_work_timer && sc->tx_complete_work_timer)
+               sc->tx_complete_work(sc);
+
+       /* Check hw_pll_work */
+       if(timestamp >= (unsigned int)sc->hw_pll_work_timer && sc->hw_pll_work_timer)
+               sc->hw_pll_work(sc);
+
+       /* shared irq, not for us */
+
+       if (!ath9k_hw_intrpend(ah))
+               return;
+
+       /*
+        * Figure out the reason(s) for the interrupt.  Note
+        * that the hal returns a pseudo-ISR that may include
+        * bits we haven't explicitly enabled so we mask the
+        * value to insure we only process bits we requested.
+        */
+       ath9k_hw_getisr(ah, &status);   /* NB: clears ISR too */
+       status &= ah->imask;    /* discard unasked-for bits */
+
+       /*
+        * If there are no status bits set, then this interrupt was not
+        * for me (should have been caught above).
+        */
+       if (!status)
+               return;
+
+       /* Cache the status */
+       sc->intrstatus = status;
+
+       if (status & SCHED_INTR)
+               sched = 1;
+
+       /*
+        * If a FATAL or RXORN interrupt is received, we have to reset the
+        * chip immediately.
+        */
+       if ((status & ATH9K_INT_FATAL) || (status & ATH9K_INT_RXORN))
+               goto chip_reset;
+
+       if (status & ATH9K_INT_TXURN)
+               ath9k_hw_updatetxtriglevel(ah, 1);
+
+       if (!(ah->caps.hw_caps & ATH9K_HW_CAP_AUTOSLEEP))
+               if (status & ATH9K_INT_TIM_TIMER) {
+                       if (sc->ps_idle)
+                               goto chip_reset;
+                       /* Clear RxAbort bit so that we can
+                        * receive frames */
+                       ath9k_setpower(sc, ATH9K_PM_AWAKE);
+                       ath9k_hw_setrxabort(sc->sc_ah, 0);
+                       sc->ps_flags |= PS_WAIT_FOR_BEACON;
+               }
+
+chip_reset:
+
+       if (sched) {
+               /* turn off every interrupt */
+               ath9k_hw_disable_interrupts(ah);
+               sc->intr_tq(sc);
+       }
+
+       return;
+
+#undef SCHED_INTR
+}
+
+void ath_radio_disable(struct ath_softc *sc, struct net80211_device *dev)
+{
+       struct ath_hw *ah = sc->sc_ah;
+       struct net80211_channel *channel = dev->channels + dev->channel;
+       int r;
+
+       sc->hw_pll_work_timer = 0;
+
+       /*
+        * Keep the LED on when the radio is disabled
+        * during idle unassociated state.
+        */
+       if (!sc->ps_idle) {
+               ath9k_hw_set_gpio(ah, ah->led_pin, 1);
+               ath9k_hw_cfg_gpio_input(ah, ah->led_pin);
+       }
+
+       /* Disable interrupts */
+       ath9k_hw_disable_interrupts(ah);
+
+       ath_drain_all_txq(sc, 0);       /* clear pending tx frames */
+
+       ath_stoprecv(sc);               /* turn off frame recv */
+       ath_flushrecv(sc);              /* flush recv queue */
+
+       if (!ah->curchan)
+               ah->curchan = ath9k_cmn_get_curchannel(dev, ah);
+
+       r = ath9k_hw_reset(ah, ah->curchan, ah->caldata, 0);
+       if (r) {
+               DBG("ath9k: "
+                       "Unable to reset channel (%d MHz), reset status %d\n",
+                       channel->center_freq, r);
+       }
+
+       ath9k_hw_phy_disable(ah);
+
+       ath9k_hw_configpcipowersave(ah, 1, 1);
+}
+
+int ath_reset(struct ath_softc *sc, int retry_tx)
+{
+       struct ath_hw *ah = sc->sc_ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       int r;
+
+       sc->hw_busy_count = 0;
+
+       /* Stop ANI */
+       common->ani.timer = 0;
+
+       ath9k_hw_disable_interrupts(ah);
+       ath_drain_all_txq(sc, retry_tx);
+
+       ath_stoprecv(sc);
+       ath_flushrecv(sc);
+
+       r = ath9k_hw_reset(ah, sc->sc_ah->curchan, ah->caldata, 0);
+       if (r)
+               DBG("ath9k: "
+                       "Unable to reset hardware; reset status %d\n", r);
+
+       if (ath_startrecv(sc) != 0)
+               DBG("ath9k: Unable to start recv logic\n");
+
+       /*
+        * We may be doing a reset in response to a request
+        * that changes the channel so update any state that
+        * might change as a result.
+        */
+       ath9k_cmn_update_txpow(ah, sc->curtxpow,
+                              sc->config.txpowlimit, &sc->curtxpow);
+
+       ath9k_hw_set_interrupts(ah, ah->imask);
+
+       if (retry_tx) {
+               int i;
+               for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) {
+                       if (ATH_TXQ_SETUP(sc, i)) {
+                               ath_txq_schedule(sc, &sc->tx.txq[i]);
+                       }
+               }
+       }
+
+       /* Start ANI */
+       ath_start_ani(common);
+
+       return r;
+}
+
+/**********************/
+/* mac80211 callbacks */
+/**********************/
+
+static int ath9k_start(struct net80211_device *dev)
+{
+       struct ath_softc *sc = dev->priv;
+       struct ath_hw *ah = sc->sc_ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       struct net80211_channel *curchan = dev->channels + dev->channel;
+       struct ath9k_channel *init_channel;
+       int r;
+
+       DBG("ath9k: "
+               "Starting driver with initial channel: %d MHz\n",
+               curchan->center_freq);
+
+       /* setup initial channel */
+       sc->chan_idx = curchan->hw_value;
+
+       init_channel = ath9k_cmn_get_curchannel(dev, ah);
+
+       /* Reset SERDES registers */
+       ath9k_hw_configpcipowersave(ah, 0, 0);
+
+       /*
+        * The basic interface to setting the hardware in a good
+        * state is ``reset''.  On return the hardware is known to
+        * be powered up and with interrupts disabled.  This must
+        * be followed by initialization of the appropriate bits
+        * and then setup of the interrupt mask.
+        */
+       r = ath9k_hw_reset(ah, init_channel, ah->caldata, 0);
+       if (r) {
+               DBG("ath9k: "
+                       "Unable to reset hardware; reset status %d (freq %d MHz)\n",
+                       r, curchan->center_freq);
+               goto mutex_unlock;
+       }
+
+       /*
+        * This is needed only to setup initial state
+        * but it's best done after a reset.
+        */
+       ath9k_cmn_update_txpow(ah, sc->curtxpow,
+                       sc->config.txpowlimit, &sc->curtxpow);
+
+       /*
+        * Setup the hardware after reset:
+        * The receive engine is set going.
+        * Frame transmit is handled entirely
+        * in the frame output path; there's nothing to do
+        * here except setup the interrupt mask.
+        */
+       if (ath_startrecv(sc) != 0) {
+               DBG("ath9k: Unable to start recv logic\n");
+               r = -EIO;
+               goto mutex_unlock;
+       }
+
+       /* Setup our intr mask. */
+       ah->imask = ATH9K_INT_TX | ATH9K_INT_RXEOL |
+                   ATH9K_INT_RXORN | ATH9K_INT_FATAL |
+                   ATH9K_INT_GLOBAL;
+
+       ah->imask |= ATH9K_INT_RX;
+
+       sc->sc_flags &= ~SC_OP_INVALID;
+       sc->sc_ah->is_monitoring = 0;
+
+       ath9k_hw_set_interrupts(ah, ah->imask);
+
+       sc->tx_complete_work(sc);
+
+       if (ah->caps.pcie_lcr_extsync_en && common->bus_ops->extn_synch_en)
+               common->bus_ops->extn_synch_en(common);
+
+mutex_unlock:
+       return r;
+}
+
+static int ath9k_tx(struct net80211_device *dev, struct io_buffer *iob)
+{
+       struct ath_softc *sc = dev->priv;
+       struct ath_tx_control txctl;
+       int ret = 0;
+
+       memset(&txctl, 0, sizeof(struct ath_tx_control));
+       txctl.txq = sc->tx.txq_map[0];
+
+       DBGIO("ath9k: transmitting packet, iob: %p\n", iob);
+
+       ret = ath_tx_start(dev, iob, &txctl);
+       if (ret) {
+               DBG("ath9k: TX failed\n");
+               goto exit;
+       }
+
+       return ret;
+exit:
+       free_iob(iob);
+       return ret;
+}
+
+static void ath9k_stop(struct net80211_device *dev)
+{
+       struct ath_softc *sc = dev->priv;
+       struct ath_hw *ah = sc->sc_ah;
+
+       sc->tx_complete_work_timer = 0;
+       sc->hw_pll_work_timer = 0;
+
+       if (sc->sc_flags & SC_OP_INVALID) {
+               DBG("ath9k: Device not present\n");
+               return;
+       }
+
+       /* prevent tasklets to enable interrupts once we disable them */
+       ah->imask &= ~ATH9K_INT_GLOBAL;
+
+       /* make sure h/w will not generate any interrupt
+        * before setting the invalid flag. */
+       ath9k_hw_disable_interrupts(ah);
+
+       if (!(sc->sc_flags & SC_OP_INVALID)) {
+               ath_drain_all_txq(sc, 0);
+               ath_stoprecv(sc);
+               ath9k_hw_phy_disable(ah);
+       } else
+               sc->rx.rxlink = NULL;
+
+       if (sc->rx.frag) {
+               free_iob(sc->rx.frag);
+               sc->rx.frag = NULL;
+       }
+
+       /* disable HAL and put h/w to sleep */
+       ath9k_hw_disable(ah);
+       ath9k_hw_configpcipowersave(ah, 1, 1);
+
+       ath_radio_disable(sc, dev);
+
+       sc->sc_flags |= SC_OP_INVALID;
+
+       DBG("ath9k: Driver halt\n");
+}
+
+static int ath9k_config(struct net80211_device *dev, int changed)
+{
+       struct ath_softc *sc = dev->priv;
+       struct ath_hw *ah = sc->sc_ah;
+
+       if ((changed & NET80211_CFG_RATE) ||
+                       (changed & NET80211_CFG_PHY_PARAMS)) {
+               int spmbl = (sc->sc_flags & SC_OP_PREAMBLE_SHORT) ? IEEE80211_TX_RC_USE_SHORT_PREAMBLE : 0;
+               u16 rate = dev->rates[dev->rate];
+               u16 slowrate = dev->rates[dev->rtscts_rate];
+               int i;
+
+               for (i = 0; i < NET80211_MAX_RATES; i++) {
+                       if (sc->rates[i].bitrate == rate &&
+                           (sc->rates[i].flags & spmbl))
+                               sc->hw_rix = i;
+
+                       if (sc->rates[i].bitrate == slowrate &&
+                           (sc->rates[i].flags & spmbl))
+                               sc->hw_rix = i;
+               }
+       }
+
+       ath9k_bss_info_changed(dev, changed);
+
+       if (changed & NET80211_CFG_CHANNEL) {
+               struct net80211_channel *curchan = dev->channels + dev->channel;
+               int pos = curchan->hw_value;
+               int old_pos = -1;
+
+               if (ah->curchan)
+                       old_pos = ah->curchan - &ah->channels[0];
+
+               sc->sc_flags &= ~SC_OP_OFFCHANNEL;
+
+               DBG2("ath9k: "
+                       "Set channel: %d MHz\n",
+                       curchan->center_freq);
+
+               ath9k_cmn_update_ichannel(&sc->sc_ah->channels[pos],
+                                         curchan);
+
+               /* update survey stats for the old channel before switching */
+               ath_update_survey_stats(sc);
+
+               /*
+                * If the operating channel changes, change the survey in-use flags
+                * along with it.
+                * Reset the survey data for the new channel, unless we're switching
+                * back to the operating channel from an off-channel operation.
+                */
+               if (sc->cur_survey != &sc->survey[pos]) {
+
+                       if (sc->cur_survey)
+                               sc->cur_survey->filled &= ~SURVEY_INFO_IN_USE;
+
+                       sc->cur_survey = &sc->survey[pos];
+
+                       memset(sc->cur_survey, 0, sizeof(struct survey_info));
+                       sc->cur_survey->filled |= SURVEY_INFO_IN_USE;
+               } else if (!(sc->survey[pos].filled & SURVEY_INFO_IN_USE)) {
+                       memset(&sc->survey[pos], 0, sizeof(struct survey_info));
+               }
+
+               if (ath_set_channel(sc, dev, &sc->sc_ah->channels[pos]) < 0) {
+                       DBG("ath9k: Unable to set channel\n");
+                       return -EINVAL;
+               }
+
+               /*
+                * The most recent snapshot of channel->noisefloor for the old
+                * channel is only available after the hardware reset. Copy it to
+                * the survey stats now.
+                */
+               if (old_pos >= 0)
+                       ath_update_survey_nf(sc, old_pos);
+       }
+
+       if (changed & NET80211_CFG_CHANNEL) {
+               DBG2("ath9k: "
+                       "Set power: %d\n", (dev->channels + dev->channel)->maxpower);
+               sc->config.txpowlimit = 2 * (dev->channels + dev->channel)->maxpower;
+               ath9k_cmn_update_txpow(ah, sc->curtxpow,
+                                      sc->config.txpowlimit, &sc->curtxpow);
+       }
+
+       return 0;
+}
+
+static void ath9k_bss_iter(struct ath_softc *sc)
+{
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+
+       if (common->dev->state & NET80211_ASSOCIATED) {
+               sc->sc_flags |= SC_OP_PRIM_STA_VIF;
+               memcpy(common->curbssid, common->dev->bssid, ETH_ALEN);
+               common->curaid = common->dev->aid;
+               ath9k_hw_write_associd(sc->sc_ah);
+               DBG("ath9k: "
+                       "Bss Info ASSOC %d, bssid: %pM\n",
+                       common->dev->aid, common->curbssid);
+
+               /*
+                * Request a re-configuration of Beacon related timers
+                * on the receipt of the first Beacon frame (i.e.,
+                * after time sync with the AP).
+                */
+               sc->ps_flags |= PS_BEACON_SYNC | PS_WAIT_FOR_BEACON;
+               /* Reset rssi stats */
+               sc->last_rssi = ATH_RSSI_DUMMY_MARKER;
+               sc->sc_ah->stats.avgbrssi = ATH_RSSI_DUMMY_MARKER;
+
+               sc->sc_flags |= SC_OP_ANI_RUN;
+               ath_start_ani(common);
+       }
+}
+
+static void ath9k_config_bss(struct ath_softc *sc)
+{
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+       struct net80211_device *dev = common->dev;
+
+       /* Reconfigure bss info */
+       if (!(dev->state & NET80211_ASSOCIATED)) {
+               DBG2("ath9k: "
+                       "ath9k: Bss Info DISASSOC %d, bssid %pM\n",
+                       common->curaid, common->curbssid);
+               sc->sc_flags &= ~(SC_OP_PRIM_STA_VIF | SC_OP_BEACONS);
+               memset(common->curbssid, 0, ETH_ALEN);
+               common->curaid = 0;
+       }
+
+       ath9k_bss_iter(sc);
+
+       /*
+        * None of station vifs are associated.
+        * Clear bssid & aid
+        */
+       if (!(sc->sc_flags & SC_OP_PRIM_STA_VIF)) {
+               ath9k_hw_write_associd(sc->sc_ah);
+               /* Stop ANI */
+               sc->sc_flags &= ~SC_OP_ANI_RUN;
+               common->ani.timer = 0;
+       }
+}
+
+static void ath9k_bss_info_changed(struct net80211_device *dev,
+                                  u32 changed)
+{
+       struct ath_softc *sc = dev->priv;
+       struct ath_hw *ah = sc->sc_ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       int slottime;
+
+       if (changed & NET80211_CFG_ASSOC) {
+               ath9k_config_bss(sc);
+
+               DBG2("ath9k: BSSID: %pM aid: 0x%x\n",
+                       common->curbssid, common->curaid);
+       }
+
+       if (changed & NET80211_CFG_PHY_PARAMS) {
+               if (dev->phy_flags & NET80211_PHY_USE_PROTECTION)
+                       slottime = 9;
+               else
+                       slottime = 20;
+               ah->slottime = slottime;
+               ath9k_hw_init_global_settings(ah);
+
+               DBG2("ath9k: BSS Changed PREAMBLE %d\n",
+                               !!(dev->phy_flags & NET80211_PHY_USE_SHORT_PREAMBLE));
+               if (dev->phy_flags & NET80211_PHY_USE_SHORT_PREAMBLE)
+                       sc->sc_flags |= SC_OP_PREAMBLE_SHORT;
+               else
+                       sc->sc_flags &= ~SC_OP_PREAMBLE_SHORT;
+
+               DBG2("ath9k: BSS Changed CTS PROT %d\n",
+                       !!(dev->phy_flags & NET80211_PHY_USE_PROTECTION));
+               if ((dev->phy_flags & NET80211_PHY_USE_PROTECTION) &&
+                   (dev->channels + dev->channel)->band != NET80211_BAND_5GHZ)
+                       sc->sc_flags |= SC_OP_PROTECT_ENABLE;
+               else
+                       sc->sc_flags &= ~SC_OP_PROTECT_ENABLE;
+       }
+}
+
+static void ath9k_poll(struct net80211_device *dev)
+{
+       ath_isr(dev);
+}
+
+static void ath9k_irq(struct net80211_device *dev, int enable)
+{
+       struct ath_softc *sc = dev->priv;
+       struct ath_hw *ah = sc->sc_ah;
+
+       ah->ah_ier = enable ? AR_IER_ENABLE : AR_IER_DISABLE;
+
+       ath9k_hw_set_interrupts(ah, ah->imask);
+}
+
+struct net80211_device_operations ath9k_ops = {
+       .transmit       = ath9k_tx,
+       .open           = ath9k_start,
+       .close          = ath9k_stop,
+       .config         = ath9k_config,
+       .poll           = ath9k_poll,
+       .irq            = ath9k_irq,
+};