iwlwifi: implement remain-on-channel
[pandora-kernel.git] / drivers / net / wireless / iwlwifi / iwl-agn.c
index 36335b1..51e5ea4 100644 (file)
@@ -59,6 +59,7 @@
 #include "iwl-sta.h"
 #include "iwl-agn-calib.h"
 #include "iwl-agn.h"
+#include "iwl-agn-led.h"
 
 
 /******************************************************************************
@@ -846,7 +847,7 @@ static void iwl_setup_rx_handlers(struct iwl_priv *priv)
  * the appropriate handlers, including command responses,
  * frame-received notifications, and other notifications.
  */
-void iwl_rx_handle(struct iwl_priv *priv)
+static void iwl_rx_handle(struct iwl_priv *priv)
 {
        struct iwl_rx_mem_buffer *rxb;
        struct iwl_rx_packet *pkt;
@@ -910,6 +911,27 @@ void iwl_rx_handle(struct iwl_priv *priv)
                        (pkt->hdr.cmd != STATISTICS_NOTIFICATION) &&
                        (pkt->hdr.cmd != REPLY_TX);
 
+               /*
+                * Do the notification wait before RX handlers so
+                * even if the RX handler consumes the RXB we have
+                * access to it in the notification wait entry.
+                */
+               if (!list_empty(&priv->_agn.notif_waits)) {
+                       struct iwl_notification_wait *w;
+
+                       spin_lock(&priv->_agn.notif_wait_lock);
+                       list_for_each_entry(w, &priv->_agn.notif_waits, list) {
+                               if (w->cmd == pkt->hdr.cmd) {
+                                       w->triggered = true;
+                                       if (w->fn)
+                                               w->fn(priv, pkt);
+                               }
+                       }
+                       spin_unlock(&priv->_agn.notif_wait_lock);
+
+                       wake_up_all(&priv->_agn.notif_waitq);
+               }
+
                /* Based on type of command response or notification,
                 *   handle those that need handling via function in
                 *   rx_handlers table.  See iwl_setup_rx_handlers() */
@@ -2720,8 +2742,6 @@ static void iwl_alive_start(struct iwl_priv *priv)
        /* At this point, the NIC is initialized and operational */
        iwl_rf_kill_ct_config(priv);
 
-       iwl_leds_init(priv);
-
        IWL_DEBUG_INFO(priv, "ALIVE processing complete.\n");
        wake_up_interruptible(&priv->wait_command_queue);
 
@@ -3188,6 +3208,8 @@ static int iwl_mac_setup_register(struct iwl_priv *priv,
                hw->wiphy->interface_modes |= ctx->exclusive_interface_modes;
        }
 
+       hw->wiphy->max_remain_on_channel_duration = 1000;
+
        hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY |
                            WIPHY_FLAG_DISABLE_BEACON_HINTS;
 
@@ -3213,6 +3235,8 @@ static int iwl_mac_setup_register(struct iwl_priv *priv,
                priv->hw->wiphy->bands[IEEE80211_BAND_5GHZ] =
                        &priv->bands[IEEE80211_BAND_5GHZ];
 
+       iwl_leds_init(priv);
+
        ret = ieee80211_register_hw(priv->hw);
        if (ret) {
                IWL_ERR(priv, "Failed to register hw (error %d)\n", ret);
@@ -3257,7 +3281,7 @@ int iwlagn_mac_start(struct ieee80211_hw *hw)
                }
        }
 
-       iwl_led_start(priv);
+       iwlagn_led_enable(priv);
 
 out:
        priv->is_open = 1;
@@ -3393,7 +3417,8 @@ int iwlagn_mac_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
 int iwlagn_mac_ampdu_action(struct ieee80211_hw *hw,
                            struct ieee80211_vif *vif,
                            enum ieee80211_ampdu_mlme_action action,
-                           struct ieee80211_sta *sta, u16 tid, u16 *ssn)
+                           struct ieee80211_sta *sta, u16 tid, u16 *ssn,
+                           u8 buf_size)
 {
        struct iwl_priv *priv = hw->priv;
        int ret = -EINVAL;
@@ -3703,6 +3728,95 @@ done:
        IWL_DEBUG_MAC80211(priv, "leave\n");
 }
 
+static void iwlagn_disable_roc(struct iwl_priv *priv)
+{
+       struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_PAN];
+       struct ieee80211_channel *chan = ACCESS_ONCE(priv->hw->conf.channel);
+
+       lockdep_assert_held(&priv->mutex);
+
+       if (!ctx->is_active)
+               return;
+
+       ctx->staging.dev_type = RXON_DEV_TYPE_2STA;
+       ctx->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK;
+       iwl_set_rxon_channel(priv, chan, ctx);
+       iwl_set_flags_for_band(priv, ctx, chan->band, NULL);
+
+       priv->_agn.hw_roc_channel = NULL;
+
+       iwlagn_commit_rxon(priv, ctx);
+
+       ctx->is_active = false;
+}
+
+static void iwlagn_bg_roc_done(struct work_struct *work)
+{
+       struct iwl_priv *priv = container_of(work, struct iwl_priv,
+                                            _agn.hw_roc_work.work);
+
+       mutex_lock(&priv->mutex);
+       ieee80211_remain_on_channel_expired(priv->hw);
+       iwlagn_disable_roc(priv);
+       mutex_unlock(&priv->mutex);
+}
+
+static int iwl_mac_remain_on_channel(struct ieee80211_hw *hw,
+                                    struct ieee80211_channel *channel,
+                                    enum nl80211_channel_type channel_type,
+                                    int duration)
+{
+       struct iwl_priv *priv = hw->priv;
+       int err = 0;
+
+       if (!(priv->valid_contexts & BIT(IWL_RXON_CTX_PAN)))
+               return -EOPNOTSUPP;
+
+       if (!(priv->contexts[IWL_RXON_CTX_PAN].interface_modes &
+                                       BIT(NL80211_IFTYPE_P2P_CLIENT)))
+               return -EOPNOTSUPP;
+
+       mutex_lock(&priv->mutex);
+
+       if (priv->contexts[IWL_RXON_CTX_PAN].is_active ||
+           test_bit(STATUS_SCAN_HW, &priv->status)) {
+               err = -EBUSY;
+               goto out;
+       }
+
+       priv->contexts[IWL_RXON_CTX_PAN].is_active = true;
+       priv->_agn.hw_roc_channel = channel;
+       priv->_agn.hw_roc_chantype = channel_type;
+       priv->_agn.hw_roc_duration = DIV_ROUND_UP(duration * 1000, 1024);
+       iwlagn_commit_rxon(priv, &priv->contexts[IWL_RXON_CTX_PAN]);
+       queue_delayed_work(priv->workqueue, &priv->_agn.hw_roc_work,
+                          msecs_to_jiffies(duration + 20));
+
+       msleep(20);
+       ieee80211_ready_on_channel(priv->hw);
+
+ out:
+       mutex_unlock(&priv->mutex);
+
+       return err;
+}
+
+static int iwl_mac_cancel_remain_on_channel(struct ieee80211_hw *hw)
+{
+       struct iwl_priv *priv = hw->priv;
+
+       if (!(priv->valid_contexts & BIT(IWL_RXON_CTX_PAN)))
+               return -EOPNOTSUPP;
+
+       cancel_delayed_work_sync(&priv->_agn.hw_roc_work);
+
+       mutex_lock(&priv->mutex);
+       iwlagn_disable_roc(priv);
+       mutex_unlock(&priv->mutex);
+
+       return 0;
+}
+
 /*****************************************************************************
  *
  * driver setup and teardown
@@ -3724,6 +3838,7 @@ static void iwl_setup_deferred_work(struct iwl_priv *priv)
        INIT_WORK(&priv->bt_runtime_config, iwl_bg_bt_runtime_config);
        INIT_DELAYED_WORK(&priv->init_alive_start, iwl_bg_init_alive_start);
        INIT_DELAYED_WORK(&priv->alive_start, iwl_bg_alive_start);
+       INIT_DELAYED_WORK(&priv->_agn.hw_roc_work, iwlagn_bg_roc_done);
 
        iwl_setup_scan_deferred_work(priv);
 
@@ -3892,6 +4007,8 @@ struct ieee80211_ops iwlagn_hw_ops = {
        .channel_switch = iwlagn_mac_channel_switch,
        .flush = iwlagn_mac_flush,
        .tx_last_beacon = iwl_mac_tx_last_beacon,
+       .remain_on_channel = iwl_mac_remain_on_channel,
+       .cancel_remain_on_channel = iwl_mac_cancel_remain_on_channel,
 };
 #endif
 
@@ -4266,6 +4383,9 @@ static void __devexit iwl_pci_remove(struct pci_dev *pdev)
         * we need to set STATUS_EXIT_PENDING bit.
         */
        set_bit(STATUS_EXIT_PENDING, &priv->status);
+
+       iwl_leds_exit(priv);
+
        if (priv->mac80211_registered) {
                ieee80211_unregister_hw(priv->hw);
                priv->mac80211_registered = 0;
@@ -4486,6 +4606,49 @@ static DEFINE_PCI_DEVICE_TABLE(iwl_hw_card_ids) = {
        {IWL_PCI_DEVICE(0x0896, 0x5025, iwl130_bgn_cfg)},
        {IWL_PCI_DEVICE(0x0896, 0x5027, iwl130_bg_cfg)},
 
+/* 2x00 Series */
+       {IWL_PCI_DEVICE(0x0890, 0x4022, iwl2000_2bgn_cfg)},
+       {IWL_PCI_DEVICE(0x0891, 0x4222, iwl2000_2bgn_cfg)},
+       {IWL_PCI_DEVICE(0x0890, 0x4422, iwl2000_2bgn_cfg)},
+       {IWL_PCI_DEVICE(0x0890, 0x4026, iwl2000_2bg_cfg)},
+       {IWL_PCI_DEVICE(0x0891, 0x4226, iwl2000_2bg_cfg)},
+       {IWL_PCI_DEVICE(0x0890, 0x4426, iwl2000_2bg_cfg)},
+
+/* 2x30 Series */
+       {IWL_PCI_DEVICE(0x0887, 0x4062, iwl2030_2bgn_cfg)},
+       {IWL_PCI_DEVICE(0x0888, 0x4262, iwl2030_2bgn_cfg)},
+       {IWL_PCI_DEVICE(0x0887, 0x4462, iwl2030_2bgn_cfg)},
+       {IWL_PCI_DEVICE(0x0887, 0x4066, iwl2030_2bg_cfg)},
+       {IWL_PCI_DEVICE(0x0888, 0x4266, iwl2030_2bg_cfg)},
+       {IWL_PCI_DEVICE(0x0887, 0x4466, iwl2030_2bg_cfg)},
+
+/* 6x35 Series */
+       {IWL_PCI_DEVICE(0x088E, 0x4060, iwl6035_2agn_cfg)},
+       {IWL_PCI_DEVICE(0x088F, 0x4260, iwl6035_2agn_cfg)},
+       {IWL_PCI_DEVICE(0x088E, 0x4460, iwl6035_2agn_cfg)},
+       {IWL_PCI_DEVICE(0x088E, 0x4064, iwl6035_2abg_cfg)},
+       {IWL_PCI_DEVICE(0x088F, 0x4264, iwl6035_2abg_cfg)},
+       {IWL_PCI_DEVICE(0x088E, 0x4464, iwl6035_2abg_cfg)},
+       {IWL_PCI_DEVICE(0x088E, 0x4066, iwl6035_2bg_cfg)},
+       {IWL_PCI_DEVICE(0x088F, 0x4266, iwl6035_2bg_cfg)},
+       {IWL_PCI_DEVICE(0x088E, 0x4466, iwl6035_2bg_cfg)},
+
+/* 200 Series */
+       {IWL_PCI_DEVICE(0x0894, 0x0022, iwl200_bgn_cfg)},
+       {IWL_PCI_DEVICE(0x0895, 0x0222, iwl200_bgn_cfg)},
+       {IWL_PCI_DEVICE(0x0894, 0x0422, iwl200_bgn_cfg)},
+       {IWL_PCI_DEVICE(0x0894, 0x0026, iwl200_bg_cfg)},
+       {IWL_PCI_DEVICE(0x0895, 0x0226, iwl200_bg_cfg)},
+       {IWL_PCI_DEVICE(0x0894, 0x0426, iwl200_bg_cfg)},
+
+/* 230 Series */
+       {IWL_PCI_DEVICE(0x0892, 0x0062, iwl230_bgn_cfg)},
+       {IWL_PCI_DEVICE(0x0893, 0x0262, iwl230_bgn_cfg)},
+       {IWL_PCI_DEVICE(0x0892, 0x0462, iwl230_bgn_cfg)},
+       {IWL_PCI_DEVICE(0x0892, 0x0066, iwl230_bg_cfg)},
+       {IWL_PCI_DEVICE(0x0893, 0x0266, iwl230_bg_cfg)},
+       {IWL_PCI_DEVICE(0x0892, 0x0466, iwl230_bg_cfg)},
+
 #endif /* CONFIG_IWL5000 */
 
        {0}