iwlwifi: multiple force reset mode
[safe/jmp/linux-2.6] / drivers / net / wireless / iwlwifi / iwl-core.c
index e3b96b4..500ced4 100644 (file)
@@ -2,7 +2,7 @@
  *
  * GPL LICENSE SUMMARY
  *
- * Copyright(c) 2008 - 2009 Intel Corporation. All rights reserved.
+ * Copyright(c) 2008 - 2010 Intel Corporation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of version 2 of the GNU General Public License as
@@ -47,6 +47,26 @@ MODULE_VERSION(IWLWIFI_VERSION);
 MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR);
 MODULE_LICENSE("GPL");
 
+/*
+ * set bt_coex_active to true, uCode will do kill/defer
+ * every time the priority line is asserted (BT is sending signals on the
+ * priority line in the PCIx).
+ * set bt_coex_active to false, uCode will ignore the BT activity and
+ * perform the normal operation
+ *
+ * User might experience transmit issue on some platform due to WiFi/BT
+ * co-exist problem. The possible behaviors are:
+ *   Able to scan and finding all the available AP
+ *   Not able to associate with any AP
+ * On those platforms, WiFi communication can be restored by set
+ * "bt_coex_active" module parameter to "false"
+ *
+ * default: bt_coex_active = true (BT_COEX_ENABLE)
+ */
+static bool bt_coex_active = true;
+module_param(bt_coex_active, bool, S_IRUGO);
+MODULE_PARM_DESC(bt_coex_active, "enable wifi/bluetooth co-exist\n");
+
 static struct iwl_wimax_coex_event_entry cu_priorities[COEX_NUM_OF_EVENTS] = {
        {COEX_CU_UNASSOC_IDLE_RP, COEX_CU_UNASSOC_IDLE_WP,
         0, COEX_UNASSOC_IDLE_FLAGS},
@@ -257,8 +277,8 @@ int iwl_hw_nic_init(struct iwl_priv *priv)
        spin_lock_irqsave(&priv->lock, flags);
        priv->cfg->ops->lib->apm_ops.init(priv);
 
-       /* Set interrupt coalescing timer to 512 usecs */
-       iwl_write8(priv, CSR_INT_COALESCING, 512 / 32);
+       /* Set interrupt coalescing calibration timer to default (512 usecs) */
+       iwl_write8(priv, CSR_INT_COALESCING, IWL_HOST_INT_CALIB_TIMEOUT_DEF);
 
        spin_unlock_irqrestore(&priv->lock, flags);
 
@@ -1353,6 +1373,8 @@ void iwl_irq_handle_error(struct iwl_priv *priv)
        priv->cfg->ops->lib->dump_nic_error_log(priv);
        if (priv->cfg->ops->lib->dump_csr)
                priv->cfg->ops->lib->dump_csr(priv);
+       if (priv->cfg->ops->lib->dump_fh)
+               priv->cfg->ops->lib->dump_fh(priv, NULL, false);
        priv->cfg->ops->lib->dump_nic_event_log(priv, false, NULL, false);
 #ifdef CONFIG_IWLWIFI_DEBUG
        if (iwl_get_debug_level(priv) & IWL_DL_FW_ERRORS)
@@ -1803,6 +1825,16 @@ irqreturn_t iwl_isr_ict(int irq, void *data)
        if (val == 0xffffffff)
                val = 0;
 
+       /*
+        * this is a w/a for a h/w bug. the h/w bug may cause the Rx bit
+        * (bit 15 before shifting it to 31) to clear when using interrupt
+        * coalescing. fortunately, bits 18 and 19 stay set when this happens
+        * so we use them to decide on the real state of the Rx bit.
+        * In order words, bit 15 is set if bit 18 or bit 19 are set.
+        */
+       if (val & 0xC0000)
+               val |= 0x8000;
+
        inta = (0xff & val) | ((0xff00 & val) << 16);
        IWL_DEBUG_ISR(priv, "ISR inta 0x%08x, enabled 0x%08x ict 0x%08x\n",
                        inta, inta_mask, val);
@@ -1965,13 +1997,20 @@ EXPORT_SYMBOL(iwl_isr_legacy);
 int iwl_send_bt_config(struct iwl_priv *priv)
 {
        struct iwl_bt_cmd bt_cmd = {
-               .flags = BT_COEX_MODE_4W,
                .lead_time = BT_LEAD_TIME_DEF,
                .max_kill = BT_MAX_KILL_DEF,
                .kill_ack_mask = 0,
                .kill_cts_mask = 0,
        };
 
+       if (!bt_coex_active)
+               bt_cmd.flags = BT_COEX_DISABLE;
+       else
+               bt_cmd.flags = BT_COEX_ENABLE;
+
+       IWL_DEBUG_INFO(priv, "BT coex %s\n",
+               (bt_cmd.flags == BT_COEX_DISABLE) ? "disable" : "active");
+
        return iwl_send_cmd_pdu(priv, REPLY_BT_CONFIG,
                                sizeof(struct iwl_bt_cmd), &bt_cmd);
 }
@@ -2334,6 +2373,21 @@ static void iwl_ht_conf(struct iwl_priv *priv,
        IWL_DEBUG_MAC80211(priv, "leave\n");
 }
 
+static inline void iwl_set_no_assoc(struct iwl_priv *priv)
+{
+       priv->assoc_id = 0;
+       iwl_led_disassociate(priv);
+       /*
+        * inform the ucode that there is no longer an
+        * association and that no more packets should be
+        * sent
+        */
+       priv->staging_rxon.filter_flags &=
+               ~RXON_FILTER_ASSOC_MSK;
+       priv->staging_rxon.assoc_id = 0;
+       iwlcore_commit_rxon(priv);
+}
+
 #define IWL_DELAY_NEXT_SCAN_AFTER_ASSOC (HZ*6)
 void iwl_bss_info_changed(struct ieee80211_hw *hw,
                          struct ieee80211_vif *vif,
@@ -2465,20 +2519,8 @@ void iwl_bss_info_changed(struct ieee80211_hw *hw,
                                        IWL_DELAY_NEXT_SCAN_AFTER_ASSOC;
                        if (!iwl_is_rfkill(priv))
                                priv->cfg->ops->lib->post_associate(priv);
-               } else {
-                       priv->assoc_id = 0;
-                       iwl_led_disassociate(priv);
-
-                       /*
-                        * inform the ucode that there is no longer an
-                        * association and that no more packets should be
-                        * send
-                        */
-                       priv->staging_rxon.filter_flags &=
-                               ~RXON_FILTER_ASSOC_MSK;
-                       priv->staging_rxon.assoc_id = 0;
-                       iwlcore_commit_rxon(priv);
-               }
+               } else
+                       iwl_set_no_assoc(priv);
        }
 
        if (changes && iwl_is_associated(priv) && priv->assoc_id) {
@@ -2493,12 +2535,14 @@ void iwl_bss_info_changed(struct ieee80211_hw *hw,
                }
        }
 
-       if ((changes & BSS_CHANGED_BEACON_ENABLED) &&
-           vif->bss_conf.enable_beacon) {
-               memcpy(priv->staging_rxon.bssid_addr,
-                      bss_conf->bssid, ETH_ALEN);
-               memcpy(priv->bssid, bss_conf->bssid, ETH_ALEN);
-               iwlcore_config_ap(priv);
+       if (changes & BSS_CHANGED_BEACON_ENABLED) {
+               if (vif->bss_conf.enable_beacon) {
+                       memcpy(priv->staging_rxon.bssid_addr,
+                              bss_conf->bssid, ETH_ALEN);
+                       memcpy(priv->bssid, bss_conf->bssid, ETH_ALEN);
+                       iwlcore_config_ap(priv);
+               } else
+                       iwl_set_no_assoc(priv);
        }
 
        mutex_unlock(&priv->mutex);
@@ -2584,44 +2628,43 @@ int iwl_set_mode(struct iwl_priv *priv, int mode)
 EXPORT_SYMBOL(iwl_set_mode);
 
 int iwl_mac_add_interface(struct ieee80211_hw *hw,
-                                struct ieee80211_if_init_conf *conf)
+                                struct ieee80211_vif *vif)
 {
        struct iwl_priv *priv = hw->priv;
-       unsigned long flags;
+       int err = 0;
+
+       IWL_DEBUG_MAC80211(priv, "enter: type %d\n", vif->type);
 
-       IWL_DEBUG_MAC80211(priv, "enter: type %d\n", conf->type);
+       mutex_lock(&priv->mutex);
 
        if (priv->vif) {
                IWL_DEBUG_MAC80211(priv, "leave - vif != NULL\n");
-               return -EOPNOTSUPP;
+               err = -EOPNOTSUPP;
+               goto out;
        }
 
-       spin_lock_irqsave(&priv->lock, flags);
-       priv->vif = conf->vif;
-       priv->iw_mode = conf->type;
-
-       spin_unlock_irqrestore(&priv->lock, flags);
+       priv->vif = vif;
+       priv->iw_mode = vif->type;
 
-       mutex_lock(&priv->mutex);
-
-       if (conf->mac_addr) {
-               IWL_DEBUG_MAC80211(priv, "Set %pM\n", conf->mac_addr);
-               memcpy(priv->mac_addr, conf->mac_addr, ETH_ALEN);
+       if (vif->addr) {
+               IWL_DEBUG_MAC80211(priv, "Set %pM\n", vif->addr);
+               memcpy(priv->mac_addr, vif->addr, ETH_ALEN);
        }
 
-       if (iwl_set_mode(priv, conf->type) == -EAGAIN)
+       if (iwl_set_mode(priv, vif->type) == -EAGAIN)
                /* we are not ready, will run again when ready */
                set_bit(STATUS_MODE_PENDING, &priv->status);
 
+ out:
        mutex_unlock(&priv->mutex);
 
        IWL_DEBUG_MAC80211(priv, "leave\n");
-       return 0;
+       return err;
 }
 EXPORT_SYMBOL(iwl_mac_add_interface);
 
 void iwl_mac_remove_interface(struct ieee80211_hw *hw,
-                                    struct ieee80211_if_init_conf *conf)
+                                    struct ieee80211_vif *vif)
 {
        struct iwl_priv *priv = hw->priv;
 
@@ -2634,7 +2677,7 @@ void iwl_mac_remove_interface(struct ieee80211_hw *hw,
                priv->staging_rxon.filter_flags &= ~RXON_FILTER_ASSOC_MSK;
                iwlcore_commit_rxon(priv);
        }
-       if (priv->vif == conf->vif) {
+       if (priv->vif == vif) {
                priv->vif = NULL;
                memset(priv->bssid, 0, ETH_ALEN);
        }
@@ -2744,6 +2787,7 @@ int iwl_mac_config(struct ieee80211_hw *hw, u32 changed)
                if ((le16_to_cpu(priv->staging_rxon.channel) != ch))
                        priv->staging_rxon.flags = 0;
 
+               iwl_set_rxon_ht(priv, ht_conf);
                iwl_set_rxon_channel(priv, conf->channel);
 
                iwl_set_flags_for_band(priv, conf->channel->band);
@@ -2807,42 +2851,6 @@ out:
 }
 EXPORT_SYMBOL(iwl_mac_config);
 
-int iwl_mac_get_tx_stats(struct ieee80211_hw *hw,
-                        struct ieee80211_tx_queue_stats *stats)
-{
-       struct iwl_priv *priv = hw->priv;
-       int i, avail;
-       struct iwl_tx_queue *txq;
-       struct iwl_queue *q;
-       unsigned long flags;
-
-       IWL_DEBUG_MAC80211(priv, "enter\n");
-
-       if (!iwl_is_ready_rf(priv)) {
-               IWL_DEBUG_MAC80211(priv, "leave - RF not ready\n");
-               return -EIO;
-       }
-
-       spin_lock_irqsave(&priv->lock, flags);
-
-       for (i = 0; i < AC_NUM; i++) {
-               txq = &priv->txq[i];
-               q = &txq->q;
-               avail = iwl_queue_space(q);
-
-               stats[i].len = q->n_window - avail;
-               stats[i].limit = q->n_window - q->high_mark;
-               stats[i].count = q->n_window;
-
-       }
-       spin_unlock_irqrestore(&priv->lock, flags);
-
-       IWL_DEBUG_MAC80211(priv, "leave\n");
-
-       return 0;
-}
-EXPORT_SYMBOL(iwl_mac_get_tx_stats);
-
 void iwl_mac_reset_tsf(struct ieee80211_hw *hw)
 {
        struct iwl_priv *priv = hw->priv;
@@ -3263,6 +3271,133 @@ void iwl_dump_csr(struct iwl_priv *priv)
 }
 EXPORT_SYMBOL(iwl_dump_csr);
 
+const static char *get_fh_string(int cmd)
+{
+       switch (cmd) {
+               IWL_CMD(FH_RSCSR_CHNL0_STTS_WPTR_REG);
+               IWL_CMD(FH_RSCSR_CHNL0_RBDCB_BASE_REG);
+               IWL_CMD(FH_RSCSR_CHNL0_WPTR);
+               IWL_CMD(FH_MEM_RCSR_CHNL0_CONFIG_REG);
+               IWL_CMD(FH_MEM_RSSR_SHARED_CTRL_REG);
+               IWL_CMD(FH_MEM_RSSR_RX_STATUS_REG);
+               IWL_CMD(FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV);
+               IWL_CMD(FH_TSSR_TX_STATUS_REG);
+               IWL_CMD(FH_TSSR_TX_ERROR_REG);
+       default:
+               return "UNKNOWN";
+
+       }
+}
+
+int iwl_dump_fh(struct iwl_priv *priv, char **buf, bool display)
+{
+       int i;
+#ifdef CONFIG_IWLWIFI_DEBUG
+       int pos = 0;
+       size_t bufsz = 0;
+#endif
+       u32 fh_tbl[] = {
+               FH_RSCSR_CHNL0_STTS_WPTR_REG,
+               FH_RSCSR_CHNL0_RBDCB_BASE_REG,
+               FH_RSCSR_CHNL0_WPTR,
+               FH_MEM_RCSR_CHNL0_CONFIG_REG,
+               FH_MEM_RSSR_SHARED_CTRL_REG,
+               FH_MEM_RSSR_RX_STATUS_REG,
+               FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV,
+               FH_TSSR_TX_STATUS_REG,
+               FH_TSSR_TX_ERROR_REG
+       };
+#ifdef CONFIG_IWLWIFI_DEBUG
+       if (display) {
+               bufsz = ARRAY_SIZE(fh_tbl) * 48 + 40;
+               *buf = kmalloc(bufsz, GFP_KERNEL);
+               if (!*buf)
+                       return -ENOMEM;
+               pos += scnprintf(*buf + pos, bufsz - pos,
+                               "FH register values:\n");
+               for (i = 0; i < ARRAY_SIZE(fh_tbl); i++) {
+                       pos += scnprintf(*buf + pos, bufsz - pos,
+                               "  %34s: 0X%08x\n",
+                               get_fh_string(fh_tbl[i]),
+                               iwl_read_direct32(priv, fh_tbl[i]));
+               }
+               return pos;
+       }
+#endif
+       IWL_ERR(priv, "FH register values:\n");
+       for (i = 0; i <  ARRAY_SIZE(fh_tbl); i++) {
+               IWL_ERR(priv, "  %34s: 0X%08x\n",
+                       get_fh_string(fh_tbl[i]),
+                       iwl_read_direct32(priv, fh_tbl[i]));
+       }
+       return 0;
+}
+EXPORT_SYMBOL(iwl_dump_fh);
+
+static void iwl_force_rf_reset(struct iwl_priv *priv)
+{
+       if (test_bit(STATUS_EXIT_PENDING, &priv->status))
+               return;
+
+       if (!iwl_is_associated(priv)) {
+               IWL_DEBUG_SCAN(priv, "force reset rejected: not associated\n");
+               return;
+       }
+       /*
+        * There is no easy and better way to force reset the radio,
+        * the only known method is switching channel which will force to
+        * reset and tune the radio.
+        * Use internal short scan (single channel) operation to should
+        * achieve this objective.
+        * Driver should reset the radio when number of consecutive missed
+        * beacon, or any other uCode error condition detected.
+        */
+       IWL_DEBUG_INFO(priv, "perform radio reset.\n");
+       iwl_internal_short_hw_scan(priv);
+       return;
+}
+
+#define IWL_DELAY_NEXT_FORCE_RESET (HZ*3)
+
+int iwl_force_reset(struct iwl_priv *priv, int mode)
+{
+       if (test_bit(STATUS_EXIT_PENDING, &priv->status))
+               return -EINVAL;
+
+       if (priv->last_force_reset_jiffies &&
+           time_after(priv->last_force_reset_jiffies +
+                      IWL_DELAY_NEXT_FORCE_RESET, jiffies)) {
+               IWL_DEBUG_INFO(priv, "force reset rejected\n");
+               return -EAGAIN;
+       }
+
+       IWL_DEBUG_INFO(priv, "perform force reset (%d)\n", mode);
+
+       switch (mode) {
+       case IWL_RF_RESET:
+               iwl_force_rf_reset(priv);
+               break;
+       case IWL_FW_RESET:
+               IWL_ERR(priv, "On demand firmware reload\n");
+               /* Set the FW error flag -- cleared on iwl_down */
+               set_bit(STATUS_FW_ERROR, &priv->status);
+               wake_up_interruptible(&priv->wait_command_queue);
+               /*
+                * Keep the restart process from trying to send host
+                * commands by clearing the INIT status bit
+                */
+               clear_bit(STATUS_READY, &priv->status);
+               queue_work(priv->workqueue, &priv->restart);
+               break;
+       default:
+               IWL_DEBUG_INFO(priv, "invalid reset request.\n");
+               return -EINVAL;
+       }
+       priv->last_force_reset_jiffies = jiffies;
+
+       return 0;
+}
+
 #ifdef CONFIG_PM
 
 int iwl_pci_suspend(struct pci_dev *pdev, pm_message_t state)