summaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/iwlegacy/iwl-3945.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/iwlegacy/iwl-3945.c')
-rw-r--r--drivers/net/wireless/iwlegacy/iwl-3945.c2744
1 files changed, 2744 insertions, 0 deletions
diff --git a/drivers/net/wireless/iwlegacy/iwl-3945.c b/drivers/net/wireless/iwlegacy/iwl-3945.c
new file mode 100644
index 000000000000..8359594839e2
--- /dev/null
+++ b/drivers/net/wireless/iwlegacy/iwl-3945.c
@@ -0,0 +1,2744 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2003 - 2011 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
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ *
+ * The full GNU General Public License is included in this distribution in the
+ * file called LICENSE.
+ *
+ * Contact Information:
+ * Intel Linux Wireless <ilw@linux.intel.com>
+ * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+ *
+ *****************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/wireless.h>
+#include <linux/firmware.h>
+#include <linux/etherdevice.h>
+#include <asm/unaligned.h>
+#include <net/mac80211.h>
+
+#include "iwl-fh.h"
+#include "iwl-3945-fh.h"
+#include "iwl-commands.h"
+#include "iwl-sta.h"
+#include "iwl-3945.h"
+#include "iwl-eeprom.h"
+#include "iwl-core.h"
+#include "iwl-helpers.h"
+#include "iwl-led.h"
+#include "iwl-3945-led.h"
+#include "iwl-3945-debugfs.h"
+
+#define IWL_DECLARE_RATE_INFO(r, ip, in, rp, rn, pp, np) \
+ [IWL_RATE_##r##M_INDEX] = { IWL_RATE_##r##M_PLCP, \
+ IWL_RATE_##r##M_IEEE, \
+ IWL_RATE_##ip##M_INDEX, \
+ IWL_RATE_##in##M_INDEX, \
+ IWL_RATE_##rp##M_INDEX, \
+ IWL_RATE_##rn##M_INDEX, \
+ IWL_RATE_##pp##M_INDEX, \
+ IWL_RATE_##np##M_INDEX, \
+ IWL_RATE_##r##M_INDEX_TABLE, \
+ IWL_RATE_##ip##M_INDEX_TABLE }
+
+/*
+ * Parameter order:
+ * rate, prev rate, next rate, prev tgg rate, next tgg rate
+ *
+ * If there isn't a valid next or previous rate then INV is used which
+ * maps to IWL_RATE_INVALID
+ *
+ */
+const struct iwl3945_rate_info iwl3945_rates[IWL_RATE_COUNT_3945] = {
+ IWL_DECLARE_RATE_INFO(1, INV, 2, INV, 2, INV, 2), /* 1mbps */
+ IWL_DECLARE_RATE_INFO(2, 1, 5, 1, 5, 1, 5), /* 2mbps */
+ IWL_DECLARE_RATE_INFO(5, 2, 6, 2, 11, 2, 11), /*5.5mbps */
+ IWL_DECLARE_RATE_INFO(11, 9, 12, 5, 12, 5, 18), /* 11mbps */
+ IWL_DECLARE_RATE_INFO(6, 5, 9, 5, 11, 5, 11), /* 6mbps */
+ IWL_DECLARE_RATE_INFO(9, 6, 11, 5, 11, 5, 11), /* 9mbps */
+ IWL_DECLARE_RATE_INFO(12, 11, 18, 11, 18, 11, 18), /* 12mbps */
+ IWL_DECLARE_RATE_INFO(18, 12, 24, 12, 24, 11, 24), /* 18mbps */
+ IWL_DECLARE_RATE_INFO(24, 18, 36, 18, 36, 18, 36), /* 24mbps */
+ IWL_DECLARE_RATE_INFO(36, 24, 48, 24, 48, 24, 48), /* 36mbps */
+ IWL_DECLARE_RATE_INFO(48, 36, 54, 36, 54, 36, 54), /* 48mbps */
+ IWL_DECLARE_RATE_INFO(54, 48, INV, 48, INV, 48, INV),/* 54mbps */
+};
+
+static inline u8 iwl3945_get_prev_ieee_rate(u8 rate_index)
+{
+ u8 rate = iwl3945_rates[rate_index].prev_ieee;
+
+ if (rate == IWL_RATE_INVALID)
+ rate = rate_index;
+ return rate;
+}
+
+/* 1 = enable the iwl3945_disable_events() function */
+#define IWL_EVT_DISABLE (0)
+#define IWL_EVT_DISABLE_SIZE (1532/32)
+
+/**
+ * iwl3945_disable_events - Disable selected events in uCode event log
+ *
+ * Disable an event by writing "1"s into "disable"
+ * bitmap in SRAM. Bit position corresponds to Event # (id/type).
+ * Default values of 0 enable uCode events to be logged.
+ * Use for only special debugging. This function is just a placeholder as-is,
+ * you'll need to provide the special bits! ...
+ * ... and set IWL_EVT_DISABLE to 1. */
+void iwl3945_disable_events(struct iwl_priv *priv)
+{
+ int i;
+ u32 base; /* SRAM address of event log header */
+ u32 disable_ptr; /* SRAM address of event-disable bitmap array */
+ u32 array_size; /* # of u32 entries in array */
+ static const u32 evt_disable[IWL_EVT_DISABLE_SIZE] = {
+ 0x00000000, /* 31 - 0 Event id numbers */
+ 0x00000000, /* 63 - 32 */
+ 0x00000000, /* 95 - 64 */
+ 0x00000000, /* 127 - 96 */
+ 0x00000000, /* 159 - 128 */
+ 0x00000000, /* 191 - 160 */
+ 0x00000000, /* 223 - 192 */
+ 0x00000000, /* 255 - 224 */
+ 0x00000000, /* 287 - 256 */
+ 0x00000000, /* 319 - 288 */
+ 0x00000000, /* 351 - 320 */
+ 0x00000000, /* 383 - 352 */
+ 0x00000000, /* 415 - 384 */
+ 0x00000000, /* 447 - 416 */
+ 0x00000000, /* 479 - 448 */
+ 0x00000000, /* 511 - 480 */
+ 0x00000000, /* 543 - 512 */
+ 0x00000000, /* 575 - 544 */
+ 0x00000000, /* 607 - 576 */
+ 0x00000000, /* 639 - 608 */
+ 0x00000000, /* 671 - 640 */
+ 0x00000000, /* 703 - 672 */
+ 0x00000000, /* 735 - 704 */
+ 0x00000000, /* 767 - 736 */
+ 0x00000000, /* 799 - 768 */
+ 0x00000000, /* 831 - 800 */
+ 0x00000000, /* 863 - 832 */
+ 0x00000000, /* 895 - 864 */
+ 0x00000000, /* 927 - 896 */
+ 0x00000000, /* 959 - 928 */
+ 0x00000000, /* 991 - 960 */
+ 0x00000000, /* 1023 - 992 */
+ 0x00000000, /* 1055 - 1024 */
+ 0x00000000, /* 1087 - 1056 */
+ 0x00000000, /* 1119 - 1088 */
+ 0x00000000, /* 1151 - 1120 */
+ 0x00000000, /* 1183 - 1152 */
+ 0x00000000, /* 1215 - 1184 */
+ 0x00000000, /* 1247 - 1216 */
+ 0x00000000, /* 1279 - 1248 */
+ 0x00000000, /* 1311 - 1280 */
+ 0x00000000, /* 1343 - 1312 */
+ 0x00000000, /* 1375 - 1344 */
+ 0x00000000, /* 1407 - 1376 */
+ 0x00000000, /* 1439 - 1408 */
+ 0x00000000, /* 1471 - 1440 */
+ 0x00000000, /* 1503 - 1472 */
+ };
+
+ base = le32_to_cpu(priv->card_alive.log_event_table_ptr);
+ if (!iwl3945_hw_valid_rtc_data_addr(base)) {
+ IWL_ERR(priv, "Invalid event log pointer 0x%08X\n", base);
+ return;
+ }
+
+ disable_ptr = iwl_legacy_read_targ_mem(priv, base + (4 * sizeof(u32)));
+ array_size = iwl_legacy_read_targ_mem(priv, base + (5 * sizeof(u32)));
+
+ if (IWL_EVT_DISABLE && (array_size == IWL_EVT_DISABLE_SIZE)) {
+ IWL_DEBUG_INFO(priv, "Disabling selected uCode log events at 0x%x\n",
+ disable_ptr);
+ for (i = 0; i < IWL_EVT_DISABLE_SIZE; i++)
+ iwl_legacy_write_targ_mem(priv,
+ disable_ptr + (i * sizeof(u32)),
+ evt_disable[i]);
+
+ } else {
+ IWL_DEBUG_INFO(priv, "Selected uCode log events may be disabled\n");
+ IWL_DEBUG_INFO(priv, " by writing \"1\"s into disable bitmap\n");
+ IWL_DEBUG_INFO(priv, " in SRAM at 0x%x, size %d u32s\n",
+ disable_ptr, array_size);
+ }
+
+}
+
+static int iwl3945_hwrate_to_plcp_idx(u8 plcp)
+{
+ int idx;
+
+ for (idx = 0; idx < IWL_RATE_COUNT_3945; idx++)
+ if (iwl3945_rates[idx].plcp == plcp)
+ return idx;
+ return -1;
+}
+
+#ifdef CONFIG_IWLWIFI_LEGACY_DEBUG
+#define TX_STATUS_ENTRY(x) case TX_3945_STATUS_FAIL_ ## x: return #x
+
+static const char *iwl3945_get_tx_fail_reason(u32 status)
+{
+ switch (status & TX_STATUS_MSK) {
+ case TX_3945_STATUS_SUCCESS:
+ return "SUCCESS";
+ TX_STATUS_ENTRY(SHORT_LIMIT);
+ TX_STATUS_ENTRY(LONG_LIMIT);
+ TX_STATUS_ENTRY(FIFO_UNDERRUN);
+ TX_STATUS_ENTRY(MGMNT_ABORT);
+ TX_STATUS_ENTRY(NEXT_FRAG);
+ TX_STATUS_ENTRY(LIFE_EXPIRE);
+ TX_STATUS_ENTRY(DEST_PS);
+ TX_STATUS_ENTRY(ABORTED);
+ TX_STATUS_ENTRY(BT_RETRY);
+ TX_STATUS_ENTRY(STA_INVALID);
+ TX_STATUS_ENTRY(FRAG_DROPPED);
+ TX_STATUS_ENTRY(TID_DISABLE);
+ TX_STATUS_ENTRY(FRAME_FLUSHED);
+ TX_STATUS_ENTRY(INSUFFICIENT_CF_POLL);
+ TX_STATUS_ENTRY(TX_LOCKED);
+ TX_STATUS_ENTRY(NO_BEACON_ON_RADAR);
+ }
+
+ return "UNKNOWN";
+}
+#else
+static inline const char *iwl3945_get_tx_fail_reason(u32 status)
+{
+ return "";
+}
+#endif
+
+/*
+ * get ieee prev rate from rate scale table.
+ * for A and B mode we need to overright prev
+ * value
+ */
+int iwl3945_rs_next_rate(struct iwl_priv *priv, int rate)
+{
+ int next_rate = iwl3945_get_prev_ieee_rate(rate);
+
+ switch (priv->band) {
+ case IEEE80211_BAND_5GHZ:
+ if (rate == IWL_RATE_12M_INDEX)
+ next_rate = IWL_RATE_9M_INDEX;
+ else if (rate == IWL_RATE_6M_INDEX)
+ next_rate = IWL_RATE_6M_INDEX;
+ break;
+ case IEEE80211_BAND_2GHZ:
+ if (!(priv->_3945.sta_supp_rates & IWL_OFDM_RATES_MASK) &&
+ iwl_legacy_is_associated(priv, IWL_RXON_CTX_BSS)) {
+ if (rate == IWL_RATE_11M_INDEX)
+ next_rate = IWL_RATE_5M_INDEX;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return next_rate;
+}
+
+
+/**
+ * iwl3945_tx_queue_reclaim - Reclaim Tx queue entries already Tx'd
+ *
+ * When FW advances 'R' index, all entries between old and new 'R' index
+ * need to be reclaimed. As result, some free space forms. If there is
+ * enough free space (> low mark), wake the stack that feeds us.
+ */
+static void iwl3945_tx_queue_reclaim(struct iwl_priv *priv,
+ int txq_id, int index)
+{
+ struct iwl_tx_queue *txq = &priv->txq[txq_id];
+ struct iwl_queue *q = &txq->q;
+ struct iwl_tx_info *tx_info;
+
+ BUG_ON(txq_id == IWL39_CMD_QUEUE_NUM);
+
+ for (index = iwl_legacy_queue_inc_wrap(index, q->n_bd);
+ q->read_ptr != index;
+ q->read_ptr = iwl_legacy_queue_inc_wrap(q->read_ptr, q->n_bd)) {
+
+ tx_info = &txq->txb[txq->q.read_ptr];
+ ieee80211_tx_status_irqsafe(priv->hw, tx_info->skb);
+ tx_info->skb = NULL;
+ priv->cfg->ops->lib->txq_free_tfd(priv, txq);
+ }
+
+ if (iwl_legacy_queue_space(q) > q->low_mark && (txq_id >= 0) &&
+ (txq_id != IWL39_CMD_QUEUE_NUM) &&
+ priv->mac80211_registered)
+ iwl_legacy_wake_queue(priv, txq);
+}
+
+/**
+ * iwl3945_rx_reply_tx - Handle Tx response
+ */
+static void iwl3945_rx_reply_tx(struct iwl_priv *priv,
+ struct iwl_rx_mem_buffer *rxb)
+{
+ struct iwl_rx_packet *pkt = rxb_addr(rxb);
+ u16 sequence = le16_to_cpu(pkt->hdr.sequence);
+ int txq_id = SEQ_TO_QUEUE(sequence);
+ int index = SEQ_TO_INDEX(sequence);
+ struct iwl_tx_queue *txq = &priv->txq[txq_id];
+ struct ieee80211_tx_info *info;
+ struct iwl3945_tx_resp *tx_resp = (void *)&pkt->u.raw[0];
+ u32 status = le32_to_cpu(tx_resp->status);
+ int rate_idx;
+ int fail;
+
+ if ((index >= txq->q.n_bd) || (iwl_legacy_queue_used(&txq->q, index) == 0)) {
+ IWL_ERR(priv, "Read index for DMA queue txq_id (%d) index %d "
+ "is out of range [0-%d] %d %d\n", txq_id,
+ index, txq->q.n_bd, txq->q.write_ptr,
+ txq->q.read_ptr);
+ return;
+ }
+
+ txq->time_stamp = jiffies;
+ info = IEEE80211_SKB_CB(txq->txb[txq->q.read_ptr].skb);
+ ieee80211_tx_info_clear_status(info);
+
+ /* Fill the MRR chain with some info about on-chip retransmissions */
+ rate_idx = iwl3945_hwrate_to_plcp_idx(tx_resp->rate);
+ if (info->band == IEEE80211_BAND_5GHZ)
+ rate_idx -= IWL_FIRST_OFDM_RATE;
+
+ fail = tx_resp->failure_frame;
+
+ info->status.rates[0].idx = rate_idx;
+ info->status.rates[0].count = fail + 1; /* add final attempt */
+
+ /* tx_status->rts_retry_count = tx_resp->failure_rts; */
+ info->flags |= ((status & TX_STATUS_MSK) == TX_STATUS_SUCCESS) ?
+ IEEE80211_TX_STAT_ACK : 0;
+
+ IWL_DEBUG_TX(priv, "Tx queue %d Status %s (0x%08x) plcp rate %d retries %d\n",
+ txq_id, iwl3945_get_tx_fail_reason(status), status,
+ tx_resp->rate, tx_resp->failure_frame);
+
+ IWL_DEBUG_TX_REPLY(priv, "Tx queue reclaim %d\n", index);
+ iwl3945_tx_queue_reclaim(priv, txq_id, index);
+
+ if (status & TX_ABORT_REQUIRED_MSK)
+ IWL_ERR(priv, "TODO: Implement Tx ABORT REQUIRED!!!\n");
+}
+
+
+
+/*****************************************************************************
+ *
+ * Intel PRO/Wireless 3945ABG/BG Network Connection
+ *
+ * RX handler implementations
+ *
+ *****************************************************************************/
+#ifdef CONFIG_IWLWIFI_LEGACY_DEBUGFS
+static void iwl3945_accumulative_statistics(struct iwl_priv *priv,
+ __le32 *stats)
+{
+ int i;
+ __le32 *prev_stats;
+ u32 *accum_stats;
+ u32 *delta, *max_delta;
+
+ prev_stats = (__le32 *)&priv->_3945.statistics;
+ accum_stats = (u32 *)&priv->_3945.accum_statistics;
+ delta = (u32 *)&priv->_3945.delta_statistics;
+ max_delta = (u32 *)&priv->_3945.max_delta;
+
+ for (i = sizeof(__le32); i < sizeof(struct iwl3945_notif_statistics);
+ i += sizeof(__le32), stats++, prev_stats++, delta++,
+ max_delta++, accum_stats++) {
+ if (le32_to_cpu(*stats) > le32_to_cpu(*prev_stats)) {
+ *delta = (le32_to_cpu(*stats) -
+ le32_to_cpu(*prev_stats));
+ *accum_stats += *delta;
+ if (*delta > *max_delta)
+ *max_delta = *delta;
+ }
+ }
+
+ /* reset accumulative statistics for "no-counter" type statistics */
+ priv->_3945.accum_statistics.general.temperature =
+ priv->_3945.statistics.general.temperature;
+ priv->_3945.accum_statistics.general.ttl_timestamp =
+ priv->_3945.statistics.general.ttl_timestamp;
+}
+#endif
+
+void iwl3945_hw_rx_statistics(struct iwl_priv *priv,
+ struct iwl_rx_mem_buffer *rxb)
+{
+ struct iwl_rx_packet *pkt = rxb_addr(rxb);
+
+ IWL_DEBUG_RX(priv, "Statistics notification received (%d vs %d).\n",
+ (int)sizeof(struct iwl3945_notif_statistics),
+ le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK);
+#ifdef CONFIG_IWLWIFI_LEGACY_DEBUGFS
+ iwl3945_accumulative_statistics(priv, (__le32 *)&pkt->u.raw);
+#endif
+ iwl_legacy_recover_from_statistics(priv, pkt);
+
+ memcpy(&priv->_3945.statistics, pkt->u.raw, sizeof(priv->_3945.statistics));
+}
+
+void iwl3945_reply_statistics(struct iwl_priv *priv,
+ struct iwl_rx_mem_buffer *rxb)
+{
+ struct iwl_rx_packet *pkt = rxb_addr(rxb);
+ __le32 *flag = (__le32 *)&pkt->u.raw;
+
+ if (le32_to_cpu(*flag) & UCODE_STATISTICS_CLEAR_MSK) {
+#ifdef CONFIG_IWLWIFI_LEGACY_DEBUGFS
+ memset(&priv->_3945.accum_statistics, 0,
+ sizeof(struct iwl3945_notif_statistics));
+ memset(&priv->_3945.delta_statistics, 0,
+ sizeof(struct iwl3945_notif_statistics));
+ memset(&priv->_3945.max_delta, 0,
+ sizeof(struct iwl3945_notif_statistics));
+#endif
+ IWL_DEBUG_RX(priv, "Statistics have been cleared\n");
+ }
+ iwl3945_hw_rx_statistics(priv, rxb);
+}
+
+
+/******************************************************************************
+ *
+ * Misc. internal state and helper functions
+ *
+ ******************************************************************************/
+
+/* This is necessary only for a number of statistics, see the caller. */
+static int iwl3945_is_network_packet(struct iwl_priv *priv,
+ struct ieee80211_hdr *header)
+{
+ /* Filter incoming packets to determine if they are targeted toward
+ * this network, discarding packets coming from ourselves */
+ switch (priv->iw_mode) {
+ case NL80211_IFTYPE_ADHOC: /* Header: Dest. | Source | BSSID */
+ /* packets to our IBSS update information */
+ return !compare_ether_addr(header->addr3, priv->bssid);
+ case NL80211_IFTYPE_STATION: /* Header: Dest. | AP{BSSID} | Source */
+ /* packets to our IBSS update information */
+ return !compare_ether_addr(header->addr2, priv->bssid);
+ default:
+ return 1;
+ }
+}
+
+static void iwl3945_pass_packet_to_mac80211(struct iwl_priv *priv,
+ struct iwl_rx_mem_buffer *rxb,
+ struct ieee80211_rx_status *stats)
+{
+ struct iwl_rx_packet *pkt = rxb_addr(rxb);
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)IWL_RX_DATA(pkt);
+ struct iwl3945_rx_frame_hdr *rx_hdr = IWL_RX_HDR(pkt);
+ struct iwl3945_rx_frame_end *rx_end = IWL_RX_END(pkt);
+ u16 len = le16_to_cpu(rx_hdr->len);
+ struct sk_buff *skb;
+ __le16 fc = hdr->frame_control;
+
+ /* We received data from the HW, so stop the watchdog */
+ if (unlikely(len + IWL39_RX_FRAME_SIZE >
+ PAGE_SIZE << priv->hw_params.rx_page_order)) {
+ IWL_DEBUG_DROP(priv, "Corruption detected!\n");
+ return;
+ }
+
+ /* We only process data packets if the interface is open */
+ if (unlikely(!priv->is_open)) {
+ IWL_DEBUG_DROP_LIMIT(priv,
+ "Dropping packet while interface is not open.\n");
+ return;
+ }
+
+ skb = dev_alloc_skb(128);
+ if (!skb) {
+ IWL_ERR(priv, "dev_alloc_skb failed\n");
+ return;
+ }
+
+ if (!iwl3945_mod_params.sw_crypto)
+ iwl_legacy_set_decrypted_flag(priv,
+ (struct ieee80211_hdr *)rxb_addr(rxb),
+ le32_to_cpu(rx_end->status), stats);
+
+ skb_add_rx_frag(skb, 0, rxb->page,
+ (void *)rx_hdr->payload - (void *)pkt, len);
+
+ iwl_legacy_update_stats(priv, false, fc, len);
+ memcpy(IEEE80211_SKB_RXCB(skb), stats, sizeof(*stats));
+
+ ieee80211_rx(priv->hw, skb);
+ priv->alloc_rxb_page--;
+ rxb->page = NULL;
+}
+
+#define IWL_DELAY_NEXT_SCAN_AFTER_ASSOC (HZ*6)
+
+static void iwl3945_rx_reply_rx(struct iwl_priv *priv,
+ struct iwl_rx_mem_buffer *rxb)
+{
+ struct ieee80211_hdr *header;
+ struct ieee80211_rx_status rx_status;
+ struct iwl_rx_packet *pkt = rxb_addr(rxb);
+ struct iwl3945_rx_frame_stats *rx_stats = IWL_RX_STATS(pkt);
+ struct iwl3945_rx_frame_hdr *rx_hdr = IWL_RX_HDR(pkt);
+ struct iwl3945_rx_frame_end *rx_end = IWL_RX_END(pkt);
+ u16 rx_stats_sig_avg __maybe_unused = le16_to_cpu(rx_stats->sig_avg);
+ u16 rx_stats_noise_diff __maybe_unused = le16_to_cpu(rx_stats->noise_diff);
+ u8 network_packet;
+
+ rx_status.flag = 0;
+ rx_status.mactime = le64_to_cpu(rx_end->timestamp);
+ rx_status.band = (rx_hdr->phy_flags & RX_RES_PHY_FLAGS_BAND_24_MSK) ?
+ IEEE80211_BAND_2GHZ : IEEE80211_BAND_5GHZ;
+ rx_status.freq =
+ ieee80211_channel_to_frequency(le16_to_cpu(rx_hdr->channel),
+ rx_status.band);
+
+ rx_status.rate_idx = iwl3945_hwrate_to_plcp_idx(rx_hdr->rate);
+ if (rx_status.band == IEEE80211_BAND_5GHZ)
+ rx_status.rate_idx -= IWL_FIRST_OFDM_RATE;
+
+ rx_status.antenna = (le16_to_cpu(rx_hdr->phy_flags) &
+ RX_RES_PHY_FLAGS_ANTENNA_MSK) >> 4;
+
+ /* set the preamble flag if appropriate */
+ if (rx_hdr->phy_flags & RX_RES_PHY_FLAGS_SHORT_PREAMBLE_MSK)
+ rx_status.flag |= RX_FLAG_SHORTPRE;
+
+ if ((unlikely(rx_stats->phy_count > 20))) {
+ IWL_DEBUG_DROP(priv, "dsp size out of range [0,20]: %d/n",
+ rx_stats->phy_count);
+ return;
+ }
+
+ if (!(rx_end->status & RX_RES_STATUS_NO_CRC32_ERROR)
+ || !(rx_end->status & RX_RES_STATUS_NO_RXE_OVERFLOW)) {
+ IWL_DEBUG_RX(priv, "Bad CRC or FIFO: 0x%08X.\n", rx_end->status);
+ return;
+ }
+
+
+
+ /* Convert 3945's rssi indicator to dBm */
+ rx_status.signal = rx_stats->rssi - IWL39_RSSI_OFFSET;
+
+ IWL_DEBUG_STATS(priv, "Rssi %d sig_avg %d noise_diff %d\n",
+ rx_status.signal, rx_stats_sig_avg,
+ rx_stats_noise_diff);
+
+ header = (struct ieee80211_hdr *)IWL_RX_DATA(pkt);
+
+ network_packet = iwl3945_is_network_packet(priv, header);
+
+ IWL_DEBUG_STATS_LIMIT(priv, "[%c] %d RSSI:%d Signal:%u, Rate:%u\n",
+ network_packet ? '*' : ' ',
+ le16_to_cpu(rx_hdr->channel),
+ rx_status.signal, rx_status.signal,
+ rx_status.rate_idx);
+
+ iwl_legacy_dbg_log_rx_data_frame(priv, le16_to_cpu(rx_hdr->len),
+ header);
+
+ if (network_packet) {
+ priv->_3945.last_beacon_time =
+ le32_to_cpu(rx_end->beacon_timestamp);
+ priv->_3945.last_tsf = le64_to_cpu(rx_end->timestamp);
+ priv->_3945.last_rx_rssi = rx_status.signal;
+ }
+
+ iwl3945_pass_packet_to_mac80211(priv, rxb, &rx_status);
+}
+
+int iwl3945_hw_txq_attach_buf_to_tfd(struct iwl_priv *priv,
+ struct iwl_tx_queue *txq,
+ dma_addr_t addr, u16 len, u8 reset, u8 pad)
+{
+ int count;
+ struct iwl_queue *q;
+ struct iwl3945_tfd *tfd, *tfd_tmp;
+
+ q = &txq->q;
+ tfd_tmp = (struct iwl3945_tfd *)txq->tfds;
+ tfd = &tfd_tmp[q->write_ptr];
+
+ if (reset)
+ memset(tfd, 0, sizeof(*tfd));
+
+ count = TFD_CTL_COUNT_GET(le32_to_cpu(tfd->control_flags));
+
+ if ((count >= NUM_TFD_CHUNKS) || (count < 0)) {
+ IWL_ERR(priv, "Error can not send more than %d chunks\n",
+ NUM_TFD_CHUNKS);
+ return -EINVAL;
+ }
+
+ tfd->tbs[count].addr = cpu_to_le32(addr);
+ tfd->tbs[count].len = cpu_to_le32(len);
+
+ count++;
+
+ tfd->control_flags = cpu_to_le32(TFD_CTL_COUNT_SET(count) |
+ TFD_CTL_PAD_SET(pad));
+
+ return 0;
+}
+
+/**
+ * iwl3945_hw_txq_free_tfd - Free one TFD, those at index [txq->q.read_ptr]
+ *
+ * Does NOT advance any indexes
+ */
+void iwl3945_hw_txq_free_tfd(struct iwl_priv *priv, struct iwl_tx_queue *txq)
+{
+ struct iwl3945_tfd *tfd_tmp = (struct iwl3945_tfd *)txq->tfds;
+ int index = txq->q.read_ptr;
+ struct iwl3945_tfd *tfd = &tfd_tmp[index];
+ struct pci_dev *dev = priv->pci_dev;
+ int i;
+ int counter;
+
+ /* sanity check */
+ counter = TFD_CTL_COUNT_GET(le32_to_cpu(tfd->control_flags));
+ if (counter > NUM_TFD_CHUNKS) {
+ IWL_ERR(priv, "Too many chunks: %i\n", counter);
+ /* @todo issue fatal error, it is quite serious situation */
+ return;
+ }
+
+ /* Unmap tx_cmd */
+ if (counter)
+ pci_unmap_single(dev,
+ dma_unmap_addr(&txq->meta[index], mapping),
+ dma_unmap_len(&txq->meta[index], len),
+ PCI_DMA_TODEVICE);
+
+ /* unmap chunks if any */
+
+ for (i = 1; i < counter; i++)
+ pci_unmap_single(dev, le32_to_cpu(tfd->tbs[i].addr),
+ le32_to_cpu(tfd->tbs[i].len), PCI_DMA_TODEVICE);
+
+ /* free SKB */
+ if (txq->txb) {
+ struct sk_buff *skb;
+
+ skb = txq->txb[txq->q.read_ptr].skb;
+
+ /* can be called from irqs-disabled context */
+ if (skb) {
+ dev_kfree_skb_any(skb);
+ txq->txb[txq->q.read_ptr].skb = NULL;
+ }
+ }
+}
+
+/**
+ * iwl3945_hw_build_tx_cmd_rate - Add rate portion to TX_CMD:
+ *
+*/
+void iwl3945_hw_build_tx_cmd_rate(struct iwl_priv *priv,
+ struct iwl_device_cmd *cmd,
+ struct ieee80211_tx_info *info,
+ struct ieee80211_hdr *hdr,
+ int sta_id, int tx_id)
+{
+ u16 hw_value = ieee80211_get_tx_rate(priv->hw, info)->hw_value;
+ u16 rate_index = min(hw_value & 0xffff, IWL_RATE_COUNT_3945);
+ u16 rate_mask;
+ int rate;
+ u8 rts_retry_limit;
+ u8 data_retry_limit;
+ __le32 tx_flags;
+ __le16 fc = hdr->frame_control;
+ struct iwl3945_tx_cmd *tx_cmd = (struct iwl3945_tx_cmd *)cmd->cmd.payload;
+
+ rate = iwl3945_rates[rate_index].plcp;
+ tx_flags = tx_cmd->tx_flags;
+
+ /* We need to figure out how to get the sta->supp_rates while
+ * in this running context */
+ rate_mask = IWL_RATES_MASK_3945;
+
+ /* Set retry limit on DATA packets and Probe Responses*/
+ if (ieee80211_is_probe_resp(fc))
+ data_retry_limit = 3;
+ else
+ data_retry_limit = IWL_DEFAULT_TX_RETRY;
+ tx_cmd->data_retry_limit = data_retry_limit;
+
+ if (tx_id >= IWL39_CMD_QUEUE_NUM)
+ rts_retry_limit = 3;
+ else
+ rts_retry_limit = 7;
+
+ if (data_retry_limit < rts_retry_limit)
+ rts_retry_limit = data_retry_limit;
+ tx_cmd->rts_retry_limit = rts_retry_limit;
+
+ tx_cmd->rate = rate;
+ tx_cmd->tx_flags = tx_flags;
+
+ /* OFDM */
+ tx_cmd->supp_rates[0] =
+ ((rate_mask & IWL_OFDM_RATES_MASK) >> IWL_FIRST_OFDM_RATE) & 0xFF;
+
+ /* CCK */
+ tx_cmd->supp_rates[1] = (rate_mask & 0xF);
+
+ IWL_DEBUG_RATE(priv, "Tx sta id: %d, rate: %d (plcp), flags: 0x%4X "
+ "cck/ofdm mask: 0x%x/0x%x\n", sta_id,
+ tx_cmd->rate, le32_to_cpu(tx_cmd->tx_flags),
+ tx_cmd->supp_rates[1], tx_cmd->supp_rates[0]);
+}
+
+static u8 iwl3945_sync_sta(struct iwl_priv *priv, int sta_id, u16 tx_rate)
+{
+ unsigned long flags_spin;
+ struct iwl_station_entry *station;
+
+ if (sta_id == IWL_INVALID_STATION)
+ return IWL_INVALID_STATION;
+
+ spin_lock_irqsave(&priv->sta_lock, flags_spin);
+ station = &priv->stations[sta_id];
+
+ station->sta.sta.modify_mask = STA_MODIFY_TX_RATE_MSK;
+ station->sta.rate_n_flags = cpu_to_le16(tx_rate);
+ station->sta.mode = STA_CONTROL_MODIFY_MSK;
+ iwl_legacy_send_add_sta(priv, &station->sta, CMD_ASYNC);
+ spin_unlock_irqrestore(&priv->sta_lock, flags_spin);
+
+ IWL_DEBUG_RATE(priv, "SCALE sync station %d to rate %d\n",
+ sta_id, tx_rate);
+ return sta_id;
+}
+
+static void iwl3945_set_pwr_vmain(struct iwl_priv *priv)
+{
+/*
+ * (for documentation purposes)
+ * to set power to V_AUX, do
+
+ if (pci_pme_capable(priv->pci_dev, PCI_D3cold)) {
+ iwl_legacy_set_bits_mask_prph(priv, APMG_PS_CTRL_REG,
+ APMG_PS_CTRL_VAL_PWR_SRC_VAUX,
+ ~APMG_PS_CTRL_MSK_PWR_SRC);
+
+ iwl_poll_bit(priv, CSR_GPIO_IN,
+ CSR_GPIO_IN_VAL_VAUX_PWR_SRC,
+ CSR_GPIO_IN_BIT_AUX_POWER, 5000);
+ }
+ */
+
+ iwl_legacy_set_bits_mask_prph(priv, APMG_PS_CTRL_REG,
+ APMG_PS_CTRL_VAL_PWR_SRC_VMAIN,
+ ~APMG_PS_CTRL_MSK_PWR_SRC);
+
+ iwl_poll_bit(priv, CSR_GPIO_IN, CSR_GPIO_IN_VAL_VMAIN_PWR_SRC,
+ CSR_GPIO_IN_BIT_AUX_POWER, 5000); /* uS */
+}
+
+static int iwl3945_rx_init(struct iwl_priv *priv, struct iwl_rx_queue *rxq)
+{
+ iwl_legacy_write_direct32(priv, FH39_RCSR_RBD_BASE(0), rxq->bd_dma);
+ iwl_legacy_write_direct32(priv, FH39_RCSR_RPTR_ADDR(0),
+ rxq->rb_stts_dma);
+ iwl_legacy_write_direct32(priv, FH39_RCSR_WPTR(0), 0);
+ iwl_legacy_write_direct32(priv, FH39_RCSR_CONFIG(0),
+ FH39_RCSR_RX_CONFIG_REG_VAL_DMA_CHNL_EN_ENABLE |
+ FH39_RCSR_RX_CONFIG_REG_VAL_RDRBD_EN_ENABLE |
+ FH39_RCSR_RX_CONFIG_REG_BIT_WR_STTS_EN |
+ FH39_RCSR_RX_CONFIG_REG_VAL_MAX_FRAG_SIZE_128 |
+ (RX_QUEUE_SIZE_LOG << FH39_RCSR_RX_CONFIG_REG_POS_RBDC_SIZE) |
+ FH39_RCSR_RX_CONFIG_REG_VAL_IRQ_DEST_INT_HOST |
+ (1 << FH39_RCSR_RX_CONFIG_REG_POS_IRQ_RBTH) |
+ FH39_RCSR_RX_CONFIG_REG_VAL_MSG_MODE_FH);
+
+ /* fake read to flush all prev I/O */
+ iwl_legacy_read_direct32(priv, FH39_RSSR_CTRL);
+
+ return 0;
+}
+
+static int iwl3945_tx_reset(struct iwl_priv *priv)
+{
+
+ /* bypass mode */
+ iwl_legacy_write_prph(priv, ALM_SCD_MODE_REG, 0x2);
+
+ /* RA 0 is active */
+ iwl_legacy_write_prph(priv, ALM_SCD_ARASTAT_REG, 0x01);
+
+ /* all 6 fifo are active */
+ iwl_legacy_write_prph(priv, ALM_SCD_TXFACT_REG, 0x3f);
+
+ iwl_legacy_write_prph(priv, ALM_SCD_SBYP_MODE_1_REG, 0x010000);
+ iwl_legacy_write_prph(priv, ALM_SCD_SBYP_MODE_2_REG, 0x030002);
+ iwl_legacy_write_prph(priv, ALM_SCD_TXF4MF_REG, 0x000004);
+ iwl_legacy_write_prph(priv, ALM_SCD_TXF5MF_REG, 0x000005);
+
+ iwl_legacy_write_direct32(priv, FH39_TSSR_CBB_BASE,
+ priv->_3945.shared_phys);
+
+ iwl_legacy_write_direct32(priv, FH39_TSSR_MSG_CONFIG,
+ FH39_TSSR_TX_MSG_CONFIG_REG_VAL_SNOOP_RD_TXPD_ON |
+ FH39_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RD_TXPD_ON |
+ FH39_TSSR_TX_MSG_CONFIG_REG_VAL_MAX_FRAG_SIZE_128B |
+ FH39_TSSR_TX_MSG_CONFIG_REG_VAL_SNOOP_RD_TFD_ON |
+ FH39_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RD_CBB_ON |
+ FH39_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RSP_WAIT_TH |
+ FH39_TSSR_TX_MSG_CONFIG_REG_VAL_RSP_WAIT_TH);
+
+
+ return 0;
+}
+
+/**
+ * iwl3945_txq_ctx_reset - Reset TX queue context
+ *
+ * Destroys all DMA structures and initialize them again
+ */
+static int iwl3945_txq_ctx_reset(struct iwl_priv *priv)
+{
+ int rc;
+ int txq_id, slots_num;
+
+ iwl3945_hw_txq_ctx_free(priv);
+
+ /* allocate tx queue structure */
+ rc = iwl_legacy_alloc_txq_mem(priv);
+ if (rc)
+ return rc;
+
+ /* Tx CMD queue */
+ rc = iwl3945_tx_reset(priv);
+ if (rc)
+ goto error;
+
+ /* Tx queue(s) */
+ for (txq_id = 0; txq_id < priv->hw_params.max_txq_num; txq_id++) {
+ slots_num = (txq_id == IWL39_CMD_QUEUE_NUM) ?
+ TFD_CMD_SLOTS : TFD_TX_CMD_SLOTS;
+ rc = iwl_legacy_tx_queue_init(priv, &priv->txq[txq_id],
+ slots_num, txq_id);
+ if (rc) {
+ IWL_ERR(priv, "Tx %d queue init failed\n", txq_id);
+ goto error;
+ }
+ }
+
+ return rc;
+
+ error:
+ iwl3945_hw_txq_ctx_free(priv);
+ return rc;
+}
+
+
+/*
+ * Start up 3945's basic functionality after it has been reset
+ * (e.g. after platform boot, or shutdown via iwl_legacy_apm_stop())
+ * NOTE: This does not load uCode nor start the embedded processor
+ */
+static int iwl3945_apm_init(struct iwl_priv *priv)
+{
+ int ret = iwl_legacy_apm_init(priv);
+
+ /* Clear APMG (NIC's internal power management) interrupts */
+ iwl_legacy_write_prph(priv, APMG_RTC_INT_MSK_REG, 0x0);
+ iwl_legacy_write_prph(priv, APMG_RTC_INT_STT_REG, 0xFFFFFFFF);
+
+ /* Reset radio chip */
+ iwl_legacy_set_bits_prph(priv, APMG_PS_CTRL_REG,
+ APMG_PS_CTRL_VAL_RESET_REQ);
+ udelay(5);
+ iwl_legacy_clear_bits_prph(priv, APMG_PS_CTRL_REG,
+ APMG_PS_CTRL_VAL_RESET_REQ);
+
+ return ret;
+}
+
+static void iwl3945_nic_config(struct iwl_priv *priv)
+{
+ struct iwl3945_eeprom *eeprom = (struct iwl3945_eeprom *)priv->eeprom;
+ unsigned long flags;
+ u8 rev_id = 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ /* Determine HW type */
+ pci_read_config_byte(priv->pci_dev, PCI_REVISION_ID, &rev_id);
+
+ IWL_DEBUG_INFO(priv, "HW Revision ID = 0x%X\n", rev_id);
+
+ if (rev_id & PCI_CFG_REV_ID_BIT_RTP)
+ IWL_DEBUG_INFO(priv, "RTP type\n");
+ else if (rev_id & PCI_CFG_REV_ID_BIT_BASIC_SKU) {
+ IWL_DEBUG_INFO(priv, "3945 RADIO-MB type\n");
+ iwl_legacy_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+ CSR39_HW_IF_CONFIG_REG_BIT_3945_MB);
+ } else {
+ IWL_DEBUG_INFO(priv, "3945 RADIO-MM type\n");
+ iwl_legacy_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+ CSR39_HW_IF_CONFIG_REG_BIT_3945_MM);
+ }
+
+ if (EEPROM_SKU_CAP_OP_MODE_MRC == eeprom->sku_cap) {
+ IWL_DEBUG_INFO(priv, "SKU OP mode is mrc\n");
+ iwl_legacy_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+ CSR39_HW_IF_CONFIG_REG_BIT_SKU_MRC);
+ } else
+ IWL_DEBUG_INFO(priv, "SKU OP mode is basic\n");
+
+ if ((eeprom->board_revision & 0xF0) == 0xD0) {
+ IWL_DEBUG_INFO(priv, "3945ABG revision is 0x%X\n",
+ eeprom->board_revision);
+ iwl_legacy_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+ CSR39_HW_IF_CONFIG_REG_BIT_BOARD_TYPE);
+ } else {
+ IWL_DEBUG_INFO(priv, "3945ABG revision is 0x%X\n",
+ eeprom->board_revision);
+ iwl_legacy_clear_bit(priv, CSR_HW_IF_CONFIG_REG,
+ CSR39_HW_IF_CONFIG_REG_BIT_BOARD_TYPE);
+ }
+
+ if (eeprom->almgor_m_version <= 1) {
+ iwl_legacy_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+ CSR39_HW_IF_CONFIG_REG_BITS_SILICON_TYPE_A);
+ IWL_DEBUG_INFO(priv, "Card M type A version is 0x%X\n",
+ eeprom->almgor_m_version);
+ } else {
+ IWL_DEBUG_INFO(priv, "Card M type B version is 0x%X\n",
+ eeprom->almgor_m_version);
+ iwl_legacy_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+ CSR39_HW_IF_CONFIG_REG_BITS_SILICON_TYPE_B);
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (eeprom->sku_cap & EEPROM_SKU_CAP_SW_RF_KILL_ENABLE)
+ IWL_DEBUG_RF_KILL(priv, "SW RF KILL supported in EEPROM.\n");
+
+ if (eeprom->sku_cap & EEPROM_SKU_CAP_HW_RF_KILL_ENABLE)
+ IWL_DEBUG_RF_KILL(priv, "HW RF KILL supported in EEPROM.\n");
+}
+
+int iwl3945_hw_nic_init(struct iwl_priv *priv)
+{
+ int rc;
+ unsigned long flags;
+ struct iwl_rx_queue *rxq = &priv->rxq;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->cfg->ops->lib->apm_ops.init(priv);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ iwl3945_set_pwr_vmain(priv);
+
+ priv->cfg->ops->lib->apm_ops.config(priv);
+
+ /* Allocate the RX queue, or reset if it is already allocated */
+ if (!rxq->bd) {
+ rc = iwl_legacy_rx_queue_alloc(priv);
+ if (rc) {
+ IWL_ERR(priv, "Unable to initialize Rx queue\n");
+ return -ENOMEM;
+ }
+ } else
+ iwl3945_rx_queue_reset(priv, rxq);
+
+ iwl3945_rx_replenish(priv);
+
+ iwl3945_rx_init(priv, rxq);
+
+
+ /* Look at using this instead:
+ rxq->need_update = 1;
+ iwl_legacy_rx_queue_update_write_ptr(priv, rxq);
+ */
+
+ iwl_legacy_write_direct32(priv, FH39_RCSR_WPTR(0), rxq->write & ~7);
+
+ rc = iwl3945_txq_ctx_reset(priv);
+ if (rc)
+ return rc;
+
+ set_bit(STATUS_INIT, &priv->status);
+
+ return 0;
+}
+
+/**
+ * iwl3945_hw_txq_ctx_free - Free TXQ Context
+ *
+ * Destroy all TX DMA queues and structures
+ */
+void iwl3945_hw_txq_ctx_free(struct iwl_priv *priv)
+{
+ int txq_id;
+
+ /* Tx queues */
+ if (priv->txq)
+ for (txq_id = 0; txq_id < priv->hw_params.max_txq_num;
+ txq_id++)
+ if (txq_id == IWL39_CMD_QUEUE_NUM)
+ iwl_legacy_cmd_queue_free(priv);
+ else
+ iwl_legacy_tx_queue_free(priv, txq_id);
+
+ /* free tx queue structure */
+ iwl_legacy_txq_mem(priv);
+}
+
+void iwl3945_hw_txq_ctx_stop(struct iwl_priv *priv)
+{
+ int txq_id;
+
+ /* stop SCD */
+ iwl_legacy_write_prph(priv, ALM_SCD_MODE_REG, 0);
+ iwl_legacy_write_prph(priv, ALM_SCD_TXFACT_REG, 0);
+
+ /* reset TFD queues */
+ for (txq_id = 0; txq_id < priv->hw_params.max_txq_num; txq_id++) {
+ iwl_legacy_write_direct32(priv, FH39_TCSR_CONFIG(txq_id), 0x0);
+ iwl_poll_direct_bit(priv, FH39_TSSR_TX_STATUS,
+ FH39_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(txq_id),
+ 1000);
+ }
+
+ iwl3945_hw_txq_ctx_free(priv);
+}
+
+/**
+ * iwl3945_hw_reg_adjust_power_by_temp
+ * return index delta into power gain settings table
+*/
+static int iwl3945_hw_reg_adjust_power_by_temp(int new_reading, int old_reading)
+{
+ return (new_reading - old_reading) * (-11) / 100;
+}
+
+/**
+ * iwl3945_hw_reg_temp_out_of_range - Keep temperature in sane range
+ */
+static inline int iwl3945_hw_reg_temp_out_of_range(int temperature)
+{
+ return ((temperature < -260) || (temperature > 25)) ? 1 : 0;
+}
+
+int iwl3945_hw_get_temperature(struct iwl_priv *priv)
+{
+ return iwl_read32(priv, CSR_UCODE_DRV_GP2);
+}
+
+/**
+ * iwl3945_hw_reg_txpower_get_temperature
+ * get the current temperature by reading from NIC
+*/
+static int iwl3945_hw_reg_txpower_get_temperature(struct iwl_priv *priv)
+{
+ struct iwl3945_eeprom *eeprom = (struct iwl3945_eeprom *)priv->eeprom;
+ int temperature;
+
+ temperature = iwl3945_hw_get_temperature(priv);
+
+ /* driver's okay range is -260 to +25.
+ * human readable okay range is 0 to +285 */
+ IWL_DEBUG_INFO(priv, "Temperature: %d\n", temperature + IWL_TEMP_CONVERT);
+
+ /* handle insane temp reading */
+ if (iwl3945_hw_reg_temp_out_of_range(temperature)) {
+ IWL_ERR(priv, "Error bad temperature value %d\n", temperature);
+
+ /* if really really hot(?),
+ * substitute the 3rd band/group's temp measured at factory */
+ if (priv->last_temperature > 100)
+ temperature = eeprom->groups[2].temperature;
+ else /* else use most recent "sane" value from driver */
+ temperature = priv->last_temperature;
+ }
+
+ return temperature; /* raw, not "human readable" */
+}
+
+/* Adjust Txpower only if temperature variance is greater than threshold.
+ *
+ * Both are lower than older versions' 9 degrees */
+#define IWL_TEMPERATURE_LIMIT_TIMER 6
+
+/**
+ * iwl3945_is_temp_calib_needed - determines if new calibration is needed
+ *
+ * records new temperature in tx_mgr->temperature.
<