]> err.no Git - linux-2.6/blobdiff - drivers/net/wireless/iwlwifi/iwl-5000.c
Merge branch 'master' of master.kernel.org:/pub/scm/linux/kernel/git/davem/net-2.6
[linux-2.6] / drivers / net / wireless / iwlwifi / iwl-5000.c
index 545970b0caa10633a69ce16693229ca8c9711cc1..b5e28b811796c5456a549e8166c1edf0b33c2cf0 100644 (file)
@@ -38,7 +38,7 @@
 #include <asm/unaligned.h>
 
 #include "iwl-eeprom.h"
-#include "iwl-4965.h"
+#include "iwl-dev.h"
 #include "iwl-core.h"
 #include "iwl-io.h"
 #include "iwl-helpers.h"
@@ -86,6 +86,38 @@ static int iwl5000_apm_init(struct iwl_priv *priv)
        return ret;
 }
 
+static void iwl5000_nic_config(struct iwl_priv *priv)
+{
+       unsigned long flags;
+       u16 radio_cfg;
+       u8 val_link;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       pci_read_config_byte(priv->pci_dev, PCI_LINK_CTRL, &val_link);
+
+       /* disable L1 entry -- workaround for pre-B1 */
+       pci_write_config_byte(priv->pci_dev, PCI_LINK_CTRL, val_link & ~0x02);
+
+       radio_cfg = iwl_eeprom_query16(priv, EEPROM_RADIO_CONFIG);
+
+       /* write radio config values to register */
+       if (EEPROM_RF_CFG_TYPE_MSK(radio_cfg) < EEPROM_5000_RF_CFG_TYPE_MAX)
+               iwl_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+                           EEPROM_RF_CFG_TYPE_MSK(radio_cfg) |
+                           EEPROM_RF_CFG_STEP_MSK(radio_cfg) |
+                           EEPROM_RF_CFG_DASH_MSK(radio_cfg));
+
+       /* set CSR_HW_CONFIG_REG for uCode use */
+       iwl_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+                   CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI |
+                   CSR_HW_IF_CONFIG_REG_BIT_MAC_SI);
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+
+
 /*
  * EEPROM
  */
@@ -125,6 +157,128 @@ static u32 eeprom_indirect_address(const struct iwl_priv *priv, u32 address)
        return (address & ADDRESS_MSK) + (offset << 1);
 }
 
+static int iwl5000_eeprom_check_version(struct iwl_priv *priv)
+{
+       u16 eeprom_ver;
+       struct iwl_eeprom_calib_hdr {
+               u8 version;
+               u8 pa_type;
+               u16 voltage;
+       } *hdr;
+
+       eeprom_ver = iwl_eeprom_query16(priv, EEPROM_VERSION);
+
+       hdr = (struct iwl_eeprom_calib_hdr *)iwl_eeprom_query_addr(priv,
+                                                       EEPROM_5000_CALIB_ALL);
+
+       if (eeprom_ver < EEPROM_5000_EEPROM_VERSION ||
+           hdr->version < EEPROM_5000_TX_POWER_VERSION)
+               goto err;
+
+       return 0;
+err:
+       IWL_ERROR("Unsuported EEPROM VER=0x%x < 0x%x CALIB=0x%x < 0x%x\n",
+                 eeprom_ver, EEPROM_5000_EEPROM_VERSION,
+                 hdr->version, EEPROM_5000_TX_POWER_VERSION);
+       return -EINVAL;
+
+}
+
+#ifdef CONFIG_IWL5000_RUN_TIME_CALIB
+
+static void iwl5000_gain_computation(struct iwl_priv *priv,
+               u32 average_noise[NUM_RX_CHAINS],
+               u16 min_average_noise_antenna_i,
+               u32 min_average_noise)
+{
+       int i;
+       s32 delta_g;
+       struct iwl_chain_noise_data *data = &priv->chain_noise_data;
+
+       /* Find Gain Code for the antennas B and C */
+       for (i = 1; i < NUM_RX_CHAINS; i++) {
+               if ((data->disconn_array[i])) {
+                       data->delta_gain_code[i] = 0;
+                       continue;
+               }
+               delta_g = (1000 * ((s32)average_noise[0] -
+                       (s32)average_noise[i])) / 1500;
+               /* bound gain by 2 bits value max, 3rd bit is sign */
+               data->delta_gain_code[i] =
+                       min(abs(delta_g), CHAIN_NOISE_MAX_DELTA_GAIN_CODE);
+
+               if (delta_g < 0)
+                       /* set negative sign */
+                       data->delta_gain_code[i] |= (1 << 2);
+       }
+
+       IWL_DEBUG_CALIB("Delta gains: ANT_B = %d  ANT_C = %d\n",
+                       data->delta_gain_code[1], data->delta_gain_code[2]);
+
+       if (!data->radio_write) {
+               struct iwl5000_calibration_chain_noise_gain_cmd cmd;
+               memset(&cmd, 0, sizeof(cmd));
+
+               cmd.op_code = IWL5000_PHY_CALIBRATE_CHAIN_NOISE_GAIN_CMD;
+               cmd.delta_gain_1 = data->delta_gain_code[1];
+               cmd.delta_gain_2 = data->delta_gain_code[2];
+               iwl_send_cmd_pdu_async(priv, REPLY_PHY_CALIBRATION_CMD,
+                       sizeof(cmd), &cmd, NULL);
+
+               data->radio_write = 1;
+               data->state = IWL_CHAIN_NOISE_CALIBRATED;
+       }
+
+       data->chain_noise_a = 0;
+       data->chain_noise_b = 0;
+       data->chain_noise_c = 0;
+       data->chain_signal_a = 0;
+       data->chain_signal_b = 0;
+       data->chain_signal_c = 0;
+       data->beacon_count = 0;
+}
+
+
+static void iwl5000_chain_noise_reset(struct iwl_priv *priv)
+{
+       struct iwl_chain_noise_data *data = &priv->chain_noise_data;
+
+       if ((data->state == IWL_CHAIN_NOISE_ALIVE) && iwl_is_associated(priv)) {
+               struct iwl5000_calibration_chain_noise_reset_cmd cmd;
+
+               memset(&cmd, 0, sizeof(cmd));
+               cmd.op_code = IWL5000_PHY_CALIBRATE_CHAIN_NOISE_RESET_CMD;
+               if (iwl_send_cmd_pdu(priv, REPLY_PHY_CALIBRATION_CMD,
+                       sizeof(cmd), &cmd))
+                       IWL_ERROR("Could not send REPLY_PHY_CALIBRATION_CMD\n");
+               data->state = IWL_CHAIN_NOISE_ACCUMULATE;
+               IWL_DEBUG_CALIB("Run chain_noise_calibrate\n");
+       }
+}
+
+static struct iwl_sensitivity_ranges iwl5000_sensitivity = {
+       .min_nrg_cck = 95,
+       .max_nrg_cck = 0,
+       .auto_corr_min_ofdm = 90,
+       .auto_corr_min_ofdm_mrc = 170,
+       .auto_corr_min_ofdm_x1 = 120,
+       .auto_corr_min_ofdm_mrc_x1 = 240,
+
+       .auto_corr_max_ofdm = 120,
+       .auto_corr_max_ofdm_mrc = 210,
+       .auto_corr_max_ofdm_x1 = 155,
+       .auto_corr_max_ofdm_mrc_x1 = 290,
+
+       .auto_corr_min_cck = 125,
+       .auto_corr_max_cck = 200,
+       .auto_corr_min_cck_mrc = 170,
+       .auto_corr_max_cck_mrc = 400,
+       .nrg_th_cck = 95,
+       .nrg_th_ofdm = 95,
+};
+
+#endif /* CONFIG_IWL5000_RUN_TIME_CALIB */
+
 static const u8 *iwl5000_eeprom_query_addr(const struct iwl_priv *priv,
                                           size_t offset)
 {
@@ -159,6 +313,9 @@ static int iwl5000_hw_set_hw_params(struct iwl_priv *priv)
        priv->hw_params.max_bsm_size = BSM_SRAM_SIZE;
        priv->hw_params.fat_channel =  BIT(IEEE80211_BAND_2GHZ) |
                                        BIT(IEEE80211_BAND_5GHZ);
+#ifdef CONFIG_IWL5000_RUN_TIME_CALIB
+       priv->hw_params.sens = &iwl5000_sensitivity;
+#endif
 
        switch (priv->hw_rev & CSR_HW_REV_TYPE_MSK) {
        case CSR_HW_REV_TYPE_5100:
@@ -166,33 +323,163 @@ static int iwl5000_hw_set_hw_params(struct iwl_priv *priv)
                priv->hw_params.tx_chains_num = 1;
                priv->hw_params.rx_chains_num = 2;
                /* FIXME: move to ANT_A, ANT_B, ANT_C enum */
-               priv->hw_params.valid_tx_ant = IWL_ANTENNA_MAIN;
-               priv->hw_params.valid_rx_ant = (IWL_ANTENNA_MAIN |
-                                               IWL_ANTENNA_AUX);
+               priv->hw_params.valid_tx_ant = ANT_A;
+               priv->hw_params.valid_rx_ant = ANT_AB;
                break;
        case CSR_HW_REV_TYPE_5300:
        case CSR_HW_REV_TYPE_5350:
                priv->hw_params.tx_chains_num = 3;
                priv->hw_params.rx_chains_num = 3;
-               /* FIXME: move to ANT_A, ANT_B, ANT_C enum */
-               priv->hw_params.valid_tx_ant = (IWL_ANTENNA_MAIN |
-                                               IWL_ANTENNA_AUX | 0x04);
-               priv->hw_params.valid_rx_ant = (IWL_ANTENNA_MAIN |
-                                               IWL_ANTENNA_AUX | 0x04);
+               priv->hw_params.valid_tx_ant = ANT_ABC;
+               priv->hw_params.valid_rx_ant = ANT_ABC;
+               break;
+       }
+
+       switch (priv->hw_rev & CSR_HW_REV_TYPE_MSK) {
+       case CSR_HW_REV_TYPE_5100:
+       case CSR_HW_REV_TYPE_5300:
+               /* 5X00 wants in Celsius */
+               priv->hw_params.ct_kill_threshold = CT_KILL_THRESHOLD;
+               break;
+       case CSR_HW_REV_TYPE_5150:
+       case CSR_HW_REV_TYPE_5350:
+               /* 5X50 wants in Kelvin */
+               priv->hw_params.ct_kill_threshold =
+                               CELSIUS_TO_KELVIN(CT_KILL_THRESHOLD);
                break;
        }
+
+       return 0;
+}
+
+static int iwl5000_alloc_shared_mem(struct iwl_priv *priv)
+{
+       priv->shared_virt = pci_alloc_consistent(priv->pci_dev,
+                                       sizeof(struct iwl5000_shared),
+                                       &priv->shared_phys);
+       if (!priv->shared_virt)
+               return -ENOMEM;
+
+       memset(priv->shared_virt, 0, sizeof(struct iwl5000_shared));
+
+       priv->rb_closed_offset = offsetof(struct iwl5000_shared, rb_closed);
+
+       return 0;
+}
+
+static void iwl5000_free_shared_mem(struct iwl_priv *priv)
+{
+       if (priv->shared_virt)
+               pci_free_consistent(priv->pci_dev,
+                                   sizeof(struct iwl5000_shared),
+                                   priv->shared_virt,
+                                   priv->shared_phys);
+}
+
+static int iwl5000_shared_mem_rx_idx(struct iwl_priv *priv)
+{
+       struct iwl5000_shared *s = priv->shared_virt;
+       return le32_to_cpu(s->rb_closed) & 0xFFF;
+}
+
+/**
+ * iwl5000_txq_update_byte_cnt_tbl - Set up entry in Tx byte-count array
+ */
+static void iwl5000_txq_update_byte_cnt_tbl(struct iwl_priv *priv,
+                                           struct iwl_tx_queue *txq,
+                                           u16 byte_cnt)
+{
+       struct iwl5000_shared *shared_data = priv->shared_virt;
+       int txq_id = txq->q.id;
+       u8 sec_ctl = 0;
+       u8 sta = 0;
+       int len;
+
+       len = byte_cnt + IWL_TX_CRC_SIZE + IWL_TX_DELIMITER_SIZE;
+
+       if (txq_id != IWL_CMD_QUEUE_NUM) {
+               sta = txq->cmd[txq->q.write_ptr].cmd.tx.sta_id;
+               sec_ctl = txq->cmd[txq->q.write_ptr].cmd.tx.sec_ctl;
+
+               switch (sec_ctl & TX_CMD_SEC_MSK) {
+               case TX_CMD_SEC_CCM:
+                       len += CCMP_MIC_LEN;
+                       break;
+               case TX_CMD_SEC_TKIP:
+                       len += TKIP_ICV_LEN;
+                       break;
+               case TX_CMD_SEC_WEP:
+                       len += WEP_IV_LEN + WEP_ICV_LEN;
+                       break;
+               }
+       }
+
+       IWL_SET_BITS16(shared_data->queues_byte_cnt_tbls[txq_id].
+                      tfd_offset[txq->q.write_ptr], byte_cnt, len);
+
+       IWL_SET_BITS16(shared_data->queues_byte_cnt_tbls[txq_id].
+                      tfd_offset[txq->q.write_ptr], sta_id, sta);
+
+       if (txq->q.write_ptr < IWL50_MAX_WIN_SIZE) {
+               IWL_SET_BITS16(shared_data->queues_byte_cnt_tbls[txq_id].
+                       tfd_offset[IWL50_QUEUE_SIZE + txq->q.write_ptr],
+                       byte_cnt, len);
+               IWL_SET_BITS16(shared_data->queues_byte_cnt_tbls[txq_id].
+                       tfd_offset[IWL50_QUEUE_SIZE + txq->q.write_ptr],
+                       sta_id, sta);
+       }
+}
+
+static u16 iwl5000_build_addsta_hcmd(const struct iwl_addsta_cmd *cmd, u8 *data)
+{
+       u16 size = (u16)sizeof(struct iwl_addsta_cmd);
+       memcpy(data, cmd, size);
+       return size;
+}
+
+
+static int iwl5000_disable_tx_fifo(struct iwl_priv *priv)
+{
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       ret = iwl_grab_nic_access(priv);
+       if (unlikely(ret)) {
+               IWL_ERROR("Tx fifo reset failed");
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return ret;
+       }
+
+       iwl_write_prph(priv, IWL50_SCD_TXFACT, 0);
+       iwl_release_nic_access(priv);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
        return 0;
 }
+
 static struct iwl_hcmd_ops iwl5000_hcmd = {
 };
 
 static struct iwl_hcmd_utils_ops iwl5000_hcmd_utils = {
+       .build_addsta_hcmd = iwl5000_build_addsta_hcmd,
+#ifdef CONFIG_IWL5000_RUN_TIME_CALIB
+       .gain_computation = iwl5000_gain_computation,
+       .chain_noise_reset = iwl5000_chain_noise_reset,
+#endif
 };
 
 static struct iwl_lib_ops iwl5000_lib = {
        .set_hw_params = iwl5000_hw_set_hw_params,
+       .alloc_shared_mem = iwl5000_alloc_shared_mem,
+       .free_shared_mem = iwl5000_free_shared_mem,
+       .shared_mem_rx_idx = iwl5000_shared_mem_rx_idx,
+       .txq_update_byte_cnt_tbl = iwl5000_txq_update_byte_cnt_tbl,
+       .disable_tx_fifo = iwl5000_disable_tx_fifo,
        .apm_ops = {
                .init = iwl5000_apm_init,
+               .config = iwl5000_nic_config,
                .set_pwr_src = iwl4965_set_pwr_src,
        },
        .eeprom_ops = {
@@ -208,6 +495,7 @@ static struct iwl_lib_ops iwl5000_lib = {
                .verify_signature  = iwlcore_eeprom_verify_signature,
                .acquire_semaphore = iwlcore_eeprom_acquire_semaphore,
                .release_semaphore = iwlcore_eeprom_release_semaphore,
+               .check_version  = iwl5000_eeprom_check_version,
                .query_addr = iwl5000_eeprom_query_addr,
        },
 };
@@ -222,6 +510,7 @@ static struct iwl_mod_params iwl50_mod_params = {
        .num_of_queues = IWL50_NUM_QUEUES,
        .enable_qos = 1,
        .amsdu_size_8K = 1,
+       .restart_fw = 1,
        /* the rest are 0 by default */
 };
 
@@ -267,5 +556,5 @@ module_param_named(qos_enable50, iwl50_mod_params.enable_qos, int, 0444);
 MODULE_PARM_DESC(qos_enable50, "enable all 50XX QoS functionality");
 module_param_named(amsdu_size_8K50, iwl50_mod_params.amsdu_size_8K, int, 0444);
 MODULE_PARM_DESC(amsdu_size_8K50, "enable 8K amsdu size in 50XX series");
-
-
+module_param_named(fw_restart50, iwl50_mod_params.restart_fw, int, 0444);
+MODULE_PARM_DESC(fw_restart50, "restart firmware in case of error");