struct ath_wiphy **sec_wiphy; /* secondary wiphys (virtual radios); may
                                       * have NULL entries */
        int num_sec_wiphy; /* number of sec_wiphy pointers in the array */
+       int chan_idx;
+       int chan_is_ht;
+       struct ath_wiphy *next_wiphy;
+       struct work_struct chan_work;
+
        struct tasklet_struct intr_tq;
        struct tasklet_struct bcon_tasklet;
        struct ath_hw *sc_ah;
                ATH_WIPHY_PAUSING,
                ATH_WIPHY_PAUSED,
        } state;
+       int chan_idx;
+       int chan_is_ht;
 };
 
 int ath_reset(struct ath_softc *sc, bool retry_tx);
 const char *ath_mac_bb_name(u32 mac_bb_version);
 const char *ath_rf_name(u16 rf_version);
 void ath_set_hw_capab(struct ath_softc *sc, struct ieee80211_hw *hw);
+void ath9k_update_ichannel(struct ath_softc *sc, struct ieee80211_hw *hw,
+                          struct ath9k_channel *ichan);
+void ath_update_chainmask(struct ath_softc *sc, int is_ht);
+int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw,
+                   struct ath9k_channel *hchan);
 
 #ifdef CONFIG_PCI
 int ath_pci_init(void);
 void ath9k_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb);
 int ath9k_wiphy_pause(struct ath_wiphy *aphy);
 int ath9k_wiphy_unpause(struct ath_wiphy *aphy);
+int ath9k_wiphy_select(struct ath_wiphy *aphy);
+void ath9k_wiphy_chan_work(struct work_struct *work);
 
 #endif /* ATH9K_H */
 
  * by reseting the chip.  To accomplish this we must first cleanup any pending
  * DMA, then restart stuff.
 */
-static int ath_set_channel(struct ath_softc *sc, struct ath9k_channel *hchan)
+int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw,
+                   struct ath9k_channel *hchan)
 {
        struct ath_hw *ah = sc->sc_ah;
        bool fastcc = true, stopped;
-       struct ieee80211_hw *hw = sc->hw;
        struct ieee80211_channel *channel = hw->conf.channel;
        int r;
 
  * the chainmask configuration, for bt coexistence, use
  * the chainmask configuration even in legacy mode.
  */
-static void ath_update_chainmask(struct ath_softc *sc, int is_ht)
+void ath_update_chainmask(struct ath_softc *sc, int is_ht)
 {
        sc->sc_flags |= SC_OP_CHAINMASK_UPDATE;
        if (is_ht ||
        ath_deinit_rfkill(sc);
 #endif
        ath_deinit_leds(sc);
+       cancel_work_sync(&sc->chan_work);
 
        for (i = 0; i < sc->num_sec_wiphy; i++) {
                struct ath_wiphy *aphy = sc->sec_wiphy[i];
        ath9k_reg_apply_radar_flags(hw->wiphy);
        ath9k_reg_apply_world_flags(hw->wiphy, REGDOM_SET_BY_INIT);
 
+       INIT_WORK(&sc->chan_work, ath9k_wiphy_chan_work);
+
        error = ieee80211_register_hw(hw);
 
        if (!ath9k_is_world_regd(sc->sc_ah)) {
 
 /* XXX: Remove me once we don't depend on ath9k_channel for all
  * this redundant data */
-static void ath9k_update_ichannel(struct ath_softc *sc,
-                         struct ath9k_channel *ichan)
+void ath9k_update_ichannel(struct ath_softc *sc, struct ieee80211_hw *hw,
+                          struct ath9k_channel *ichan)
 {
-       struct ieee80211_hw *hw = sc->hw;
        struct ieee80211_channel *chan = hw->conf.channel;
        struct ieee80211_conf *conf = &hw->conf;
 
 
        pos = curchan->hw_value;
 
+       sc->chan_idx = pos;
        init_channel = &sc->sc_ah->channels[pos];
-       ath9k_update_ichannel(sc, init_channel);
+       ath9k_update_ichannel(sc, hw, init_channel);
 
        /* Reset SERDES registers */
        ath9k_hw_configpcipowersave(sc->sc_ah, 0);
                struct ieee80211_channel *curchan = hw->conf.channel;
                int pos = curchan->hw_value;
 
+               aphy->chan_idx = pos;
+               aphy->chan_is_ht = conf_is_ht(conf);
+
+               /* TODO: do not change operation channel immediately if there
+                * are other virtual wiphys that use another channel */
+
                DPRINTF(sc, ATH_DBG_CONFIG, "Set channel: %d MHz\n",
                        curchan->center_freq);
 
                /* XXX: remove me eventualy */
-               ath9k_update_ichannel(sc, &sc->sc_ah->channels[pos]);
+               ath9k_update_ichannel(sc, hw, &sc->sc_ah->channels[pos]);
 
                ath_update_chainmask(sc, conf_is_ht(conf));
 
-               if (ath_set_channel(sc, &sc->sc_ah->channels[pos]) < 0) {
+               if (ath_set_channel(sc, hw, &sc->sc_ah->channels[pos]) < 0) {
                        DPRINTF(sc, ATH_DBG_FATAL, "Unable to set channel\n");
                        mutex_unlock(&sc->mutex);
                        return -EINVAL;
 
        return -1;
 }
 
+static bool __ath9k_wiphy_pausing(struct ath_softc *sc)
+{
+       int i;
+       if (sc->pri_wiphy->state == ATH_WIPHY_PAUSING)
+               return true;
+       for (i = 0; i < sc->num_sec_wiphy; i++) {
+               if (sc->sec_wiphy[i] &&
+                   sc->sec_wiphy[i]->state == ATH_WIPHY_PAUSING)
+                       return true;
+       }
+       return false;
+}
+
+static bool ath9k_wiphy_pausing(struct ath_softc *sc)
+{
+       bool ret;
+       spin_lock_bh(&sc->wiphy_lock);
+       ret = __ath9k_wiphy_pausing(sc);
+       spin_unlock_bh(&sc->wiphy_lock);
+       return ret;
+}
+
+static int __ath9k_wiphy_unpause(struct ath_wiphy *aphy);
+
+/* caller must hold wiphy_lock */
+static void __ath9k_wiphy_unpause_ch(struct ath_wiphy *aphy)
+{
+       if (aphy == NULL)
+               return;
+       if (aphy->chan_idx != aphy->sc->chan_idx)
+               return; /* wiphy not on the selected channel */
+       __ath9k_wiphy_unpause(aphy);
+}
+
+static void ath9k_wiphy_unpause_channel(struct ath_softc *sc)
+{
+       int i;
+       spin_lock_bh(&sc->wiphy_lock);
+       __ath9k_wiphy_unpause_ch(sc->pri_wiphy);
+       for (i = 0; i < sc->num_sec_wiphy; i++)
+               __ath9k_wiphy_unpause_ch(sc->sec_wiphy[i]);
+       spin_unlock_bh(&sc->wiphy_lock);
+}
+
+void ath9k_wiphy_chan_work(struct work_struct *work)
+{
+       struct ath_softc *sc = container_of(work, struct ath_softc, chan_work);
+       struct ath_wiphy *aphy = sc->next_wiphy;
+
+       if (aphy == NULL)
+               return;
+
+       /*
+        * All pending interfaces paused; ready to change
+        * channels.
+        */
+
+       /* Change channels */
+       mutex_lock(&sc->mutex);
+       /* XXX: remove me eventually */
+       ath9k_update_ichannel(sc, aphy->hw,
+                             &sc->sc_ah->channels[sc->chan_idx]);
+       ath_update_chainmask(sc, sc->chan_is_ht);
+       if (ath_set_channel(sc, aphy->hw,
+                           &sc->sc_ah->channels[sc->chan_idx]) < 0) {
+               printk(KERN_DEBUG "ath9k: Failed to set channel for new "
+                      "virtual wiphy\n");
+               mutex_unlock(&sc->mutex);
+               return;
+       }
+       mutex_unlock(&sc->mutex);
+
+       ath9k_wiphy_unpause_channel(sc);
+}
+
 /*
  * ath9k version of ieee80211_tx_status() for TX frames that are generated
  * internally in the driver.
                         */
                }
                aphy->state = ATH_WIPHY_PAUSED;
+               if (!ath9k_wiphy_pausing(aphy->sc)) {
+                       /*
+                        * Drop from tasklet to work to allow mutex for channel
+                        * change.
+                        */
+                       queue_work(aphy->sc->hw->workqueue,
+                                  &aphy->sc->chan_work);
+               }
        }
 
        kfree(tx_info_priv);
        dev_kfree_skb(skb);
 }
 
+static void ath9k_mark_paused(struct ath_wiphy *aphy)
+{
+       struct ath_softc *sc = aphy->sc;
+       aphy->state = ATH_WIPHY_PAUSED;
+       if (!__ath9k_wiphy_pausing(sc))
+               queue_work(sc->hw->workqueue, &sc->chan_work);
+}
+
 static void ath9k_pause_iter(void *data, u8 *mac, struct ieee80211_vif *vif)
 {
        struct ath_wiphy *aphy = data;
        switch (vif->type) {
        case NL80211_IFTYPE_STATION:
                if (!vif->bss_conf.assoc) {
-                       aphy->state = ATH_WIPHY_PAUSED;
+                       ath9k_mark_paused(aphy);
                        break;
                }
                /* TODO: could avoid this if already in PS mode */
-               ath9k_send_nullfunc(aphy, vif, avp->bssid, 1);
+               if (ath9k_send_nullfunc(aphy, vif, avp->bssid, 1)) {
+                       printk(KERN_DEBUG "%s: failed to send PS nullfunc\n",
+                              __func__);
+                       ath9k_mark_paused(aphy);
+               }
                break;
        case NL80211_IFTYPE_AP:
                /* Beacon transmission is paused by aphy->state change */
-               aphy->state = ATH_WIPHY_PAUSED;
+               ath9k_mark_paused(aphy);
                break;
        default:
                break;
        spin_unlock_bh(&aphy->sc->wiphy_lock);
        return ret;
 }
+
+/* caller must hold wiphy_lock */
+static void __ath9k_wiphy_pause_all(struct ath_softc *sc)
+{
+       int i;
+       if (sc->pri_wiphy->state == ATH_WIPHY_ACTIVE)
+               __ath9k_wiphy_pause(sc->pri_wiphy);
+       for (i = 0; i < sc->num_sec_wiphy; i++) {
+               if (sc->sec_wiphy[i] &&
+                   sc->sec_wiphy[i]->state == ATH_WIPHY_ACTIVE)
+                       __ath9k_wiphy_pause(sc->sec_wiphy[i]);
+       }
+}
+
+int ath9k_wiphy_select(struct ath_wiphy *aphy)
+{
+       struct ath_softc *sc = aphy->sc;
+       bool now;
+
+       spin_lock_bh(&sc->wiphy_lock);
+       if (__ath9k_wiphy_pausing(sc)) {
+               spin_unlock_bh(&sc->wiphy_lock);
+               return -EBUSY; /* previous select still in progress */
+       }
+
+       /* Store the new channel */
+       sc->chan_idx = aphy->chan_idx;
+       sc->chan_is_ht = aphy->chan_is_ht;
+       sc->next_wiphy = aphy;
+
+       __ath9k_wiphy_pause_all(sc);
+       now = !__ath9k_wiphy_pausing(aphy->sc);
+       spin_unlock_bh(&sc->wiphy_lock);
+
+       if (now) {
+               /* Ready to request channel change immediately */
+               queue_work(aphy->sc->hw->workqueue, &aphy->sc->chan_work);
+       }
+
+       /*
+        * wiphys will be unpaused in ath9k_tx_status() once channel has been
+        * changed if any wiphy needs time to become paused.
+        */
+
+       return 0;
+}