[IWLWIFI]: add iwlwifi wireless drivers
Zhu Yi [Wed, 26 Sep 2007 00:54:57 +0000 (17:54 -0700)]
This patch adds the mac80211 based wireless drivers for the Intel
PRO/Wireless 3945ABG/BG Network Connection and Intel Wireless WiFi
Link AGN (4965) adapters.

[ Move driver into it's own directory -DaveM ]

Signed-off-by: Zhu Yi <yi.zhu@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

28 files changed:
MAINTAINERS
drivers/net/wireless/Kconfig
drivers/net/wireless/Makefile
drivers/net/wireless/iwlwifi/Kconfig [new file with mode: 0644]
drivers/net/wireless/iwlwifi/Makefile [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-3945-hw.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-3945-rs.c [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-3945-rs.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-3945.c [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-3945.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-4965-hw.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-4965-rs.c [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-4965-rs.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-4965.c [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-4965.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-channel.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-commands.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-debug.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-eeprom.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-helpers.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-hw.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-io.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-priv.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-prph.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl-spectrum.h [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl3945-base.c [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwl4965-base.c [new file with mode: 0644]
drivers/net/wireless/iwlwifi/iwlwifi.h [new file with mode: 0644]

index ef94028..934afd3 100644 (file)
@@ -2080,6 +2080,15 @@ L:       http://lists.sourceforge.net/mailman/listinfo/ipw2100-devel
 W:     http://ipw2200.sourceforge.net
 S:     Supported
 
+INTEL WIRELESS WIFI LINK (iwlwifi)
+P:     Zhu Yi
+M:     yi.zhu@intel.com
+L:     linux-wireless@vger.kernel.org
+L:     ipw3945-devel@lists.sourceforge.net
+W:     http://intellinuxwireless.org
+T:     git git://intellinuxwireless.org/repos/iwlwifi
+S:     Supported
+
 IOC3 ETHERNET DRIVER
 P:     Ralf Baechle
 M:     ralf@linux-mips.org
index c210265..085ba13 100644 (file)
@@ -577,6 +577,7 @@ config ADM8211
 
          Thanks to Infineon-ADMtek for their support of this driver.
 
+source "drivers/net/wireless/iwlwifi/Kconfig"
 source "drivers/net/wireless/hostap/Kconfig"
 source "drivers/net/wireless/bcm43xx/Kconfig"
 source "drivers/net/wireless/b43/Kconfig"
index d8dd907..351024f 100644 (file)
@@ -51,3 +51,5 @@ rtl8187-objs          := rtl8187_dev.o rtl8187_rtl8225.o
 obj-$(CONFIG_RTL8187)  += rtl8187.o
 
 obj-$(CONFIG_ADM8211)  += adm8211.o
+
+obj-$(CONFIG_IWLWIFI)  += iwlwifi/
diff --git a/drivers/net/wireless/iwlwifi/Kconfig b/drivers/net/wireless/iwlwifi/Kconfig
new file mode 100644 (file)
index 0000000..25cfc6c
--- /dev/null
@@ -0,0 +1,128 @@
+config IWLWIFI
+       bool "Intel Wireless WiFi Link Drivers"
+       depends on PCI && MAC80211 && WLAN_80211 && EXPERIMENTAL
+       select FW_LOADER
+       default n
+       ---help---
+         Select to enable drivers based on the iwlwifi project.  This
+         project provides a common foundation for Intel's wireless
+         drivers designed to use the mac80211 subsystem.
+
+         See <file:Documentation/networking/README.iwlwifi> for
+         information on the capabilities currently enabled in this
+         driver and for tips for debugging issues and problems.
+
+config IWLWIFI_DEBUG
+       bool "Enable full debugging output in iwlwifi drivers"
+       depends on IWLWIFI
+       default y
+       ---help---
+         This option will enable debug tracing output for the iwlwifi
+         drivers.
+
+         This will result in the kernel module being ~100k larger.  You can
+         control which debug output is sent to the kernel log by setting the
+         value in
+
+                 /sys/bus/pci/drivers/${DRIVER}/debug_level
+
+         This entry will only exist if this option is enabled.
+
+         To set a value, simply echo an 8-byte hex value to the same file:
+
+                 % echo 0x43fff > /sys/bus/pci/drivers/${DRIVER}/debug_level
+
+         You can find the list of debug mask values in:
+                 drivers/net/wireless/mac80211/iwlwifi/iwl-debug.h
+
+         If this is your first time using this driver, you should say Y here
+         as the debug information can assist others in helping you resolve
+         any problems you may encounter.
+
+config IWLWIFI_SENSITIVITY
+       bool "Enable Sensitivity Calibration in iwlwifi drivers"
+       depends on IWLWIFI
+       default y
+       ---help---
+         This option will enable sensitivity calibration for the iwlwifi
+         drivers.
+
+config IWLWIFI_SPECTRUM_MEASUREMENT
+       bool "Enable Spectrum Measurement in iwlwifi drivers"
+       depends on IWLWIFI
+       default y
+       ---help---
+         This option will enable spectrum measurement for the iwlwifi drivers.
+
+config IWLWIFI_QOS
+       bool "Enable Wireless QoS in iwlwifi drivers"
+       depends on IWLWIFI
+       default y
+       ---help---
+         This option will enable wireless quality of service (QoS) for the
+         iwlwifi drivers.
+
+config IWLWIFI_HT
+       bool "Enable 802.11n HT features in iwlwifi drivers"
+       depends on EXPERIMENTAL
+       depends on IWLWIFI && MAC80211_HT
+       default n
+       ---help---
+         This option enables IEEE 802.11n High Throughput features
+         for the iwlwifi drivers.
+
+config IWL4965
+       tristate "Intel Wireless WiFi 4965AGN"
+       depends on m && IWLWIFI && EXPERIMENTAL
+       default m
+       ---help---
+         Select to build the driver supporting the:
+
+         Intel Wireless WiFi Link 4965AGN
+
+         This driver uses the kernel's mac80211 subsystem.
+
+         See <file:Documentation/networking/README.iwlwifi> for
+         information on the capabilities currently enabled in this
+         driver and for tips for debugging any issues or problems.
+
+         In order to use this driver, you will need a microcode (uCode)
+         image for it. You can obtain the microcode from:
+
+                 <http://intellinuxwireless.org/>.
+
+         See the above referenced README.iwlwifi for information on where
+         to install the microcode images.
+
+         If you want to compile the driver as a module ( = code which can be
+         inserted in and remvoed from the running kernel whenever you want),
+         say M here and read <file:Documentation/modules.txt>.  The module
+         will be called iwl4965.ko.
+
+config IWL3945
+       tristate "Intel PRO/Wireless 3945ABG/BG Network Connection"
+       depends on m && IWLWIFI && EXPERIMENTAL
+       default m
+       ---help---
+         Select to build the driver supporting the:
+
+         Intel PRO/Wireless 3945ABG/BG Network Connection
+
+         This driver uses the kernel's mac80211 subsystem.
+
+         See <file:Documentation/networking/README.iwlwifi> for
+         information on the capabilities currently enabled in this
+         driver and for tips for debugging any issues or problems.
+
+         In order to use this driver, you will need a microcode (uCode)
+         image for it. You can obtain the microcode from:
+
+                 <http://intellinuxwireless.org/>.
+
+         See the above referenced README.iwlwifi for information on where
+         to install the microcode images.
+
+         If you want to compile the driver as a module ( = code which can be
+         inserted in and remvoed from the running kernel whenever you want),
+         say M here and read <file:Documentation/modules.txt>.  The module
+         will be called iwl3945.ko.
diff --git a/drivers/net/wireless/iwlwifi/Makefile b/drivers/net/wireless/iwlwifi/Makefile
new file mode 100644 (file)
index 0000000..03837ff
--- /dev/null
@@ -0,0 +1,11 @@
+obj-$(CONFIG_IWL3945)  += iwl3945.o
+iwl3945-objs           = iwl3945-base.o iwl-3945.o iwl-3945-rs.o
+CFLAGS_iwl3945-base.o  = -DIWL=3945
+CFLAGS_iwl-3945.o      = -DIWL=3945
+CFLAGS_iwl-3945-rs.o   = -DIWL=3945
+
+obj-$(CONFIG_IWL4965)  += iwl4965.o
+iwl4965-objs           = iwl4965-base.o iwl-4965.o iwl-4965-rs.o
+CFLAGS_iwl4965-base.o  = -DIWL=4965
+CFLAGS_iwl-4965.o      = -DIWL=4965
+CFLAGS_iwl-4965-rs.o   = -DIWL=4965
diff --git a/drivers/net/wireless/iwlwifi/iwl-3945-hw.h b/drivers/net/wireless/iwlwifi/iwl-3945-hw.h
new file mode 100644 (file)
index 0000000..fb5f064
--- /dev/null
@@ -0,0 +1,118 @@
+/******************************************************************************
+ *
+ * This file is provided under a dual BSD/GPLv2 license.  When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2005 - 2007 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 Geeral 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.GPL.
+ *
+ * Contact Information:
+ * James P. Ketrenos <ipw2100-admin@linux.intel.com>
+ * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2005 - 2007 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name Intel Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *****************************************************************************/
+
+#ifndef __iwl_3945_hw__
+#define __iwl_3945_hw__
+
+#define IWL_RX_BUF_SIZE 3000
+/* card static random access memory (SRAM) for processor data and instructs */
+#define ALM_RTC_INST_UPPER_BOUND               (0x014000)
+#define ALM_RTC_DATA_UPPER_BOUND               (0x808000)
+
+#define ALM_RTC_INST_SIZE (ALM_RTC_INST_UPPER_BOUND - RTC_INST_LOWER_BOUND)
+#define ALM_RTC_DATA_SIZE (ALM_RTC_DATA_UPPER_BOUND - RTC_DATA_LOWER_BOUND)
+
+#define IWL_MAX_BSM_SIZE ALM_RTC_INST_SIZE
+#define IWL_MAX_INST_SIZE ALM_RTC_INST_SIZE
+#define IWL_MAX_DATA_SIZE ALM_RTC_DATA_SIZE
+#define IWL_MAX_NUM_QUEUES     8
+
+static inline int iwl_hw_valid_rtc_data_addr(u32 addr)
+{
+       return (addr >= RTC_DATA_LOWER_BOUND) &&
+              (addr < ALM_RTC_DATA_UPPER_BOUND);
+}
+
+/* Base physical address of iwl_shared is provided to FH_TSSR_CBB_BASE
+ * and &iwl_shared.rx_read_ptr[0] is provided to FH_RCSR_RPTR_ADDR(0) */
+struct iwl_shared {
+       __le32 tx_base_ptr[8];
+       __le32 rx_read_ptr[3];
+} __attribute__ ((packed));
+
+struct iwl_tfd_frame_data {
+       __le32 addr;
+       __le32 len;
+} __attribute__ ((packed));
+
+struct iwl_tfd_frame {
+       __le32 control_flags;
+       struct iwl_tfd_frame_data pa[4];
+       u8 reserved[28];
+} __attribute__ ((packed));
+
+static inline u8 iwl_hw_get_rate(__le16 rate_n_flags)
+{
+       return le16_to_cpu(rate_n_flags) & 0xFF;
+}
+
+static inline u16 iwl_hw_get_rate_n_flags(__le16 rate_n_flags)
+{
+       return le16_to_cpu(rate_n_flags);
+}
+
+static inline __le16 iwl_hw_set_rate_n_flags(u8 rate, u16 flags)
+{
+       return cpu_to_le16((u16)rate|flags);
+}
+#endif
diff --git a/drivers/net/wireless/iwlwifi/iwl-3945-rs.c b/drivers/net/wireless/iwlwifi/iwl-3945-rs.c
new file mode 100644 (file)
index 0000000..a4f4c87
--- /dev/null
@@ -0,0 +1,979 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2005 - 2007 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:
+ * James P. Ketrenos <ipw2100-admin@linux.intel.com>
+ * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+ *
+ *****************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/wireless.h>
+#include <net/mac80211.h>
+#include <net/ieee80211.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/delay.h>
+
+#include <linux/workqueue.h>
+
+#include <net/mac80211.h>
+#include <linux/wireless.h>
+
+#include "../net/mac80211/ieee80211_rate.h"
+
+#include "iwlwifi.h"
+
+#define RS_NAME "iwl-3945-rs"
+
+struct iwl_rate_scale_data {
+       u64 data;
+       s32 success_counter;
+       s32 success_ratio;
+       s32 counter;
+       s32 average_tpt;
+       unsigned long stamp;
+};
+
+struct iwl_rate_scale_priv {
+       spinlock_t lock;
+       s32 *expected_tpt;
+       unsigned long last_partial_flush;
+       unsigned long last_flush;
+       u32 flush_time;
+       u32 last_tx_packets;
+       u32 tx_packets;
+       u8 tgg;
+       u8 flush_pending;
+       u8 start_rate;
+       u8 ibss_sta_added;
+       struct timer_list rate_scale_flush;
+       struct iwl_rate_scale_data win[IWL_RATE_COUNT];
+};
+
+static s32 iwl_expected_tpt_g[IWL_RATE_COUNT] = {
+       0, 0, 76, 104, 130, 168, 191, 202, 7, 13, 35, 58
+};
+
+static s32 iwl_expected_tpt_g_prot[IWL_RATE_COUNT] = {
+       0, 0, 0, 80, 93, 113, 123, 125, 7, 13, 35, 58
+};
+
+static s32 iwl_expected_tpt_a[IWL_RATE_COUNT] = {
+       40, 57, 72, 98, 121, 154, 177, 186, 0, 0, 0, 0
+};
+
+static s32 iwl_expected_tpt_b[IWL_RATE_COUNT] = {
+       0, 0, 0, 0, 0, 0, 0, 0, 7, 13, 35, 58
+};
+
+struct iwl_tpt_entry {
+       s8 min_rssi;
+       u8 index;
+};
+
+static struct iwl_tpt_entry iwl_tpt_table_a[] = {
+       {-60, IWL_RATE_54M_INDEX},
+       {-64, IWL_RATE_48M_INDEX},
+       {-72, IWL_RATE_36M_INDEX},
+       {-80, IWL_RATE_24M_INDEX},
+       {-84, IWL_RATE_18M_INDEX},
+       {-85, IWL_RATE_12M_INDEX},
+       {-87, IWL_RATE_9M_INDEX},
+       {-89, IWL_RATE_6M_INDEX}
+};
+
+static struct iwl_tpt_entry iwl_tpt_table_b[] = {
+       {-86, IWL_RATE_11M_INDEX},
+       {-88, IWL_RATE_5M_INDEX},
+       {-90, IWL_RATE_2M_INDEX},
+       {-92, IWL_RATE_1M_INDEX}
+
+};
+
+static struct iwl_tpt_entry iwl_tpt_table_g[] = {
+       {-60, IWL_RATE_54M_INDEX},
+       {-64, IWL_RATE_48M_INDEX},
+       {-68, IWL_RATE_36M_INDEX},
+       {-80, IWL_RATE_24M_INDEX},
+       {-84, IWL_RATE_18M_INDEX},
+       {-85, IWL_RATE_12M_INDEX},
+       {-86, IWL_RATE_11M_INDEX},
+       {-88, IWL_RATE_5M_INDEX},
+       {-90, IWL_RATE_2M_INDEX},
+       {-92, IWL_RATE_1M_INDEX}
+};
+
+#define IWL_RATE_MAX_WINDOW          62
+#define IWL_RATE_FLUSH        (3*HZ/10)
+#define IWL_RATE_WIN_FLUSH       (HZ/2)
+#define IWL_RATE_HIGH_TH          11520
+#define IWL_RATE_MIN_FAILURE_TH       8
+#define IWL_RATE_MIN_SUCCESS_TH       8
+#define IWL_RATE_DECREASE_TH       1920
+
+static u8 iwl_get_rate_index_by_rssi(s32 rssi, u8 mode)
+{
+       u32 index = 0;
+       u32 table_size = 0;
+       struct iwl_tpt_entry *tpt_table = NULL;
+
+       if ((rssi < IWL_MIN_RSSI_VAL) || (rssi > IWL_MAX_RSSI_VAL))
+               rssi = IWL_MIN_RSSI_VAL;
+
+       switch (mode) {
+       case MODE_IEEE80211G:
+               tpt_table = iwl_tpt_table_g;
+               table_size = ARRAY_SIZE(iwl_tpt_table_g);
+               break;
+
+       case MODE_IEEE80211A:
+               tpt_table = iwl_tpt_table_a;
+               table_size = ARRAY_SIZE(iwl_tpt_table_a);
+               break;
+
+       default:
+       case MODE_IEEE80211B:
+               tpt_table = iwl_tpt_table_b;
+               table_size = ARRAY_SIZE(iwl_tpt_table_b);
+               break;
+       }
+
+       while ((index < table_size) && (rssi < tpt_table[index].min_rssi))
+               index++;
+
+       index = min(index, (table_size - 1));
+
+       return tpt_table[index].index;
+}
+
+static void iwl_clear_window(struct iwl_rate_scale_data *window)
+{
+       window->data = 0;
+       window->success_counter = 0;
+       window->success_ratio = IWL_INVALID_VALUE;
+       window->counter = 0;
+       window->average_tpt = IWL_INVALID_VALUE;
+       window->stamp = 0;
+}
+
+/**
+ * iwl_rate_scale_flush_windows - flush out the rate scale windows
+ *
+ * Returns the number of windows that have gathered data but were
+ * not flushed.  If there were any that were not flushed, then
+ * reschedule the rate flushing routine.
+ */
+static int iwl_rate_scale_flush_windows(struct iwl_rate_scale_priv *rs_priv)
+{
+       int unflushed = 0;
+       int i;
+       unsigned long flags;
+
+       /*
+        * For each rate, if we have collected data on that rate
+        * and it has been more than IWL_RATE_WIN_FLUSH
+        * since we flushed, clear out the gathered statistics
+        */
+       for (i = 0; i < IWL_RATE_COUNT; i++) {
+               if (!rs_priv->win[i].counter)
+                       continue;
+
+               spin_lock_irqsave(&rs_priv->lock, flags);
+               if (time_after(jiffies, rs_priv->win[i].stamp +
+                              IWL_RATE_WIN_FLUSH)) {
+                       IWL_DEBUG_RATE("flushing %d samples of rate "
+                                      "index %d\n",
+                                      rs_priv->win[i].counter, i);
+                       iwl_clear_window(&rs_priv->win[i]);
+               } else
+                       unflushed++;
+               spin_unlock_irqrestore(&rs_priv->lock, flags);
+       }
+
+       return unflushed;
+}
+
+#define IWL_RATE_FLUSH_MAX              5000   /* msec */
+#define IWL_RATE_FLUSH_MIN              50     /* msec */
+
+static void iwl_bg_rate_scale_flush(unsigned long data)
+{
+       struct iwl_rate_scale_priv *rs_priv = (void *)data;
+       int unflushed = 0;
+       unsigned long flags;
+       u32 packet_count, duration, pps;
+
+       IWL_DEBUG_RATE("enter\n");
+
+       unflushed = iwl_rate_scale_flush_windows(rs_priv);
+
+       spin_lock_irqsave(&rs_priv->lock, flags);
+
+       rs_priv->flush_pending = 0;
+
+       /* Number of packets Rx'd since last time this timer ran */
+       packet_count = (rs_priv->tx_packets - rs_priv->last_tx_packets) + 1;
+
+       rs_priv->last_tx_packets = rs_priv->tx_packets + 1;
+
+       if (unflushed) {
+               duration =
+                   jiffies_to_msecs(jiffies - rs_priv->last_partial_flush);
+/*              duration = jiffies_to_msecs(rs_priv->flush_time); */
+
+               IWL_DEBUG_RATE("Tx'd %d packets in %dms\n",
+                              packet_count, duration);
+
+               /* Determine packets per second */
+               if (duration)
+                       pps = (packet_count * 1000) / duration;
+               else
+                       pps = 0;
+
+               if (pps) {
+                       duration = IWL_RATE_FLUSH_MAX / pps;
+                       if (duration < IWL_RATE_FLUSH_MIN)
+                               duration = IWL_RATE_FLUSH_MIN;
+               } else
+                       duration = IWL_RATE_FLUSH_MAX;
+
+               rs_priv->flush_time = msecs_to_jiffies(duration);
+
+               IWL_DEBUG_RATE("new flush period: %d msec ave %d\n",
+                              duration, packet_count);
+
+               mod_timer(&rs_priv->rate_scale_flush, jiffies +
+                         rs_priv->flush_time);
+
+               rs_priv->last_partial_flush = jiffies;
+       }
+
+       /* If there weren't any unflushed entries, we don't schedule the timer
+        * to run again */
+
+       rs_priv->last_flush = jiffies;
+
+       spin_unlock_irqrestore(&rs_priv->lock, flags);
+
+       IWL_DEBUG_RATE("leave\n");
+}
+
+/**
+ * iwl_collect_tx_data - Update the success/failure sliding window
+ *
+ * We keep a sliding window of the last 64 packets transmitted
+ * at this rate.  window->data contains the bitmask of successful
+ * packets.
+ */
+static void iwl_collect_tx_data(struct iwl_rate_scale_priv *rs_priv,
+                               struct iwl_rate_scale_data *window,
+                               int success, int retries)
+{
+       unsigned long flags;
+
+       if (!retries) {
+               IWL_DEBUG_RATE("leave: retries == 0 -- should be at least 1\n");
+               return;
+       }
+
+       while (retries--) {
+               spin_lock_irqsave(&rs_priv->lock, flags);
+
+               /* If we have filled up the window then subtract one from the
+                * success counter if the high-bit is counting toward
+                * success */
+               if (window->counter == IWL_RATE_MAX_WINDOW) {
+                       if (window->data & (1ULL << (IWL_RATE_MAX_WINDOW - 1)))
+                               window->success_counter--;
+               } else
+                       window->counter++;
+
+               /* Slide the window to the left one bit */
+               window->data = (window->data << 1);
+
+               /* If this packet was a success then set the low bit high */
+               if (success) {
+                       window->success_counter++;
+                       window->data |= 1;
+               }
+
+               /* window->counter can't be 0 -- it is either >0 or
+                * IWL_RATE_MAX_WINDOW */
+               window->success_ratio = 12800 * window->success_counter /
+                   window->counter;
+
+               /* Tag this window as having been updated */
+               window->stamp = jiffies;
+
+               spin_unlock_irqrestore(&rs_priv->lock, flags);
+       }
+}
+
+static void rs_rate_init(void *priv_rate, void *priv_sta,
+                        struct ieee80211_local *local, struct sta_info *sta)
+{
+       int i;
+
+       IWL_DEBUG_RATE("enter\n");
+
+       /* TODO: what is a good starting rate for STA? About middle? Maybe not
+        * the lowest or the highest rate.. Could consider using RSSI from
+        * previous packets? Need to have IEEE 802.1X auth succeed immediately
+        * after assoc.. */
+
+       for (i = IWL_RATE_COUNT - 1; i >= 0; i--) {
+               if (sta->supp_rates & (1 << i)) {
+                       sta->txrate = i;
+                       break;
+               }
+       }
+
+       sta->last_txrate = sta->txrate;
+
+       IWL_DEBUG_RATE("leave\n");
+}
+
+static void *rs_alloc(struct ieee80211_local *local)
+{
+       return local->hw.priv;
+}
+
+/* rate scale requires free function to be implmented */
+static void rs_free(void *priv)
+{
+       return;
+}
+static void rs_clear(void *priv)
+{
+       return;
+}
+
+
+static void *rs_alloc_sta(void *priv, gfp_t gfp)
+{
+       struct iwl_rate_scale_priv *rs_priv;
+       int i;
+
+       IWL_DEBUG_RATE("enter\n");
+
+       rs_priv = kzalloc(sizeof(struct iwl_rate_scale_priv), gfp);
+       if (!rs_priv) {
+               IWL_DEBUG_RATE("leave: ENOMEM\n");
+               return NULL;
+       }
+
+       spin_lock_init(&rs_priv->lock);
+
+       rs_priv->start_rate = IWL_RATE_INVALID;
+
+       /* default to just 802.11b */
+       rs_priv->expected_tpt = iwl_expected_tpt_b;
+
+       rs_priv->last_partial_flush = jiffies;
+       rs_priv->last_flush = jiffies;
+       rs_priv->flush_time = IWL_RATE_FLUSH;
+       rs_priv->last_tx_packets = 0;
+       rs_priv->ibss_sta_added = 0;
+
+       init_timer(&rs_priv->rate_scale_flush);
+       rs_priv->rate_scale_flush.data = (unsigned long)rs_priv;
+       rs_priv->rate_scale_flush.function = &iwl_bg_rate_scale_flush;
+
+       for (i = 0; i < IWL_RATE_COUNT; i++)
+               iwl_clear_window(&rs_priv->win[i]);
+
+       IWL_DEBUG_RATE("leave\n");
+
+       return rs_priv;
+}
+
+static void rs_free_sta(void *priv, void *priv_sta)
+{
+       struct iwl_rate_scale_priv *rs_priv = priv_sta;
+
+       IWL_DEBUG_RATE("enter\n");
+       del_timer_sync(&rs_priv->rate_scale_flush);
+       kfree(rs_priv);
+       IWL_DEBUG_RATE("leave\n");
+}
+
+/**
+ * rs_tx_status - Update rate control values based on Tx results
+ *
+ * NOTE: Uses iwl_priv->retry_rate for the # of retries attempted by
+ * the hardware for each rate.
+ */
+static void rs_tx_status(void *priv_rate,
+                        struct net_device *dev,
+                        struct sk_buff *skb,
+                        struct ieee80211_tx_status *tx_resp)
+{
+       u8 retries, current_count;
+       int scale_rate_index, first_index, last_index;
+       unsigned long flags;
+       struct sta_info *sta;
+       struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+       struct iwl_priv *priv = (struct iwl_priv *)priv_rate;
+       struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
+       struct iwl_rate_scale_priv *rs_priv;
+
+       IWL_DEBUG_RATE("enter\n");
+
+       retries = tx_resp->retry_count;
+
+       first_index = tx_resp->control.tx_rate;
+       if ((first_index < 0) || (first_index >= IWL_RATE_COUNT)) {
+               IWL_DEBUG_RATE("leave: Rate out of bounds: %0x for %d\n",
+                              tx_resp->control.tx_rate, first_index);
+               return;
+       }
+
+       sta = sta_info_get(local, hdr->addr1);
+       if (!sta || !sta->rate_ctrl_priv) {
+               if (sta)
+                       sta_info_put(sta);
+               IWL_DEBUG_RATE("leave: No STA priv data to update!\n");
+               return;
+       }
+
+       rs_priv = (void *)sta->rate_ctrl_priv;
+
+       rs_priv->tx_packets++;
+
+       scale_rate_index = first_index;
+       last_index = first_index;
+
+       /*
+        * Update the window for each rate.  We determine which rates
+        * were Tx'd based on the total number of retries vs. the number
+        * of retries configured for each rate -- currently set to the
+        * priv value 'retry_rate' vs. rate specific
+        *
+        * On exit from this while loop last_index indicates the rate
+        * at which the frame was finally transmitted (or failed if no
+        * ACK)
+        */
+       while (retries > 0) {
+               if (retries < priv->retry_rate) {
+                       current_count = retries;
+                       last_index = scale_rate_index;
+               } else {
+                       current_count = priv->retry_rate;
+                       last_index = iwl_get_prev_ieee_rate(scale_rate_index);
+               }
+
+               /* Update this rate accounting for as many retries
+                * as was used for it (per current_count) */
+               iwl_collect_tx_data(rs_priv,
+                                   &rs_priv->win[scale_rate_index],
+                                   0, current_count);
+               IWL_DEBUG_RATE("Update rate %d for %d retries.\n",
+                              scale_rate_index, current_count);
+
+               retries -= current_count;
+
+               if (retries)
+                       scale_rate_index =
+                           iwl_get_prev_ieee_rate(scale_rate_index);
+       }
+
+       /* Update the last index window with success/failure based on ACK */
+       IWL_DEBUG_RATE("Update rate %d with %s.\n",
+                      last_index,
+                      (tx_resp->flags & IEEE80211_TX_STATUS_ACK) ?
+                      "success" : "failure");
+       iwl_collect_tx_data(rs_priv,
+                           &rs_priv->win[last_index],
+                           tx_resp->flags & IEEE80211_TX_STATUS_ACK, 1);
+
+       /* We updated the rate scale window -- if its been more than
+        * flush_time since the last run, schedule the flush
+        * again */
+       spin_lock_irqsave(&rs_priv->lock, flags);
+
+       if (!rs_priv->flush_pending &&
+           time_after(jiffies, rs_priv->last_partial_flush +
+                      rs_priv->flush_time)) {
+
+               rs_priv->flush_pending = 1;
+               mod_timer(&rs_priv->rate_scale_flush,
+                         jiffies + rs_priv->flush_time);
+       }
+
+       spin_unlock_irqrestore(&rs_priv->lock, flags);
+
+       sta_info_put(sta);
+
+       IWL_DEBUG_RATE("leave\n");
+
+       return;
+}
+
+static struct ieee80211_rate *iwl_get_lowest_rate(struct ieee80211_local
+                                                 *local)
+{
+       struct ieee80211_hw_mode *mode = local->oper_hw_mode;
+       int i;
+
+       for (i = 0; i < mode->num_rates; i++) {
+               struct ieee80211_rate *rate = &mode->rates[i];
+
+               if (rate->flags & IEEE80211_RATE_SUPPORTED)
+                       return rate;
+       }
+
+       return &mode->rates[0];
+}
+
+static u16 iwl_get_adjacent_rate(struct iwl_rate_scale_priv *rs_priv,
+                                u8 index, u16 rate_mask, int phymode)
+{
+       u8 high = IWL_RATE_INVALID;
+       u8 low = IWL_RATE_INVALID;
+
+       /* 802.11A walks to the next literal adjascent rate in
+        * the rate table */
+       if (unlikely(phymode == MODE_IEEE80211A)) {
+               int i;
+               u32 mask;
+
+               /* Find the previous rate that is in the rate mask */
+               i = index - 1;
+               for (mask = (1 << i); i >= 0; i--, mask >>= 1) {
+                       if (rate_mask & mask) {
+                               low = i;
+                               break;
+                       }
+               }
+
+               /* Find the next rate that is in the rate mask */
+               i = index + 1;
+               for (mask = (1 << i); i < IWL_RATE_COUNT; i++, mask <<= 1) {
+                       if (rate_mask & mask) {
+                               high = i;
+                               break;
+                       }
+               }
+
+               return (high << 8) | low;
+       }
+
+       low = index;
+       while (low != IWL_RATE_INVALID) {
+               if (rs_priv->tgg)
+                       low = iwl_rates[low].prev_rs_tgg;
+               else
+                       low = iwl_rates[low].prev_rs;
+               if (low == IWL_RATE_INVALID)
+                       break;
+               if (rate_mask & (1 << low))
+                       break;
+               IWL_DEBUG_RATE("Skipping masked lower rate: %d\n", low);
+       }
+
+       high = index;
+       while (high != IWL_RATE_INVALID) {
+               if (rs_priv->tgg)
+                       high = iwl_rates[high].next_rs_tgg;
+               else
+                       high = iwl_rates[high].next_rs;
+               if (high == IWL_RATE_INVALID)
+                       break;
+               if (rate_mask & (1 << high))
+                       break;
+               IWL_DEBUG_RATE("Skipping masked higher rate: %d\n", high);
+       }
+
+       return (high << 8) | low;
+}
+
+/**
+ * rs_get_rate - find the rate for the requested packet
+ *
+ * Returns the ieee80211_rate structure allocated by the driver.
+ *
+ * The rate control algorithm has no internal mapping between hw_mode's
+ * rate ordering and the rate ordering used by the rate control algorithm.
+ *
+ * The rate control algorithm uses a single table of rates that goes across
+ * the entire A/B/G spectrum vs. being limited to just one particular
+ * hw_mode.
+ *
+ * As such, we can't convert the index obtained below into the hw_mode's
+ * rate table and must reference the driver allocated rate table
+ *
+ */
+static struct ieee80211_rate *rs_get_rate(void *priv_rate,
+                                         struct net_device *dev,
+                                         struct sk_buff *skb,
+                                         struct rate_control_extra *extra)
+{
+       u8 low = IWL_RATE_INVALID;
+       u8 high = IWL_RATE_INVALID;
+       u16 high_low;
+       int index;
+       struct iwl_rate_scale_priv *rs_priv;
+       struct iwl_rate_scale_data *window = NULL;
+       int current_tpt = IWL_INVALID_VALUE;
+       int low_tpt = IWL_INVALID_VALUE;
+       int high_tpt = IWL_INVALID_VALUE;
+       u32 fail_count;
+       s8 scale_action = 0;
+       unsigned long flags;
+       struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
+       struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+       struct sta_info *sta;
+       u16 fc, rate_mask;
+       struct iwl_priv *priv = (struct iwl_priv *)priv_rate;
+
+       IWL_DEBUG_RATE("enter\n");
+
+       memset(extra, 0, sizeof(*extra));
+
+       fc = le16_to_cpu(hdr->frame_control);
+       if (((fc & IEEE80211_FCTL_FTYPE) != IEEE80211_FTYPE_DATA) ||
+           (is_multicast_ether_addr(hdr->addr1))) {
+               /* Send management frames and broadcast/multicast data using
+                * lowest rate. */
+               /* TODO: this could probably be improved.. */
+               IWL_DEBUG_RATE("leave: lowest rate (not data or is "
+                              "multicast)\n");
+
+               return iwl_get_lowest_rate(local);
+       }
+
+       sta = sta_info_get(local, hdr->addr1);
+       if (!sta || !sta->rate_ctrl_priv) {
+               IWL_DEBUG_RATE("leave: No STA priv data to update!\n");
+               if (sta)
+                       sta_info_put(sta);
+               return NULL;
+       }
+
+       rate_mask = sta->supp_rates;
+       index = min(sta->txrate & 0xffff, IWL_RATE_COUNT - 1);
+
+       rs_priv = (void *)sta->rate_ctrl_priv;
+
+       if ((priv->iw_mode == IEEE80211_IF_TYPE_IBSS) &&
+           !rs_priv->ibss_sta_added) {
+               u8 sta_id = iwl_hw_find_station(priv, hdr->addr1);
+
+               if (sta_id == IWL_INVALID_STATION) {
+                       IWL_DEBUG_RATE("LQ: ADD station " MAC_FMT "\n",
+                                       MAC_ARG(hdr->addr1));
+                       sta_id = iwl_add_station(priv,
+                                   hdr->addr1, 0, CMD_ASYNC);
+               }
+               if (sta_id != IWL_INVALID_STATION)
+                       rs_priv->ibss_sta_added = 1;
+       }
+
+       spin_lock_irqsave(&rs_priv->lock, flags);
+
+       if (rs_priv->start_rate != IWL_RATE_INVALID) {
+               index = rs_priv->start_rate;
+               rs_priv->start_rate = IWL_RATE_INVALID;
+       }
+
+       window = &(rs_priv->win[index]);
+
+       fail_count = window->counter - window->success_counter;
+
+       if (((fail_count <= IWL_RATE_MIN_FAILURE_TH) &&
+            (window->success_counter < IWL_RATE_MIN_SUCCESS_TH))) {
+               window->average_tpt = IWL_INVALID_VALUE;
+               spin_unlock_irqrestore(&rs_priv->lock, flags);
+
+               IWL_DEBUG_RATE("Invalid average_tpt on rate %d: "
+                              "counter: %d, success_counter: %d, "
+                              "expected_tpt is %sNULL\n",
+                              index,
+                              window->counter,
+                              window->success_counter,
+                              rs_priv->expected_tpt ? "not " : "");
+               goto out;
+
+       }
+
+       window->average_tpt = ((window->success_ratio *
+                               rs_priv->expected_tpt[index] + 64) / 128);
+       current_tpt = window->average_tpt;
+
+       high_low = iwl_get_adjacent_rate(rs_priv, index, rate_mask,
+                                        local->hw.conf.phymode);
+       low = high_low & 0xff;
+       high = (high_low >> 8) & 0xff;
+
+       if (low != IWL_RATE_INVALID)
+               low_tpt = rs_priv->win[low].average_tpt;
+
+       if (high != IWL_RATE_INVALID)
+               high_tpt = rs_priv->win[high].average_tpt;
+
+       spin_unlock_irqrestore(&rs_priv->lock, flags);
+
+       scale_action = 1;
+
+       if ((window->success_ratio < IWL_RATE_DECREASE_TH) || !current_tpt) {
+               IWL_DEBUG_RATE("decrease rate because of low success_ratio\n");
+               scale_action = -1;
+       } else if ((low_tpt == IWL_INVALID_VALUE) &&
+                  (high_tpt == IWL_INVALID_VALUE))
+               scale_action = 1;
+       else if ((low_tpt != IWL_INVALID_VALUE) &&
+                  (high_tpt != IWL_INVALID_VALUE)
+                  && (low_tpt < current_tpt)
+                  && (high_tpt < current_tpt)) {
+               IWL_DEBUG_RATE("No action -- low [%d] & high [%d] < "
+                              "current_tpt [%d]\n",
+                              low_tpt, high_tpt, current_tpt);
+               scale_action = 0;
+       } else {
+               if (high_tpt != IWL_INVALID_VALUE) {
+                       if (high_tpt > current_tpt)
+                               scale_action = 1;
+                       else {
+                               IWL_DEBUG_RATE
+                                   ("decrease rate because of high tpt\n");
+                               scale_action = -1;
+                       }
+               } else if (low_tpt != IWL_INVALID_VALUE) {
+                       if (low_tpt > current_tpt) {
+                               IWL_DEBUG_RATE
+                                   ("decrease rate because of low tpt\n");
+                               scale_action = -1;
+                       } else
+                               scale_action = 1;
+               }
+       }
+
+       if ((window->success_ratio > IWL_RATE_HIGH_TH) ||
+           (current_tpt > window->average_tpt)) {
+               IWL_DEBUG_RATE("No action -- success_ratio [%d] > HIGH_TH or "
+                              "current_tpt [%d] > average_tpt [%d]\n",
+                              window->success_ratio,
+                              current_tpt, window->average_tpt);
+               scale_action = 0;
+       }
+
+       switch (scale_action) {
+       case -1:
+               if (low != IWL_RATE_INVALID)
+                       index = low;
+               break;
+
+       case 1:
+               if (high != IWL_RATE_INVALID)
+                       index = high;
+
+               break;
+
+       case 0:
+       default:
+               break;
+       }
+
+       IWL_DEBUG_RATE("Selected %d (action %d) - low %d high %d\n",
+                      index, scale_action, low, high);
+
+ out:
+
+       sta->last_txrate = index;
+       sta->txrate = sta->last_txrate;
+       sta_info_put(sta);
+
+       IWL_DEBUG_RATE("leave: %d\n", index);
+
+       return &priv->ieee_rates[index];
+}
+
+static struct rate_control_ops rs_ops = {
+       .module = NULL,
+       .name = RS_NAME,
+       .tx_status = rs_tx_status,
+       .get_rate = rs_get_rate,
+       .rate_init = rs_rate_init,
+       .clear = rs_clear,
+       .alloc = rs_alloc,
+       .free = rs_free,
+       .alloc_sta = rs_alloc_sta,
+       .free_sta = rs_free_sta,
+};
+
+int iwl_fill_rs_info(struct ieee80211_hw *hw, char *buf, u8 sta_id)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+       struct iwl_priv *priv = hw->priv;
+       struct iwl_rate_scale_priv *rs_priv;
+       struct sta_info *sta;
+       unsigned long flags;
+       int count = 0, i;
+       u32 samples = 0, success = 0, good = 0;
+       unsigned long now = jiffies;
+       u32 max_time = 0;
+
+       sta = sta_info_get(local, priv->stations[sta_id].sta.sta.addr);
+       if (!sta || !sta->rate_ctrl_priv) {
+               if (sta) {
+                       sta_info_put(sta);
+                       IWL_DEBUG_RATE("leave - no private rate data!\n");
+               } else
+                       IWL_DEBUG_RATE("leave - no station!\n");
+               return sprintf(buf, "station %d not found\n", sta_id);
+       }
+
+       rs_priv = (void *)sta->rate_ctrl_priv;
+       spin_lock_irqsave(&rs_priv->lock, flags);
+       i = IWL_RATE_54M_INDEX;
+       while (1) {
+               u64 mask;
+               int j;
+
+               count +=
+                   sprintf(&buf[count], " %2dMbs: ", iwl_rates[i].ieee / 2);
+
+               mask = (1ULL << (IWL_RATE_MAX_WINDOW - 1));
+               for (j = 0; j < IWL_RATE_MAX_WINDOW; j++, mask >>= 1)
+                       buf[count++] =
+                           (rs_priv->win[i].data & mask) ? '1' : '0';
+
+               samples += rs_priv->win[i].counter;
+               good += rs_priv->win[i].success_counter;
+               success += rs_priv->win[i].success_counter * iwl_rates[i].ieee;
+
+               if (rs_priv->win[i].stamp) {
+                       int delta =
+                           jiffies_to_msecs(now - rs_priv->win[i].stamp);
+
+                       if (delta > max_time)
+                               max_time = delta;
+
+                       count += sprintf(&buf[count], "%5dms\n", delta);
+               } else
+                       buf[count++] = '\n';
+
+               j = iwl_get_prev_ieee_rate(i);
+               if (j == i)
+                       break;
+               i = j;
+       }
+       spin_unlock_irqrestore(&rs_priv->lock, flags);
+       sta_info_put(sta);
+
+       /* Display the average rate of all samples taken.
+        *
+        * NOTE:  We multiple # of samples by 2 since the IEEE measurement
+        * added from iwl_rates is actually 2X the rate */
+       if (samples)
+               count += sprintf(
+                       &buf[count],
+                       "\nAverage rate is %3d.%02dMbs over last %4dms\n"
+                       "%3d%% success (%d good packets over %d tries)\n",
+                       success / (2 * samples), (success * 5 / samples) % 10,
+                       max_time, good * 100 / samples, good, samples);
+       else
+               count += sprintf(&buf[count], "\nAverage rate: 0Mbs\n");
+
+       return count;
+}
+
+void iwl_rate_scale_init(struct ieee80211_hw *hw, s32 sta_id)
+{
+       struct iwl_priv *priv = hw->priv;
+       s32 rssi = 0;
+       unsigned long flags;
+       struct ieee80211_local *local = hw_to_local(hw);
+       struct iwl_rate_scale_priv *rs_priv;
+       struct sta_info *sta;
+
+       IWL_DEBUG_RATE("enter\n");
+
+       if (!local->rate_ctrl->ops->name ||
+           strcmp(local->rate_ctrl->ops->name, RS_NAME)) {
+               IWL_WARNING("iwl-3945-rs not selected as rate control algo!\n");
+               IWL_DEBUG_RATE("leave - mac80211 picked the wrong RC algo.\n");
+               return;
+       }
+
+       sta = sta_info_get(local, priv->stations[sta_id].sta.sta.addr);
+       if (!sta || !sta->rate_ctrl_priv) {
+               if (sta)
+                       sta_info_put(sta);
+               IWL_DEBUG_RATE("leave - no private rate data!\n");
+               return;
+       }
+
+       rs_priv = (void *)sta->rate_ctrl_priv;
+
+       spin_lock_irqsave(&rs_priv->lock, flags);
+
+       rs_priv->tgg = 0;
+       switch (priv->phymode) {
+       case MODE_IEEE80211G:
+               if (priv->active_rxon.flags & RXON_FLG_TGG_PROTECT_MSK) {
+                       rs_priv->tgg = 1;
+                       rs_priv->expected_tpt = iwl_expected_tpt_g_prot;
+               } else
+                       rs_priv->expected_tpt = iwl_expected_tpt_g;
+               break;
+
+       case MODE_IEEE80211A:
+               rs_priv->expected_tpt = iwl_expected_tpt_a;
+               break;
+
+       default:
+               IWL_WARNING("Invalid phymode.  Defaulting to 802.11b\n");
+       case MODE_IEEE80211B:
+               rs_priv->expected_tpt = iwl_expected_tpt_b;
+               break;
+       }
+
+       sta_info_put(sta);
+       spin_unlock_irqrestore(&rs_priv->lock, flags);
+
+       rssi = priv->last_rx_rssi;
+       if (rssi == 0)
+               rssi = IWL_MIN_RSSI_VAL;
+
+       IWL_DEBUG(IWL_DL_INFO | IWL_DL_RATE, "Network RSSI: %d\n", rssi);
+
+       rs_priv->start_rate = iwl_get_rate_index_by_rssi(rssi, priv->phymode);
+
+       IWL_DEBUG_RATE("leave: rssi %d assign rate index: "
+                      "%d (plcp 0x%x)\n", rssi, rs_priv->start_rate,
+                      iwl_rates[rs_priv->start_rate].plcp);
+}
+
+void iwl_rate_control_register(struct ieee80211_hw *hw)
+{
+       ieee80211_rate_control_register(&rs_ops);
+}
+
+void iwl_rate_control_unregister(struct ieee80211_hw *hw)
+{
+       ieee80211_rate_control_unregister(&rs_ops);
+}
+
+
diff --git a/drivers/net/wireless/iwlwifi/iwl-3945-rs.h b/drivers/net/wireless/iwlwifi/iwl-3945-rs.h
new file mode 100644 (file)
index 0000000..b926738
--- /dev/null
@@ -0,0 +1,191 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2005 - 2007 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:
+ * James P. Ketrenos <ipw2100-admin@linux.intel.com>
+ * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+ *
+ *****************************************************************************/
+
+#ifndef __iwl_3945_rs_h__
+#define __iwl_3945_rs_h__
+
+struct iwl_rate_info {
+       u8 plcp;
+       u8 ieee;
+       u8 prev_ieee;           /* previous rate in IEEE speeds */
+       u8 next_ieee;           /* next rate in IEEE speeds */
+       u8 prev_rs;             /* previous rate used in rs algo */
+       u8 next_rs;             /* next rate used in rs algo */
+       u8 prev_rs_tgg;         /* previous rate used in TGG rs algo */
+       u8 next_rs_tgg;         /* next rate used in TGG rs algo */
+};
+
+enum {
+       IWL_RATE_6M_INDEX = 0,
+       IWL_RATE_9M_INDEX,
+       IWL_RATE_12M_INDEX,
+       IWL_RATE_18M_INDEX,
+       IWL_RATE_24M_INDEX,
+       IWL_RATE_36M_INDEX,
+       IWL_RATE_48M_INDEX,
+       IWL_RATE_54M_INDEX,
+       IWL_RATE_1M_INDEX,
+       IWL_RATE_2M_INDEX,
+       IWL_RATE_5M_INDEX,
+       IWL_RATE_11M_INDEX,
+       IWL_RATE_COUNT,
+       IWL_RATE_INVM_INDEX,
+       IWL_RATE_INVALID = IWL_RATE_INVM_INDEX
+};
+
+enum {
+       IWL_FIRST_OFDM_RATE = IWL_RATE_6M_INDEX,
+       IWL_LAST_OFDM_RATE = IWL_RATE_54M_INDEX,
+       IWL_FIRST_CCK_RATE = IWL_RATE_1M_INDEX,
+       IWL_LAST_CCK_RATE = IWL_RATE_11M_INDEX,
+};
+
+/* #define vs. enum to keep from defaulting to 'large integer' */
+#define        IWL_RATE_6M_MASK   (1<<IWL_RATE_6M_INDEX)
+#define        IWL_RATE_9M_MASK   (1<<IWL_RATE_9M_INDEX)
+#define        IWL_RATE_12M_MASK  (1<<IWL_RATE_12M_INDEX)
+#define        IWL_RATE_18M_MASK  (1<<IWL_RATE_18M_INDEX)
+#define        IWL_RATE_24M_MASK  (1<<IWL_RATE_24M_INDEX)
+#define        IWL_RATE_36M_MASK  (1<<IWL_RATE_36M_INDEX)
+#define        IWL_RATE_48M_MASK  (1<<IWL_RATE_48M_INDEX)
+#define        IWL_RATE_54M_MASK  (1<<IWL_RATE_54M_INDEX)
+#define        IWL_RATE_1M_MASK   (1<<IWL_RATE_1M_INDEX)
+#define        IWL_RATE_2M_MASK   (1<<IWL_RATE_2M_INDEX)
+#define        IWL_RATE_5M_MASK   (1<<IWL_RATE_5M_INDEX)
+#define        IWL_RATE_11M_MASK  (1<<IWL_RATE_11M_INDEX)
+
+enum {
+       IWL_RATE_6M_PLCP = 13,
+       IWL_RATE_9M_PLCP = 15,
+       IWL_RATE_12M_PLCP = 5,
+       IWL_RATE_18M_PLCP = 7,
+       IWL_RATE_24M_PLCP = 9,
+       IWL_RATE_36M_PLCP = 11,
+       IWL_RATE_48M_PLCP = 1,
+       IWL_RATE_54M_PLCP = 3,
+       IWL_RATE_1M_PLCP = 10,
+       IWL_RATE_2M_PLCP = 20,
+       IWL_RATE_5M_PLCP = 55,
+       IWL_RATE_11M_PLCP = 110,
+};
+
+enum {
+       IWL_RATE_6M_IEEE = 12,
+       IWL_RATE_9M_IEEE = 18,
+       IWL_RATE_12M_IEEE = 24,
+       IWL_RATE_18M_IEEE = 36,
+       IWL_RATE_24M_IEEE = 48,
+       IWL_RATE_36M_IEEE = 72,
+       IWL_RATE_48M_IEEE = 96,
+       IWL_RATE_54M_IEEE = 108,
+       IWL_RATE_1M_IEEE = 2,
+       IWL_RATE_2M_IEEE = 4,
+       IWL_RATE_5M_IEEE = 11,
+       IWL_RATE_11M_IEEE = 22,
+};
+
+#define IWL_CCK_BASIC_RATES_MASK    \
+       (IWL_RATE_1M_MASK          | \
+       IWL_RATE_2M_MASK)
+
+#define IWL_CCK_RATES_MASK          \
+       (IWL_BASIC_RATES_MASK      | \
+       IWL_RATE_5M_MASK          | \
+       IWL_RATE_11M_MASK)
+
+#define IWL_OFDM_BASIC_RATES_MASK   \
+       (IWL_RATE_6M_MASK         | \
+       IWL_RATE_12M_MASK         | \
+       IWL_RATE_24M_MASK)
+
+#define IWL_OFDM_RATES_MASK         \
+       (IWL_OFDM_BASIC_RATES_MASK | \
+       IWL_RATE_9M_MASK          | \
+       IWL_RATE_18M_MASK         | \
+       IWL_RATE_36M_MASK         | \
+       IWL_RATE_48M_MASK         | \
+       IWL_RATE_54M_MASK)
+
+#define IWL_BASIC_RATES_MASK         \
+       (IWL_OFDM_BASIC_RATES_MASK | \
+        IWL_CCK_BASIC_RATES_MASK)
+
+#define IWL_RATES_MASK ((1<<IWL_RATE_COUNT)-1)
+
+#define IWL_INVALID_VALUE    -1
+
+#define IWL_MIN_RSSI_VAL                 -100
+#define IWL_MAX_RSSI_VAL                    0
+
+extern const struct iwl_rate_info iwl_rates[IWL_RATE_COUNT];
+
+static inline u8 iwl_get_prev_ieee_rate(u8 rate_index)
+{
+       u8 rate = iwl_rates[rate_index].prev_ieee;
+
+       if (rate == IWL_RATE_INVALID)
+               rate = rate_index;
+       return rate;
+}
+
+/**
+ * iwl_fill_rs_info - Fill an output text buffer with the rate representation
+ *
+ * NOTE:  This is provided as a quick mechanism for a user to visualize
+ * the performance of the rate control alogirthm and is not meant to be
+ * parsed software.
+ */
+extern int iwl_fill_rs_info(struct ieee80211_hw *, char *buf, u8 sta_id);
+
+/**
+ * iwl_rate_scale_init - Initialize the rate scale table based on assoc info
+ *
+ * The specific througput table used is based on the type of network
+ * the associated with, including A, B, G, and G w/ TGG protection
+ */
+extern void iwl_rate_scale_init(struct ieee80211_hw *hw, s32 sta_id);
+
+/**
+ * iwl_rate_control_register - Register the rate control algorithm callbacks
+ *
+ * Since the rate control algorithm is hardware specific, there is no need
+ * or reason to place it as a stand alone module.  The driver can call
+ * iwl_rate_control_register in order to register the rate control callbacks
+ * with the mac80211 subsystem.  This should be performed prior to calling
+ * ieee80211_register_hw
+ *
+ */
+extern void iwl_rate_control_register(struct ieee80211_hw *hw);
+
+/**
+ * iwl_rate_control_unregister - Unregister the rate control callbacks
+ *
+ * This should be called after calling ieee80211_unregister_hw, but before
+ * the driver is unloaded.
+ */
+extern void iwl_rate_control_unregister(struct ieee80211_hw *hw);
+
+#endif
diff --git a/drivers/net/wireless/iwlwifi/iwl-3945.c b/drivers/net/wireless/iwlwifi/iwl-3945.c
new file mode 100644 (file)
index 0000000..26f03a0
--- /dev/null
@@ -0,0 +1,2290 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2003 - 2007 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:
+ * James P. Ketrenos <ipw2100-admin@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/version.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/wireless.h>
+#include <linux/firmware.h>
+#include <net/mac80211.h>
+
+#include <linux/etherdevice.h>
+#include <linux/delay.h>
+
+#include "iwlwifi.h"
+#include "iwl-helpers.h"
+#include "iwl-3945.h"
+#include "iwl-3945-rs.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 }
+
+/*
+ * 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 iwl_rate_info iwl_rates[IWL_RATE_COUNT] = {
+       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 */
+       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 */
+};
+
+/* 1 = enable the iwl_disable_events() function */
+#define IWL_EVT_DISABLE (0)
+#define IWL_EVT_DISABLE_SIZE (1532/32)
+
+/**
+ * iwl_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 iwl_disable_events(struct iwl_priv *priv)
+{
+       int rc;
+       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 */
+       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 (!iwl_hw_valid_rtc_data_addr(base)) {
+               IWL_ERROR("Invalid event log pointer 0x%08X\n", base);
+               return;
+       }
+
+       rc = iwl_grab_restricted_access(priv);
+       if (rc) {
+               IWL_WARNING("Can not read from adapter at this time.\n");
+               return;
+       }
+
+       disable_ptr = iwl_read_restricted_mem(priv, base + (4 * sizeof(u32)));
+       array_size = iwl_read_restricted_mem(priv, base + (5 * sizeof(u32)));
+       iwl_release_restricted_access(priv);
+
+       if (IWL_EVT_DISABLE && (array_size == IWL_EVT_DISABLE_SIZE)) {
+               IWL_DEBUG_INFO("Disabling selected uCode log events at 0x%x\n",
+                              disable_ptr);
+               rc = iwl_grab_restricted_access(priv);
+               for (i = 0; i < IWL_EVT_DISABLE_SIZE; i++)
+                       iwl_write_restricted_mem(priv,
+                                                disable_ptr +
+                                                (i * sizeof(u32)),
+                                                evt_disable[i]);
+
+               iwl_release_restricted_access(priv);
+       } else {
+               IWL_DEBUG_INFO("Selected uCode log events may be disabled\n");
+               IWL_DEBUG_INFO("  by writing \"1\"s into disable bitmap\n");
+               IWL_DEBUG_INFO("  in SRAM at 0x%x, size %d u32s\n",
+                              disable_ptr, array_size);
+       }
+
+}
+
+/**
+ * iwl3945_get_antenna_flags - Get antenna flags for RXON command
+ * @priv: eeprom and antenna fields are used to determine antenna flags
+ *
+ * priv->eeprom  is used to determine if antenna AUX/MAIN are reversed
+ * priv->antenna specifies the antenna diversity mode:
+ *
+ * IWL_ANTENNA_DIVERISTY - NIC selects best antenna by itself
+ * IWL_ANTENNA_MAIN      - Force MAIN antenna
+ * IWL_ANTENNA_AUX       - Force AUX antenna
+ */
+__le32 iwl3945_get_antenna_flags(const struct iwl_priv *priv)
+{
+       switch (priv->antenna) {
+       case IWL_ANTENNA_DIVERSITY:
+               return 0;
+
+       case IWL_ANTENNA_MAIN:
+               if (priv->eeprom.antenna_switch_type)
+                       return RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_B_MSK;
+               return RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_A_MSK;
+
+       case IWL_ANTENNA_AUX:
+               if (priv->eeprom.antenna_switch_type)
+                       return RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_A_MSK;
+               return RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_B_MSK;
+       }
+
+       /* bad antenna selector value */
+       IWL_ERROR("Bad antenna selector value (0x%x)\n", priv->antenna);
+       return 0;               /* "diversity" is default if error */
+}
+
+/*****************************************************************************
+ *
+ * Intel PRO/Wireless 3945ABG/BG Network Connection
+ *
+ *  RX handler implementations
+ *
+ *  Used by iwl-base.c
+ *
+ *****************************************************************************/
+
+void iwl_hw_rx_statistics(struct iwl_priv *priv, struct iwl_rx_mem_buffer *rxb)
+{
+       struct iwl_rx_packet *pkt = (void *)rxb->skb->data;
+       IWL_DEBUG_RX("Statistics notification received (%d vs %d).\n",
+                    (int)sizeof(struct iwl_notif_statistics),
+                    le32_to_cpu(pkt->len));
+
+       memcpy(&priv->statistics, pkt->u.raw, sizeof(priv->statistics));
+
+       priv->last_statistics_time = jiffies;
+}
+
+static void iwl3945_handle_data_packet(struct iwl_priv *priv, int is_data,
+                                  struct iwl_rx_mem_buffer *rxb,
+                                  struct ieee80211_rx_status *stats,
+                                  u16 phy_flags)
+{
+       struct ieee80211_hdr *hdr;
+       struct iwl_rx_packet *pkt = (struct iwl_rx_packet *)rxb->skb->data;
+       struct iwl_rx_frame_hdr *rx_hdr = IWL_RX_HDR(pkt);
+       struct iwl_rx_frame_end *rx_end = IWL_RX_END(pkt);
+       short len = le16_to_cpu(rx_hdr->len);
+
+       /* We received data from the HW, so stop the watchdog */
+       if (unlikely((len + IWL_RX_FRAME_SIZE) > skb_tailroom(rxb->skb))) {
+               IWL_DEBUG_DROP("Corruption detected!\n");
+               return;
+       }
+
+       /* We only process data packets if the interface is open */
+       if (unlikely(!priv->is_open)) {
+               IWL_DEBUG_DROP_LIMIT
+                   ("Dropping packet while interface is not open.\n");
+               return;
+       }
+       if (priv->iw_mode == IEEE80211_IF_TYPE_MNTR) {
+               if (iwl_param_hwcrypto)
+                       iwl_set_decrypted_flag(priv, rxb->skb,
+                                              le32_to_cpu(rx_end->status),
+                                              stats);
+               iwl_handle_data_packet_monitor(priv, rxb, IWL_RX_DATA(pkt),
+                                              len, stats, phy_flags);
+               return;
+       }
+
+       skb_reserve(rxb->skb, (void *)rx_hdr->payload - (void *)pkt);
+       /* Set the size of the skb to the size of the frame */
+       skb_put(rxb->skb, le16_to_cpu(rx_hdr->len));
+
+       hdr = (void *)rxb->skb->data;
+
+       if (iwl_param_hwcrypto)
+               iwl_set_decrypted_flag(priv, rxb->skb,
+                                      le32_to_cpu(rx_end->status), stats);
+
+       ieee80211_rx_irqsafe(priv->hw, rxb->skb, stats);
+       rxb->skb = NULL;
+}
+
+static void iwl3945_rx_reply_rx(struct iwl_priv *priv,
+                               struct iwl_rx_mem_buffer *rxb)
+{
+       struct iwl_rx_packet *pkt = (void *)rxb->skb->data;
+       struct iwl_rx_frame_stats *rx_stats = IWL_RX_STATS(pkt);
+       struct iwl_rx_frame_hdr *rx_hdr = IWL_RX_HDR(pkt);
+       struct iwl_rx_frame_end *rx_end = IWL_RX_END(pkt);
+       struct ieee80211_hdr *header;
+       u16 phy_flags = le16_to_cpu(rx_hdr->phy_flags);
+       u16 rx_stats_sig_avg = le16_to_cpu(rx_stats->sig_avg);
+       u16 rx_stats_noise_diff = le16_to_cpu(rx_stats->noise_diff);
+       struct ieee80211_rx_status stats = {
+               .mactime = le64_to_cpu(rx_end->timestamp),
+               .freq = ieee80211chan2mhz(le16_to_cpu(rx_hdr->channel)),
+               .channel = le16_to_cpu(rx_hdr->channel),
+               .phymode = (rx_hdr->phy_flags & RX_RES_PHY_FLAGS_BAND_24_MSK) ?
+               MODE_IEEE80211G : MODE_IEEE80211A,
+               .antenna = 0,
+               .rate = rx_hdr->rate,
+               .flag = 0,
+       };
+       u8 network_packet;
+       int snr;
+
+       if ((unlikely(rx_stats->phy_count > 20))) {
+               IWL_DEBUG_DROP
+                   ("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("Bad CRC or FIFO: 0x%08X.\n", rx_end->status);
+               return;
+       }
+
+       if (priv->iw_mode == IEEE80211_IF_TYPE_MNTR) {
+               iwl3945_handle_data_packet(priv, 1, rxb, &stats, phy_flags);
+               return;
+       }
+
+       /* Convert 3945's rssi indicator to dBm */
+       stats.ssi = rx_stats->rssi - IWL_RSSI_OFFSET;
+
+       /* Set default noise value to -127 */
+       if (priv->last_rx_noise == 0)
+               priv->last_rx_noise = IWL_NOISE_MEAS_NOT_AVAILABLE;
+
+       /* 3945 provides noise info for OFDM frames only.
+        * sig_avg and noise_diff are measured by the 3945's digital signal
+        *   processor (DSP), and indicate linear levels of signal level and
+        *   distortion/noise within the packet preamble after
+        *   automatic gain control (AGC).  sig_avg should stay fairly
+        *   constant if the radio's AGC is working well.
+        * Since these values are linear (not dB or dBm), linear
+        *   signal-to-noise ratio (SNR) is (sig_avg / noise_diff).
+        * Convert linear SNR to dB SNR, then subtract that from rssi dBm
+        *   to obtain noise level in dBm.
+        * Calculate stats.signal (quality indicator in %) based on SNR. */
+       if (rx_stats_noise_diff) {
+               snr = rx_stats_sig_avg / rx_stats_noise_diff;
+               stats.noise = stats.ssi - iwl_calc_db_from_ratio(snr);
+               stats.signal = iwl_calc_sig_qual(stats.ssi, stats.noise);
+
+       /* If noise info not available, calculate signal quality indicator (%)
+        *   using just the dBm signal level. */
+       } else {
+               stats.noise = priv->last_rx_noise;
+               stats.signal = iwl_calc_sig_qual(stats.ssi, 0);
+       }
+
+
+       IWL_DEBUG_STATS("Rssi %d noise %d qual %d sig_avg %d noise_diff %d\n",
+                       stats.ssi, stats.noise, stats.signal,
+                       rx_stats_sig_avg, rx_stats_noise_diff);
+
+       stats.freq = ieee80211chan2mhz(stats.channel);
+
+       /* can be covered by iwl_report_frame() in most cases */
+/*      IWL_DEBUG_RX("RX status: 0x%08X\n", rx_end->status); */
+
+       header = (struct ieee80211_hdr *)IWL_RX_DATA(pkt);
+
+       network_packet = iwl_is_network_packet(priv, header);
+
+#ifdef CONFIG_IWLWIFI_DEBUG
+       if (iwl_debug_level & IWL_DL_STATS && net_ratelimit())
+               IWL_DEBUG_STATS
+                   ("[%c] %d RSSI: %d Signal: %u, Noise: %u, Rate: %u\n",
+                    network_packet ? '*' : ' ',
+                    stats.channel, stats.ssi, stats.ssi,
+                    stats.ssi, stats.rate);
+
+       if (iwl_debug_level & (IWL_DL_RX))
+               /* Set "1" to report good data frames in groups of 100 */
+               iwl_report_frame(priv, pkt, header, 1);
+#endif
+
+       if (network_packet) {
+               priv->last_beacon_time = le32_to_cpu(rx_end->beacon_timestamp);
+               priv->last_tsf = le64_to_cpu(rx_end->timestamp);
+               priv->last_rx_rssi = stats.ssi;
+               priv->last_rx_noise = stats.noise;
+       }
+
+       switch (le16_to_cpu(header->frame_control) & IEEE80211_FCTL_FTYPE) {
+       case IEEE80211_FTYPE_MGMT:
+               switch (le16_to_cpu(header->frame_control) &
+                       IEEE80211_FCTL_STYPE) {
+               case IEEE80211_STYPE_PROBE_RESP:
+               case IEEE80211_STYPE_BEACON:{
+                               /* If this is a beacon or probe response for
+                                * our network then cache the beacon
+                                * timestamp */
+                               if ((((priv->iw_mode == IEEE80211_IF_TYPE_STA)
+                                     && !compare_ether_addr(header->addr2,
+                                                            priv->bssid)) ||
+                                    ((priv->iw_mode == IEEE80211_IF_TYPE_IBSS)
+                                     && !compare_ether_addr(header->addr3,
+                                                            priv->bssid)))) {
+                                       struct ieee80211_mgmt *mgmt =
+                                           (struct ieee80211_mgmt *)header;
+                                       __le32 *pos;
+                                       pos =
+                                           (__le32 *) & mgmt->u.beacon.
+                                           timestamp;
+                                       priv->timestamp0 = le32_to_cpu(pos[0]);
+                                       priv->timestamp1 = le32_to_cpu(pos[1]);
+                                       priv->beacon_int = le16_to_cpu(
+                                           mgmt->u.beacon.beacon_int);
+                                       if (priv->call_post_assoc_from_beacon &&
+                                           (priv->iw_mode ==
+                                               IEEE80211_IF_TYPE_STA))
+                                               queue_work(priv->workqueue,
+                                                   &priv->post_associate.work);
+
+                                       priv->call_post_assoc_from_beacon = 0;
+                               }
+
+                               break;
+                       }
+
+               case IEEE80211_STYPE_ACTION:
+                       /* TODO: Parse 802.11h frames for CSA... */
+                       break;
+
+                       /*
+                        * TODO: There is no callback function from upper
+                        * stack to inform us when associated status. this
+                        * work around to sniff assoc_resp management frame
+                        * and finish the association process.
+                        */
+               case IEEE80211_STYPE_ASSOC_RESP:
+               case IEEE80211_STYPE_REASSOC_RESP:{
+                               struct ieee80211_mgmt *mgnt =
+                                   (struct ieee80211_mgmt *)header;
+                               priv->assoc_id = (~((1 << 15) | (1 << 14)) &
+                                                 le16_to_cpu(mgnt->u.
+                                                             assoc_resp.aid));
+                               priv->assoc_capability =
+                                   le16_to_cpu(mgnt->u.assoc_resp.capab_info);
+                               if (priv->beacon_int)
+                                       queue_work(priv->workqueue,
+                                           &priv->post_associate.work);
+                               else
+                                       priv->call_post_assoc_from_beacon = 1;
+                               break;
+                       }
+
+               case IEEE80211_STYPE_PROBE_REQ:{
+                               if (priv->iw_mode == IEEE80211_IF_TYPE_IBSS)
+                                       IWL_DEBUG_DROP
+                                           ("Dropping (non network): " MAC_FMT
+                                            ", " MAC_FMT ", " MAC_FMT "\n",
+                                            MAC_ARG(header->addr1),
+                                            MAC_ARG(header->addr2),
+                                            MAC_ARG(header->addr3));
+                               return;
+                       }
+               }
+
+               iwl3945_handle_data_packet(priv, 0, rxb, &stats, phy_flags);
+               break;
+
+       case IEEE80211_FTYPE_CTL:
+               break;
+
+       case IEEE80211_FTYPE_DATA:
+               if (unlikely(is_duplicate_packet(priv, header)))
+                       IWL_DEBUG_DROP("Dropping (dup): " MAC_FMT ", "
+                                      MAC_FMT ", " MAC_FMT "\n",
+                                      MAC_ARG(header->addr1),
+                                      MAC_ARG(header->addr2),
+                                      MAC_ARG(header->addr3));
+               else
+                       iwl3945_handle_data_packet(priv, 1, rxb, &stats,
+                                                  phy_flags);
+               break;
+       }
+}
+
+int iwl_hw_txq_attach_buf_to_tfd(struct iwl_priv *priv, void *ptr,
+                                dma_addr_t addr, u16 len)
+{
+       int count;
+       u32 pad;
+       struct iwl_tfd_frame *tfd = (struct iwl_tfd_frame *)ptr;
+
+       count = TFD_CTL_COUNT_GET(le32_to_cpu(tfd->control_flags));
+       pad = TFD_CTL_PAD_GET(le32_to_cpu(tfd->control_flags));
+
+       if ((count >= NUM_TFD_CHUNKS) || (count < 0)) {
+               IWL_ERROR("Error can not send more than %d chunks\n",
+                         NUM_TFD_CHUNKS);
+               return -EINVAL;
+       }
+
+       tfd->pa[count].addr = cpu_to_le32(addr);
+       tfd->pa[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;
+}
+
+/**
+ * iwl_hw_txq_free_tfd - Free one TFD, those at index [txq->q.last_used]
+ *
+ * Does NOT advance any indexes
+ */
+int iwl_hw_txq_free_tfd(struct iwl_priv *priv, struct iwl_tx_queue *txq)
+{
+       struct iwl_tfd_frame *bd_tmp = (struct iwl_tfd_frame *)&txq->bd[0];
+       struct iwl_tfd_frame *bd = &bd_tmp[txq->q.last_used];
+       struct pci_dev *dev = priv->pci_dev;
+       int i;
+       int counter;
+
+       /* classify bd */
+       if (txq->q.id == IWL_CMD_QUEUE_NUM)
+               /* nothing to cleanup after for host commands */
+               return 0;
+
+       /* sanity check */
+       counter = TFD_CTL_COUNT_GET(le32_to_cpu(bd->control_flags));
+       if (counter > NUM_TFD_CHUNKS) {
+               IWL_ERROR("Too many chunks: %i\n", counter);
+               /* @todo issue fatal error, it is quite serious situation */
+               return 0;
+       }
+
+       /* unmap chunks if any */
+
+       for (i = 1; i < counter; i++) {
+               pci_unmap_single(dev, le32_to_cpu(bd->pa[i].addr),
+                                le32_to_cpu(bd->pa[i].len), PCI_DMA_TODEVICE);
+               if (txq->txb[txq->q.last_used].skb[0]) {
+                       struct sk_buff *skb = txq->txb[txq->q.last_used].skb[0];
+                       if (txq->txb[txq->q.last_used].skb[0]) {
+                               /* Can be called from interrupt context */
+                               dev_kfree_skb_any(skb);
+                               txq->txb[txq->q.last_used].skb[0] = NULL;
+                       }
+               }
+       }
+       return 0;
+}
+
+u8 iwl_hw_find_station(struct iwl_priv *priv, const u8 *addr)
+{
+       int i;
+       int ret = IWL_INVALID_STATION;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->sta_lock, flags);
+       for (i = IWL_STA_ID; i < priv->hw_setting.max_stations; i++)
+               if ((priv->stations[i].used) &&
+                   (!compare_ether_addr
+                    (priv->stations[i].sta.sta.addr, addr))) {
+                       ret = i;
+                       goto out;
+               }
+
+       IWL_DEBUG_INFO("can not find STA " MAC_FMT " (total %d)\n",
+                      MAC_ARG(addr), priv->num_stations);
+ out:
+       spin_unlock_irqrestore(&priv->sta_lock, flags);
+       return ret;
+}
+
+/**
+ * iwl_hw_build_tx_cmd_rate - Add rate portion to TX_CMD:
+ *
+*/
+void iwl_hw_build_tx_cmd_rate(struct iwl_priv *priv,
+                             struct iwl_cmd *cmd,
+                             struct ieee80211_tx_control *ctrl,
+                             struct ieee80211_hdr *hdr, int sta_id, int tx_id)
+{
+       unsigned long flags;
+       u16 rate_index = min(ctrl->tx_rate & 0xffff, IWL_RATE_COUNT - 1);
+       u16 rate_mask;
+       int rate;
+       u8 rts_retry_limit;
+       u8 data_retry_limit;
+       __le32 tx_flags;
+       u16 fc = le16_to_cpu(hdr->frame_control);
+
+       rate = iwl_rates[rate_index].plcp;
+       tx_flags = cmd->cmd.tx.tx_flags;
+
+       /* We need to figure out how to get the sta->supp_rates while
+        * in this running context; perhaps encoding into ctrl->tx_rate? */
+       rate_mask = IWL_RATES_MASK;
+
+       spin_lock_irqsave(&priv->sta_lock, flags);
+
+       priv->stations[sta_id].current_rate.rate_n_flags = rate;
+
+       if ((priv->iw_mode == IEEE80211_IF_TYPE_IBSS) &&
+           (sta_id != IWL3945_BROADCAST_ID) &&
+               (sta_id != IWL_MULTICAST_ID))
+               priv->stations[IWL_STA_ID].current_rate.rate_n_flags = rate;
+
+       spin_unlock_irqrestore(&priv->sta_lock, flags);
+
+       if (tx_id >= IWL_CMD_QUEUE_NUM)
+               rts_retry_limit = 3;
+       else
+               rts_retry_limit = 7;
+
+       if (ieee80211_is_probe_response(fc)) {
+               data_retry_limit = 3;
+               if (data_retry_limit < rts_retry_limit)
+                       rts_retry_limit = data_retry_limit;
+       } else
+               data_retry_limit = IWL_DEFAULT_TX_RETRY;
+
+       if (priv->data_retry_limit != -1)
+               data_retry_limit = priv->data_retry_limit;
+
+       if ((fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_MGMT) {
+               switch (fc & IEEE80211_FCTL_STYPE) {
+               case IEEE80211_STYPE_AUTH:
+               case IEEE80211_STYPE_DEAUTH:
+               case IEEE80211_STYPE_ASSOC_REQ:
+               case IEEE80211_STYPE_REASSOC_REQ:
+                       if (tx_flags & TX_CMD_FLG_RTS_MSK) {
+                               tx_flags &= ~TX_CMD_FLG_RTS_MSK;
+                               tx_flags |= TX_CMD_FLG_CTS_MSK;
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       cmd->cmd.tx.rts_retry_limit = rts_retry_limit;
+       cmd->cmd.tx.data_retry_limit = data_retry_limit;
+       cmd->cmd.tx.rate = rate;
+       cmd->cmd.tx.tx_flags = tx_flags;
+
+       /* OFDM */
+       cmd->cmd.tx.supp_rates[0] = rate_mask & IWL_OFDM_RATES_MASK;
+
+       /* CCK */
+       cmd->cmd.tx.supp_rates[1] = (rate_mask >> 8) & 0xF;
+
+       IWL_DEBUG_RATE("Tx sta id: %d, rate: %d (plcp), flags: 0x%4X "
+                      "cck/ofdm mask: 0x%x/0x%x\n", sta_id,
+                      cmd->cmd.tx.rate, le32_to_cpu(cmd->cmd.tx.tx_flags),
+                      cmd->cmd.tx.supp_rates[1], cmd->cmd.tx.supp_rates[0]);
+}
+
+u8 iwl3945_sync_sta(struct iwl_priv *priv, int sta_id, u16 tx_rate, u8 flags)
+{
+       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->current_rate.rate_n_flags = tx_rate;
+       station->sta.mode = STA_CONTROL_MODIFY_MSK;
+
+       spin_unlock_irqrestore(&priv->sta_lock, flags_spin);
+
+       iwl_send_add_station(priv, &station->sta, flags);
+       IWL_DEBUG_RATE("SCALE sync station %d to rate %d\n",
+                       sta_id, tx_rate);
+       return sta_id;
+}
+
+void iwl_hw_card_show_info(struct iwl_priv *priv)
+{
+       IWL_DEBUG_INFO("3945ABG HW Version %u.%u.%u\n",
+                      ((priv->eeprom.board_revision >> 8) & 0x0F),
+                      ((priv->eeprom.board_revision >> 8) >> 4),
+                      (priv->eeprom.board_revision & 0x00FF));
+
+       IWL_DEBUG_INFO("3945ABG PBA Number %.*s\n",
+                      (int)sizeof(priv->eeprom.board_pba_number),
+                      priv->eeprom.board_pba_number);
+
+       IWL_DEBUG_INFO("EEPROM_ANTENNA_SWITCH_TYPE is 0x%02X\n",
+                      priv->eeprom.antenna_switch_type);
+}
+
+static int iwl3945_nic_set_pwr_src(struct iwl_priv *priv, int pwr_max)
+{
+       int rc;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       rc = iwl_grab_restricted_access(priv);
+       if (rc) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return rc;
+       }
+
+       if (!pwr_max) {
+               u32 val;
+
+               rc = pci_read_config_dword(priv->pci_dev,
+                               PCI_POWER_SOURCE, &val);
+               if (val & PCI_CFG_PMC_PME_FROM_D3COLD_SUPPORT) {
+                       iwl_set_bits_mask_restricted_reg(priv, APMG_PS_CTRL_REG,
+                                       APMG_PS_CTRL_VAL_PWR_SRC_VAUX,
+                                       ~APMG_PS_CTRL_MSK_PWR_SRC);
+                       iwl_release_restricted_access(priv);
+
+                       iwl_poll_bit(priv, CSR_GPIO_IN,
+                                    CSR_GPIO_IN_VAL_VAUX_PWR_SRC,
+                                    CSR_GPIO_IN_BIT_AUX_POWER, 5000);
+               } else
+                       iwl_release_restricted_access(priv);
+       } else {
+               iwl_set_bits_mask_restricted_reg(priv, APMG_PS_CTRL_REG,
+                               APMG_PS_CTRL_VAL_PWR_SRC_VMAIN,
+                               ~APMG_PS_CTRL_MSK_PWR_SRC);
+
+               iwl_release_restricted_access(priv);
+               iwl_poll_bit(priv, CSR_GPIO_IN, CSR_GPIO_IN_VAL_VMAIN_PWR_SRC,
+                            CSR_GPIO_IN_BIT_AUX_POWER, 5000);  /* uS */
+       }
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return rc;
+}
+
+static int iwl3945_rx_init(struct iwl_priv *priv, struct iwl_rx_queue *rxq)
+{
+       int rc;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       rc = iwl_grab_restricted_access(priv);
+       if (rc) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return rc;
+       }
+
+       iwl_write_restricted(priv, FH_RCSR_RBD_BASE(0), rxq->dma_addr);
+       iwl_write_restricted(priv, FH_RCSR_RPTR_ADDR(0),
+                            priv->hw_setting.shared_phys +
+                            offsetof(struct iwl_shared, rx_read_ptr[0]));
+       iwl_write_restricted(priv, FH_RCSR_WPTR(0), 0);
+       iwl_write_restricted(priv, FH_RCSR_CONFIG(0),
+               ALM_FH_RCSR_RX_CONFIG_REG_VAL_DMA_CHNL_EN_ENABLE |
+               ALM_FH_RCSR_RX_CONFIG_REG_VAL_RDRBD_EN_ENABLE |
+               ALM_FH_RCSR_RX_CONFIG_REG_BIT_WR_STTS_EN |
+               ALM_FH_RCSR_RX_CONFIG_REG_VAL_MAX_FRAG_SIZE_128 |
+               (RX_QUEUE_SIZE_LOG << ALM_FH_RCSR_RX_CONFIG_REG_POS_RBDC_SIZE) |
+               ALM_FH_RCSR_RX_CONFIG_REG_VAL_IRQ_DEST_INT_HOST |
+               (1 << ALM_FH_RCSR_RX_CONFIG_REG_POS_IRQ_RBTH) |
+               ALM_FH_RCSR_RX_CONFIG_REG_VAL_MSG_MODE_FH);
+
+       /* fake read to flush all prev I/O */
+       iwl_read_restricted(priv, FH_RSSR_CTRL);
+
+       iwl_release_restricted_access(priv);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return 0;
+}
+
+static int iwl3945_tx_reset(struct iwl_priv *priv)
+{
+       int rc;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       rc = iwl_grab_restricted_access(priv);
+       if (rc) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return rc;
+       }
+
+       /* bypass mode */
+       iwl_write_restricted_reg(priv, SCD_MODE_REG, 0x2);
+
+       /* RA 0 is active */
+       iwl_write_restricted_reg(priv, SCD_ARASTAT_REG, 0x01);
+
+       /* all 6 fifo are active */
+       iwl_write_restricted_reg(priv, SCD_TXFACT_REG, 0x3f);
+
+       iwl_write_restricted_reg(priv, SCD_SBYP_MODE_1_REG, 0x010000);
+       iwl_write_restricted_reg(priv, SCD_SBYP_MODE_2_REG, 0x030002);
+       iwl_write_restricted_reg(priv, SCD_TXF4MF_REG, 0x000004);
+       iwl_write_restricted_reg(priv, SCD_TXF5MF_REG, 0x000005);
+
+       iwl_write_restricted(priv, FH_TSSR_CBB_BASE,
+                            priv->hw_setting.shared_phys);
+
+       iwl_write_restricted(priv, FH_TSSR_MSG_CONFIG,
+               ALM_FH_TSSR_TX_MSG_CONFIG_REG_VAL_SNOOP_RD_TXPD_ON |
+               ALM_FH_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RD_TXPD_ON |
+               ALM_FH_TSSR_TX_MSG_CONFIG_REG_VAL_MAX_FRAG_SIZE_128B |
+               ALM_FH_TSSR_TX_MSG_CONFIG_REG_VAL_SNOOP_RD_TFD_ON |
+               ALM_FH_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RD_CBB_ON |
+               ALM_FH_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RSP_WAIT_TH |
+               ALM_FH_TSSR_TX_MSG_CONFIG_REG_VAL_RSP_WAIT_TH);
+
+       iwl_release_restricted_access(priv);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       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;
+
+       iwl_hw_txq_ctx_free(priv);
+
+       /* Tx CMD queue */
+       rc = iwl3945_tx_reset(priv);
+       if (rc)
+               goto error;
+
+       /* Tx queue(s) */
+       for (txq_id = 0; txq_id < TFD_QUEUE_MAX; txq_id++) {
+               slots_num = (txq_id == IWL_CMD_QUEUE_NUM) ?
+                               TFD_CMD_SLOTS : TFD_TX_CMD_SLOTS;
+               rc = iwl_tx_queue_init(priv, &priv->txq[txq_id], slots_num,
+                               txq_id);
+               if (rc) {
+                       IWL_ERROR("Tx %d queue init failed\n", txq_id);
+                       goto error;
+               }
+       }
+
+       return rc;
+
+ error:
+       iwl_hw_txq_ctx_free(priv);
+       return rc;
+}
+
+int iwl_hw_nic_init(struct iwl_priv *priv)
+{
+       u8 rev_id;
+       int rc;
+       unsigned long flags;
+       struct iwl_rx_queue *rxq = &priv->rxq;
+
+       iwl_power_init_handle(priv);
+
+       spin_lock_irqsave(&priv->lock, flags);
+       iwl_set_bit(priv, CSR_ANA_PLL_CFG, (1 << 24));
+       iwl_set_bit(priv, CSR_GIO_CHICKEN_BITS,
+                   CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX);
+
+       iwl_set_bit(priv, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
+       rc = iwl_poll_bit(priv, CSR_GP_CNTRL,
+                         CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY,
+                         CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, 25000);
+       if (rc < 0) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               IWL_DEBUG_INFO("Failed to init the card\n");
+               return rc;
+       }
+
+       rc = iwl_grab_restricted_access(priv);
+       if (rc) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return rc;
+       }
+       iwl_write_restricted_reg(priv, APMG_CLK_EN_REG,
+                                APMG_CLK_VAL_DMA_CLK_RQT |
+                                APMG_CLK_VAL_BSM_CLK_RQT);
+       udelay(20);
+       iwl_set_bits_restricted_reg(priv, APMG_PCIDEV_STT_REG,
+                                   APMG_PCIDEV_STT_VAL_L1_ACT_DIS);
+       iwl_release_restricted_access(priv);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       /* Determine HW type */
+       rc = pci_read_config_byte(priv->pci_dev, PCI_REVISION_ID, &rev_id);
+       if (rc)
+               return rc;
+       IWL_DEBUG_INFO("HW Revision ID = 0x%X\n", rev_id);
+
+       iwl3945_nic_set_pwr_src(priv, 1);
+       spin_lock_irqsave(&priv->lock, flags);
+
+       if (rev_id & PCI_CFG_REV_ID_BIT_RTP)
+               IWL_DEBUG_INFO("RTP type \n");
+       else if (rev_id & PCI_CFG_REV_ID_BIT_BASIC_SKU) {
+               IWL_DEBUG_INFO("ALM-MB type\n");
+               iwl_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+                           CSR_HW_IF_CONFIG_REG_BIT_ALMAGOR_MB);
+       } else {
+               IWL_DEBUG_INFO("ALM-MM type\n");
+               iwl_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+                           CSR_HW_IF_CONFIG_REG_BIT_ALMAGOR_MM);
+       }
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       /* Initialize the EEPROM */
+       rc = iwl_eeprom_init(priv);
+       if (rc)
+               return rc;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       if (EEPROM_SKU_CAP_OP_MODE_MRC == priv->eeprom.sku_cap) {
+               IWL_DEBUG_INFO("SKU OP mode is mrc\n");
+               iwl_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+                           CSR_HW_IF_CONFIG_REG_BIT_SKU_MRC);
+       } else
+               IWL_DEBUG_INFO("SKU OP mode is basic\n");
+
+       if ((priv->eeprom.board_revision & 0xF0) == 0xD0) {
+               IWL_DEBUG_INFO("3945ABG revision is 0x%X\n",
+                              priv->eeprom.board_revision);
+               iwl_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+                           CSR_HW_IF_CONFIG_REG_BIT_BOARD_TYPE);
+       } else {
+               IWL_DEBUG_INFO("3945ABG revision is 0x%X\n",
+                              priv->eeprom.board_revision);
+               iwl_clear_bit(priv, CSR_HW_IF_CONFIG_REG,
+                             CSR_HW_IF_CONFIG_REG_BIT_BOARD_TYPE);
+       }
+
+       if (priv->eeprom.almgor_m_version <= 1) {
+               iwl_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+                           CSR_HW_IF_CONFIG_REG_BITS_SILICON_TYPE_A);
+               IWL_DEBUG_INFO("Card M type A version is 0x%X\n",
+                              priv->eeprom.almgor_m_version);
+       } else {
+               IWL_DEBUG_INFO("Card M type B version is 0x%X\n",
+                              priv->eeprom.almgor_m_version);
+               iwl_set_bit(priv, CSR_HW_IF_CONFIG_REG,
+                           CSR_HW_IF_CONFIG_REG_BITS_SILICON_TYPE_B);
+       }
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       if (priv->eeprom.sku_cap & EEPROM_SKU_CAP_SW_RF_KILL_ENABLE)
+               IWL_DEBUG_RF_KILL("SW RF KILL supported in EEPROM.\n");
+
+       if (priv->eeprom.sku_cap & EEPROM_SKU_CAP_HW_RF_KILL_ENABLE)
+               IWL_DEBUG_RF_KILL("HW RF KILL supported in EEPROM.\n");
+
+       /* Allocate the RX queue, or reset if it is already allocated */
+       if (!rxq->bd) {
+               rc = iwl_rx_queue_alloc(priv);
+               if (rc) {
+                       IWL_ERROR("Unable to initialize Rx queue\n");
+                       return -ENOMEM;
+               }
+       } else
+               iwl_rx_queue_reset(priv, rxq);
+
+       iwl_rx_replenish(priv);
+
+       iwl3945_rx_init(priv, rxq);
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       /* Look at using this instead:
+       rxq->need_update = 1;
+       iwl_rx_queue_update_write_ptr(priv, rxq);
+       */
+
+       rc = iwl_grab_restricted_access(priv);
+       if (rc) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return rc;
+       }
+       iwl_write_restricted(priv, FH_RCSR_WPTR(0), rxq->write & ~7);
+       iwl_release_restricted_access(priv);
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       rc = iwl3945_txq_ctx_reset(priv);
+       if (rc)
+               return rc;
+
+       set_bit(STATUS_INIT, &priv->status);
+
+       return 0;
+}
+
+/**
+ * iwl_hw_txq_ctx_free - Free TXQ Context
+ *
+ * Destroy all TX DMA queues and structures
+ */
+void iwl_hw_txq_ctx_free(struct iwl_priv *priv)
+{
+       int txq_id;
+
+       /* Tx queues */
+       for (txq_id = 0; txq_id < TFD_QUEUE_MAX; txq_id++)
+               iwl_tx_queue_free(priv, &priv->txq[txq_id]);
+}
+
+void iwl_hw_txq_ctx_stop(struct iwl_priv *priv)
+{
+       int queue;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       if (iwl_grab_restricted_access(priv)) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               iwl_hw_txq_ctx_free(priv);
+               return;
+       }
+
+       /* stop SCD */
+       iwl_write_restricted_reg(priv, SCD_MODE_REG, 0);
+
+       /* reset TFD queues */
+       for (queue = TFD_QUEUE_MIN; queue < TFD_QUEUE_MAX; queue++) {
+               iwl_write_restricted(priv, FH_TCSR_CONFIG(queue), 0x0);
+               iwl_poll_restricted_bit(priv, FH_TSSR_TX_STATUS,
+                               ALM_FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(queue),
+                               1000);
+       }
+
+       iwl_release_restricted_access(priv);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       iwl_hw_txq_ctx_free(priv);
+}
+
+int iwl_hw_nic_stop_master(struct iwl_priv *priv)
+{
+       int rc = 0;
+       u32 reg_val;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       /* set stop master bit */
+       iwl_set_bit(priv, CSR_RESET, CSR_RESET_REG_FLAG_STOP_MASTER);
+
+       reg_val = iwl_read32(priv, CSR_GP_CNTRL);
+
+       if (CSR_GP_CNTRL_REG_FLAG_MAC_POWER_SAVE ==
+           (reg_val & CSR_GP_CNTRL_REG_MSK_POWER_SAVE_TYPE))
+               IWL_DEBUG_INFO("Card in power save, master is already "
+                              "stopped\n");
+       else {
+               rc = iwl_poll_bit(priv, CSR_RESET,
+                                 CSR_RESET_REG_FLAG_MASTER_DISABLED,
+                                 CSR_RESET_REG_FLAG_MASTER_DISABLED, 100);
+               if (rc < 0) {
+                       spin_unlock_irqrestore(&priv->lock, flags);
+                       return rc;
+               }
+       }
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+       IWL_DEBUG_INFO("stop master\n");
+
+       return rc;
+}
+
+int iwl_hw_nic_reset(struct iwl_priv *priv)
+{
+       int rc;
+       unsigned long flags;
+
+       iwl_hw_nic_stop_master(priv);
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       iwl_set_bit(priv, CSR_RESET, CSR_RESET_REG_FLAG_SW_RESET);
+
+       rc = iwl_poll_bit(priv, CSR_GP_CNTRL,
+                         CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY,
+                         CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, 25000);
+
+       rc = iwl_grab_restricted_access(priv);
+       if (!rc) {
+               iwl_write_restricted_reg(priv, APMG_CLK_CTRL_REG,
+                                        APMG_CLK_VAL_BSM_CLK_RQT);
+
+               udelay(10);
+
+               iwl_set_bit(priv, CSR_GP_CNTRL,
+                           CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
+
+               iwl_write_restricted_reg(priv, APMG_RTC_INT_MSK_REG, 0x0);
+               iwl_write_restricted_reg(priv, APMG_RTC_INT_STT_REG,
+                                       0xFFFFFFFF);
+
+               /* enable DMA */
+               iwl_write_restricted_reg(priv, APMG_CLK_EN_REG,
+                                        APMG_CLK_VAL_DMA_CLK_RQT |
+                                        APMG_CLK_VAL_BSM_CLK_RQT);
+               udelay(10);
+
+               iwl_set_bits_restricted_reg(priv, APMG_PS_CTRL_REG,
+                               APMG_PS_CTRL_VAL_RESET_REQ);
+               udelay(5);
+               iwl_clear_bits_restricted_reg(priv, APMG_PS_CTRL_REG,
+                               APMG_PS_CTRL_VAL_RESET_REQ);
+               iwl_release_restricted_access(priv);
+       }
+
+       /* Clear the 'host command active' bit... */
+       clear_bit(STATUS_HCMD_ACTIVE, &priv->status);
+
+       wake_up_interruptible(&priv->wait_command_queue);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return rc;
+}
+
+/**
+ * iwl_hw_reg_adjust_power_by_temp - return index delta into power gain settings table
+ */
+static int iwl_hw_reg_adjust_power_by_temp(int new_reading, int old_reading)
+{
+       return (new_reading - old_reading) * (-11) / 100;
+}
+
+/**
+ * iwl_hw_reg_temp_out_of_range - Keep temperature in sane range
+ */
+static inline int iwl_hw_reg_temp_out_of_range(int temperature)
+{
+       return (((temperature < -260) || (temperature > 25)) ? 1 : 0);
+}
+
+int iwl_hw_get_temperature(struct iwl_priv *priv)
+{
+       return iwl_read32(priv, CSR_UCODE_DRV_GP2);
+}
+
+/**
+ * iwl_hw_reg_txpower_get_temperature - get current temperature by reading from NIC
+ */
+static int iwl_hw_reg_txpower_get_temperature(struct iwl_priv *priv)
+{
+       int temperature;
+
+       temperature = iwl_hw_get_temperature(priv);
+
+       /* driver's okay range is -260 to +25.
+        *   human readable okay range is 0 to +285 */
+       IWL_DEBUG_INFO("Temperature: %d\n", temperature + IWL_TEMP_CONVERT);
+
+       /* handle insane temp reading */
+       if (iwl_hw_reg_temp_out_of_range(temperature)) {
+               IWL_ERROR("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 = priv->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
+
+/**
+ * is_temp_calib_needed - determines if new calibration is needed
+ *
+ * records new temperature in tx_mgr->temperature.
+ * replaces tx_mgr->last_temperature *only* if calib needed
+ *    (assumes caller will actually do the calibration!). */
+static int is_temp_calib_needed(struct iwl_priv *priv)
+{
+       int temp_diff;
+
+       priv->temperature = iwl_hw_reg_txpower_get_temperature(priv);
+       temp_diff = priv->temperature - priv->last_temperature;
+
+       /* get absolute value */
+       if (temp_diff < 0) {
+               IWL_DEBUG_POWER("Getting cooler, delta %d,\n", temp_diff);
+               temp_diff = -temp_diff;
+       } else if (temp_diff == 0)
+               IWL_DEBUG_POWER("Same temp,\n");
+       else
+               IWL_DEBUG_POWER("Getting warmer, delta %d,\n", temp_diff);
+
+       /* if we don't need calibration, *don't* update last_temperature */
+       if (temp_diff < IWL_TEMPERATURE_LIMIT_TIMER) {
+               IWL_DEBUG_POWER("Timed thermal calib not needed\n");
+               return 0;
+       }
+
+       IWL_DEBUG_POWER("Timed thermal calib needed\n");
+
+       /* assume that caller will actually do calib ...
+        *   update the "last temperature" value */
+       priv->last_temperature = priv->temperature;
+       return 1;
+}
+
+#define IWL_MAX_GAIN_ENTRIES 78
+#define IWL_CCK_FROM_OFDM_POWER_DIFF  -5
+#define IWL_CCK_FROM_OFDM_INDEX_DIFF (10)
+
+/* radio and DSP power table, each step is 1/2 dB.
+ * 1st number is for RF analog gain, 2nd number is for DSP pre-DAC gain. */
+static struct iwl_tx_power power_gain_table[2][IWL_MAX_GAIN_ENTRIES] = {
+       {
+        {251, 127},            /* 2.4 GHz, highest power */
+        {251, 127},
+        {251, 127},
+        {251, 127},
+        {251, 125},
+        {251, 110},
+        {251, 105},
+        {251, 98},
+        {187, 125},
+        {187, 115},
+        {187, 108},
+        {187, 99},
+        {243, 119},
+        {243, 111},
+        {243, 105},
+        {243, 97},
+        {243, 92},
+        {211, 106},
+        {211, 100},
+        {179, 120},
+        {179, 113},
+        {179, 107},
+        {147, 125},
+        {147, 119},
+        {147, 112},
+        {147, 106},
+        {147, 101},
+        {147, 97},
+        {147, 91},
+        {115, 107},
+        {235, 121},
+        {235, 115},
+        {235, 109},
+        {203, 127},
+        {203, 121},
+        {203, 115},
+        {203, 108},
+        {203, 102},
+        {203, 96},
+        {203, 92},
+        {171, 110},
+        {171, 104},
+        {171, 98},
+        {139, 116},
+        {227, 125},
+        {227, 119},
+        {227, 113},
+        {227, 107},
+        {227, 101},
+        {227, 96},
+        {195, 113},
+        {195, 106},
+        {195, 102},
+        {195, 95},
+        {163, 113},
+        {163, 106},
+        {163, 102},
+        {163, 95},
+        {131, 113},
+        {131, 106},
+        {131, 102},
+        {131, 95},
+        {99, 113},
+        {99, 106},
+        {99, 102},
+        {99, 95},
+        {67, 113},
+        {67, 106},
+        {67, 102},
+        {67, 95},
+        {35, 113},
+        {35, 106},
+        {35, 102},
+        {35, 95},
+        {3, 113},
+        {3, 106},
+        {3, 102},
+        {3, 95} },             /* 2.4 GHz, lowest power */
+       {
+        {251, 127},            /* 5.x GHz, highest power */
+        {251, 120},
+        {251, 114},
+        {219, 119},
+        {219, 101},
+        {187, 113},
+        {187, 102},
+        {155, 114},
+        {155, 103},
+        {123, 117},
+        {123, 107},
+        {123, 99},
+        {123, 92},
+        {91, 108},
+        {59, 125},
+        {59, 118},
+        {59, 109},
+        {59, 102},
+        {59, 96},
+        {59, 90},
+        {27, 104},
+        {27, 98},
+        {27, 92},
+        {115, 118},
+        {115, 111},
+        {115, 104},
+        {83, 126},
+        {83, 121},
+        {83, 113},
+        {83, 105},
+        {83, 99},
+        {51, 118},
+        {51, 111},
+        {51, 104},
+        {51, 98},
+        {19, 116},
+        {19, 109},
+        {19, 102},
+        {19, 98},
+        {19, 93},
+        {171, 113},
+        {171, 107},
+        {171, 99},
+        {139, 120},
+        {139, 113},
+        {139, 107},
+        {139, 99},
+        {107, 120},
+        {107, 113},
+        {107, 107},
+        {107, 99},
+        {75, 120},
+        {75, 113},
+        {75, 107},
+        {75, 99},
+        {43, 120},
+        {43, 113},
+        {43, 107},
+        {43, 99},
+        {11, 120},
+        {11, 113},
+        {11, 107},
+        {11, 99},
+        {131, 107},
+        {131, 99},
+        {99, 120},
+        {99, 113},
+        {99, 107},
+        {99, 99},
+        {67, 120},
+        {67, 113},
+        {67, 107},
+        {67, 99},
+        {35, 120},
+        {35, 113},
+        {35, 107},
+        {35, 99},
+        {3, 120} }             /* 5.x GHz, lowest power */
+};
+
+static inline u8 iwl_hw_reg_fix_power_index(int index)
+{
+       if (index < 0)
+               return 0;
+       if (index >= IWL_MAX_GAIN_ENTRIES)
+               return IWL_MAX_GAIN_ENTRIES - 1;
+       return (u8) index;
+}
+
+/* Kick off thermal recalibration check every 60 seconds */
+#define REG_RECALIB_PERIOD (60)
+
+/**
+ * iwl_hw_reg_set_scan_power - Set Tx power for scan probe requests
+ *
+ * Set (in our channel info database) the direct scan Tx power for 1 Mbit (CCK)
+ * or 6 Mbit (OFDM) rates.
+ */
+static void iwl_hw_reg_set_scan_power(struct iwl_priv *priv, u32 scan_tbl_index,
+                              s32 rate_index, const s8 *clip_pwrs,
+                              struct iwl_channel_info *ch_info,
+                              int band_index)
+{
+       struct iwl_scan_power_info *scan_power_info;
+       s8 power;
+       u8 power_index;
+
+       scan_power_info = &ch_info->scan_pwr_info[scan_tbl_index];
+
+       /* use this channel group's 6Mbit clipping/saturation pwr,
+        *   but cap at regulatory scan power restriction (set during init
+        *   based on eeprom channel data) for this channel.  */
+       power = min(ch_info->scan_power, clip_pwrs[IWL_RATE_6M_INDEX]);
+
+       /* further limit to user's max power preference.
+        * FIXME:  Other spectrum management power limitations do not
+        *   seem to apply?? */
+       power = min(power, priv->user_txpower_limit);
+       scan_power_info->requested_power = power;
+
+       /* find difference between new scan *power* and current "normal"
+        *   Tx *power* for 6Mb.  Use this difference (x2) to adjust the
+        *   current "normal" temperature-compensated Tx power *index* for
+        *   this rate (1Mb or 6Mb) to yield new temp-compensated scan power
+        *   *index*. */
+       power_index = ch_info->power_info[rate_index].power_table_index
+           - (power - ch_info->power_info
+              [IWL_RATE_6M_INDEX].requested_power) * 2;
+
+       /* store reference index that we use when adjusting *all* scan
+        *   powers.  So we can accommodate user (all channel) or spectrum
+        *   management (single channel) power changes "between" temperature
+        *   feedback compensation procedures.
+        * don't force fit this reference index into gain table; it may be a
+        *   negative number.  This will help avoid errors when we're at
+        *   the lower bounds (highest gains, for warmest temperatures)
+        *   of the table. */
+
+       /* don't exceed table bounds for "real" setting */
+       power_index = iwl_hw_reg_fix_power_index(power_index);
+
+       scan_power_info->power_table_index = power_index;
+       scan_power_info->tpc.tx_gain =
+           power_gain_table[band_index][power_index].tx_gain;
+       scan_power_info->tpc.dsp_atten =
+           power_gain_table[band_index][power_index].dsp_atten;
+}
+
+/**
+ * iwl_hw_reg_send_txpower - fill in Tx Power command with gain settings
+ *
+ * Configures power settings for all rates for the current channel,
+ * using values from channel info struct, and send to NIC
+ */
+int iwl_hw_reg_send_txpower(struct iwl_priv *priv)
+{
+       int rate_idx;
+       const struct iwl_channel_info *ch_info = NULL;
+       struct iwl_txpowertable_cmd txpower = {
+               .channel = priv->active_rxon.channel,
+       };
+
+       txpower.band = (priv->phymode == MODE_IEEE80211A) ? 0 : 1;
+       ch_info = iwl_get_channel_info(priv,
+                                      priv->phymode,
+                                      le16_to_cpu(priv->active_rxon.channel));
+       if (!ch_info) {
+               IWL_ERROR
+                   ("Failed to get channel info for channel %d [%d]\n",
+                    le16_to_cpu(priv->active_rxon.channel), priv->phymode);
+               return -EINVAL;
+       }
+
+       if (!is_channel_valid(ch_info)) {
+               IWL_DEBUG_POWER("Not calling TX_PWR_TABLE_CMD on "
+                               "non-Tx channel.\n");
+               return 0;
+       }
+
+       /* fill cmd with power settings for all rates for current channel */
+       for (rate_idx = 0; rate_idx < IWL_RATE_COUNT; rate_idx++) {
+               txpower.power[rate_idx].tpc = ch_info->power_info[rate_idx].tpc;
+               txpower.power[rate_idx].rate = iwl_rates[rate_idx].plcp;
+
+               IWL_DEBUG_POWER("ch %d:%d rf %d dsp %3d rate code 0x%02x\n",
+                               le16_to_cpu(txpower.channel),
+                               txpower.band,
+                               txpower.power[rate_idx].tpc.tx_gain,
+                               txpower.power[rate_idx].tpc.dsp_atten,
+                               txpower.power[rate_idx].rate);
+       }
+
+       return iwl_send_cmd_pdu(priv, REPLY_TX_PWR_TABLE_CMD,
+                               sizeof(struct iwl_txpowertable_cmd), &txpower);
+
+}
+
+/**
+ * iwl_hw_reg_set_new_power - Configures power tables at new levels
+ * @ch_info: Channel to update.  Uses power_info.requested_power.
+ *
+ * Replace requested_power and base_power_index ch_info fields for
+ * one channel.
+ *
+ * Called if user or spectrum management changes power preferences.
+ * Takes into account h/w and modulation limitations (clip power).
+ *
+ * This does *not* send anything to NIC, just sets up ch_info for one channel.
+ *
+ * NOTE: reg_compensate_for_temperature_dif() *must* be run after this to
+ *      properly fill out the scan powers, and actual h/w gain settings,
+ *      and send changes to NIC
+ */
+static int iwl_hw_reg_set_new_power(struct iwl_priv *priv,
+                            struct iwl_channel_info *ch_info)
+{
+       struct iwl_channel_power_info *power_info;
+       int power_changed = 0;
+       int i;
+       const s8 *clip_pwrs;
+       int power;
+
+       /* Get this chnlgrp's rate-to-max/clip-powers table */
+       clip_pwrs = priv->clip_groups[ch_info->group_index].clip_powers;
+
+       /* Get this channel's rate-to-current-power settings table */
+       power_info = ch_info->power_info;
+
+       /* update OFDM Txpower settings */
+       for (i = IWL_FIRST_OFDM_RATE; i <= IWL_LAST_OFDM_RATE;
+            i++, ++power_info) {
+               int delta_idx;
+
+               /* limit new power to be no more than h/w capability */
+               power = min(ch_info->curr_txpow, clip_pwrs[i]);
+               if (power == power_info->requested_power)
+                       continue;
+
+               /* find difference between old and new requested powers,
+                *    update base (non-temp-compensated) power index */
+               delta_idx = (power - power_info->requested_power) * 2;
+               power_info->base_power_index -= delta_idx;
+
+               /* save new requested power value */
+               power_info->requested_power = power;
+
+               power_changed = 1;
+       }
+
+       /* update CCK Txpower settings, based on OFDM 12M setting ...
+        *    ... all CCK power settings for a given channel are the *same*. */
+       if (power_changed) {
+               power =
+                   ch_info->power_info[IWL_RATE_12M_INDEX].
+                   requested_power + IWL_CCK_FROM_OFDM_POWER_DIFF;
+
+               /* do all CCK rates' iwl_channel_power_info structures */
+               for (i = IWL_FIRST_CCK_RATE; i <= IWL_LAST_CCK_RATE; i++) {
+                       power_info->requested_power = power;
+                       power_info->base_power_index =
+                           ch_info->power_info[IWL_RATE_12M_INDEX].
+                           base_power_index + IWL_CCK_FROM_OFDM_INDEX_DIFF;
+                       ++power_info;
+               }
+       }
+
+       return 0;
+}
+
+/**
+ * iwl_hw_reg_get_ch_txpower_limit - returns new power limit for channel
+ *
+ * NOTE: Returned power limit may be less (but not more) than requested,
+ *      based strictly on regulatory (eeprom and spectrum mgt) limitations
+ *      (no consideration for h/w clipping limitations).
+ */
+static int iwl_hw_reg_get_ch_txpower_limit(struct iwl_channel_info *ch_info)
+{
+       s8 max_power;
+
+#if 0
+       /* if we're using TGd limits, use lower of TGd or EEPROM */
+       if (ch_info->tgd_data.max_power != 0)
+               max_power = min(ch_info->tgd_data.max_power,
+                               ch_info->eeprom.max_power_avg);
+
+       /* else just use EEPROM limits */
+       else
+#endif
+               max_power = ch_info->eeprom.max_power_avg;
+
+       return min(max_power, ch_info->max_power_avg);
+}
+
+/**
+ * iwl_hw_reg_comp_txpower_temp - Compensate for temperature
+ *
+ * Compensate txpower settings of *all* channels for temperature.
+ * This only accounts for the difference between current temperature
+ *   and the factory calibration temperatures, and bases the new settings
+ *   on the channel's base_power_index.
+ *
+ * If RxOn is "associated", this sends the new Txpower to NIC!
+ */
+static int iwl_hw_reg_comp_txpower_temp(struct iwl_priv *priv)
+{
+       struct iwl_channel_info *ch_info = NULL;
+       int delta_index;
+       const s8 *clip_pwrs; /* array of h/w max power levels for each rate */
+       u8 a_band;
+       u8 rate_index;
+       u8 scan_tbl_index;
+       u8 i;
+       int ref_temp;
+       int temperature = priv->temperature;
+
+       /* set up new Tx power info for each and every channel, 2.4 and 5.x */
+       for (i = 0; i < priv->channel_count; i++) {
+               ch_info = &priv->channel_info[i];
+               a_band = is_channel_a_band(ch_info);
+
+               /* Get this chnlgrp's factory calibration temperature */
+               ref_temp = (s16)priv->eeprom.groups[ch_info->group_index].
+                   temperature;
+
+               /* get power index adjustment based on curr and factory
+                * temps */
+               delta_index = iwl_hw_reg_adjust_power_by_temp(temperature,
+                                                             ref_temp);
+
+               /* set tx power value for all rates, OFDM and CCK */
+               for (rate_index = 0; rate_index < IWL_RATE_COUNT;
+                    rate_index++) {
+                       int power_idx =
+                           ch_info->power_info[rate_index].base_power_index;
+
+                       /* temperature compensate */
+                       power_idx += delta_index;
+
+                       /* stay within table range */
+                       power_idx = iwl_hw_reg_fix_power_index(power_idx);
+                       ch_info->power_info[rate_index].
+                           power_table_index = (u8) power_idx;
+                       ch_info->power_info[rate_index].tpc =
+                           power_gain_table[a_band][power_idx];
+               }
+
+               /* Get this chnlgrp's rate-to-max/clip-powers table */
+               clip_pwrs = priv->clip_groups[ch_info->group_index].clip_powers;
+
+               /* set scan tx power, 1Mbit for CCK, 6Mbit for OFDM */
+               for (scan_tbl_index = 0;
+                    scan_tbl_index < IWL_NUM_SCAN_RATES; scan_tbl_index++) {
+                       s32 actual_index = (scan_tbl_index == 0) ?
+                           IWL_RATE_1M_INDEX : IWL_RATE_6M_INDEX;
+                       iwl_hw_reg_set_scan_power(priv, scan_tbl_index,
+                                          actual_index, clip_pwrs,
+                                          ch_info, a_band);
+               }
+       }
+
+       /* send Txpower command for current channel to ucode */
+       return iwl_hw_reg_send_txpower(priv);
+}
+
+int iwl_hw_reg_set_txpower(struct iwl_priv *priv, s8 power)
+{
+       struct iwl_channel_info *ch_info;
+       s8 max_power;
+       u8 a_band;
+       u8 i;
+
+       if (priv->user_txpower_limit == power) {
+               IWL_DEBUG_POWER("Requested Tx power same as current "
+                               "limit: %ddBm.\n", power);
+               return 0;
+       }
+
+       IWL_DEBUG_POWER("Setting upper limit clamp to %ddBm.\n", power);
+       priv->user_txpower_limit = power;
+
+       /* set up new Tx powers for each and every channel, 2.4 and 5.x */
+
+       for (i = 0; i < priv->channel_count; i++) {
+               ch_info = &priv->channel_info[i];
+               a_band = is_channel_a_band(ch_info);
+
+               /* find minimum power of all user and regulatory constraints
+                *    (does not consider h/w clipping limitations) */
+               max_power = iwl_hw_reg_get_ch_txpower_limit(ch_info);
+               max_power = min(power, max_power);
+               if (max_power != ch_info->curr_txpow) {
+                       ch_info->curr_txpow = max_power;
+
+                       /* this considers the h/w clipping limitations */
+                       iwl_hw_reg_set_new_power(priv, ch_info);
+               }
+       }
+
+       /* update txpower settings for all channels,
+        *   send to NIC if associated. */
+       is_temp_calib_needed(priv);
+       iwl_hw_reg_comp_txpower_temp(priv);
+
+       return 0;
+}
+
+/* will add 3945 channel switch cmd handling later */
+int iwl_hw_channel_switch(struct iwl_priv *priv, u16 channel)
+{
+       return 0;
+}
+
+/**
+ * iwl3945_reg_txpower_periodic -  called when time to check our temperature.
+ *
+ * -- reset periodic timer
+ * -- see if temp has changed enough to warrant re-calibration ... if so:
+ *     -- correct coeffs for temp (can reset temp timer)
+ *     -- save this temp as "last",
+ *     -- send new set of gain settings to NIC
+ * NOTE:  This should continue working, even when we're not associated,
+ *   so we can keep our internal table of scan powers current. */
+void iwl3945_reg_txpower_periodic(struct iwl_priv *priv)
+{
+       /* This will kick in the "brute force"
+        * iwl_hw_reg_comp_txpower_temp() below */
+       if (!is_temp_calib_needed(priv))
+               goto reschedule;
+
+       /* Set up a new set of temp-adjusted TxPowers, send to NIC.
+        * This is based *only* on current temperature,
+        * ignoring any previous power measurements */
+       iwl_hw_reg_comp_txpower_temp(priv);
+
+ reschedule:
+       queue_delayed_work(priv->workqueue,
+                          &priv->thermal_periodic, REG_RECALIB_PERIOD * HZ);
+}
+
+void iwl3945_bg_reg_txpower_periodic(struct work_struct *work)
+{
+       struct iwl_priv *priv = container_of(work, struct iwl_priv,
+                                            thermal_periodic.work);
+
+       if (test_bit(STATUS_EXIT_PENDING, &priv->status))
+               return;
+
+       mutex_lock(&priv->mutex);
+       iwl3945_reg_txpower_periodic(priv);
+       mutex_unlock(&priv->mutex);
+}
+
+/**
+ * iwl_hw_reg_get_ch_grp_index - find the channel-group index (0-4)
+ *                                for the channel.
+ *
+ * This function is used when initializing channel-info structs.
+ *
+ * NOTE: These channel groups do *NOT* match the bands above!
+ *      These channel groups are based on factory-tested channels;
+ *      on A-band, EEPROM's "group frequency" entries represent the top
+ *      channel in each group 1-4.  Group 5 All B/G channels are in group 0.
+ */
+static u16 iwl_hw_reg_get_ch_grp_index(struct iwl_priv *priv,
+                                      const struct iwl_channel_info *ch_info)
+{
+       struct iwl_eeprom_txpower_group *ch_grp = &priv->eeprom.groups[0];
+       u8 group;
+       u16 group_index = 0;    /* based on factory calib frequencies */
+       u8 grp_channel;
+
+       /* Find the group index for the channel ... don't use index 1(?) */
+       if (is_channel_a_band(ch_info)) {
+               for (group = 1; group < 5; group++) {
+                       grp_channel = ch_grp[group].group_channel;
+                       if (ch_info->channel <= grp_channel) {
+                               group_index = group;
+                               break;
+                       }
+               }
+               /* group 4 has a few channels *above* its factory cal freq */
+               if (group == 5)
+                       group_index = 4;
+       } else
+               group_index = 0;        /* 2.4 GHz, group 0 */
+
+       IWL_DEBUG_POWER("Chnl %d mapped to grp %d\n", ch_info->channel,
+                       group_index);
+       return group_index;
+}
+
+/**
+ * iwl_hw_reg_get_matched_power_index - Interpolate to get nominal index
+ *
+ * Interpolate to get nominal (i.e. at factory calibration temperature) index
+ *   into radio/DSP gain settings table for requested power.
+ */
+static int iwl_hw_reg_get_matched_power_index(struct iwl_priv *priv,
+                                      s8 requested_power,
+                                      s32 setting_index, s32 *new_index)
+{
+       const struct iwl_eeprom_txpower_group *chnl_grp = NULL;
+       s32 index0, index1;
+       s32 power = 2 * requested_power;
+       s32 i;
+       const struct iwl_eeprom_txpower_sample *samples;
+       s32 gains0, gains1;
+       s32 res;
+       s32 denominator;
+
+       chnl_grp = &priv->eeprom.groups[setting_index];
+       samples = chnl_grp->samples;
+       for (i = 0; i < 5; i++) {
+               if (power == samples[i].power) {
+                       *new_index = samples[i].gain_index;
+                       return 0;
+               }
+       }
+
+       if (power > samples[1].power) {
+               index0 = 0;
+               index1 = 1;
+       } else if (power > samples[2].power) {
+               index0 = 1;
+               index1 = 2;
+       } else if (power > samples[3].power) {
+               index0 = 2;
+               index1 = 3;
+       } else {
+               index0 = 3;
+               index1 = 4;
+       }
+
+       denominator = (s32) samples[index1].power - (s32) samples[index0].power;
+       if (denominator == 0)
+               return -EINVAL;
+       gains0 = (s32) samples[index0].gain_index * (1 << 19);
+       gains1 = (s32) samples[index1].gain_index * (1 << 19);
+       res = gains0 + (gains1 - gains0) *
+           ((s32) power - (s32) samples[index0].power) / denominator +
+           (1 << 18);
+       *new_index = res >> 19;
+       return 0;
+}
+
+static void iwl_hw_reg_init_channel_groups(struct iwl_priv *priv)
+{
+       u32 i;
+       s32 rate_index;
+       const struct iwl_eeprom_txpower_group *group;
+
+       IWL_DEBUG_POWER("Initializing factory calib info from EEPROM\n");
+
+       for (i = 0; i < IWL_NUM_TX_CALIB_GROUPS; i++) {
+               s8 *clip_pwrs;  /* table of power levels for each rate */
+               s8 satur_pwr;   /* saturation power for each chnl group */
+               group = &priv->eeprom.groups[i];
+
+               /* sanity check on factory saturation power value */
+               if (group->saturation_power < 40) {
+                       IWL_WARNING("Error: saturation power is %d, "
+                                   "less than minimum expected 40\n",
+                                   group->saturation_power);
+                       return;
+               }
+
+               /*
+                * Derive requested power levels for each rate, based on
+                *   hardware capabilities (saturation power for band).
+                * Basic value is 3dB down from saturation, with further
+                *   power reductions for highest 3 data rates.  These
+                *   backoffs provide headroom for high rate modulation
+                *   power peaks, without too much distortion (clipping).
+                */
+               /* we'll fill in this array with h/w max power levels */
+               clip_pwrs = (s8 *) priv->clip_groups[i].clip_powers;
+
+               /* divide factory saturation power by 2 to find -3dB level */
+               satur_pwr = (s8) (group->saturation_power >> 1);
+
+               /* fill in channel group's nominal powers for each rate */
+               for (rate_index = 0;
+                    rate_index < IWL_RATE_COUNT; rate_index++, clip_pwrs++) {
+                       switch (rate_index) {
+                       case IWL_RATE_36M_INDEX:
+                               if (i == 0)     /* B/G */
+                                       *clip_pwrs = satur_pwr;
+                               else    /* A */
+                                       *clip_pwrs = satur_pwr - 5;
+                               break;
+                       case IWL_RATE_48M_INDEX:
+                               if (i == 0)
+                                       *clip_pwrs = satur_pwr - 7;
+                               else
+                                       *clip_pwrs = satur_pwr - 10;
+                               break;
+                       case IWL_RATE_54M_INDEX:
+                               if (i == 0)
+                                       *clip_pwrs = satur_pwr - 9;
+                               else
+                                       *clip_pwrs = satur_pwr - 12;
+                               break;
+                       default:
+                               *clip_pwrs = satur_pwr;
+                               break;
+                       }
+               }
+       }
+}
+
+/**
+ * iwl3945_txpower_set_from_eeprom - Set channel power info based on EEPROM
+ *
+ * Second pass (during init) to set up priv->channel_info
+ *
+ * Set up Tx-power settings in our channel info database for each VALID
+ * (for this geo/SKU) channel, at all Tx data rates, based on eeprom values
+ * and current temperature.
+ *
+ * Since this is based on current temperature (at init time), these values may
+ * not be valid for very long, but it gives us a starting/default point,
+ * and allows us to active (i.e. using Tx) scan.
+ *
+ * This does *not* write values to NIC, just sets up our internal table.
+ */
+int iwl3945_txpower_set_from_eeprom(struct iwl_priv *priv)
+{
+       struct iwl_channel_info *ch_info = NULL;
+       struct iwl_channel_power_info *pwr_info;
+       int delta_index;
+       u8 rate_index;
+       u8 scan_tbl_index;
+       const s8 *clip_pwrs;    /* array of power levels for each rate */
+       u8 gain, dsp_atten;
+       s8 power;
+       u8 pwr_index, base_pwr_index, a_band;
+       u8 i;
+       int temperature;
+
+       /* save temperature reference,
+        *   so we can determine next time to calibrate */
+       temperature = iwl_hw_reg_txpower_get_temperature(priv);
+       priv->last_temperature = temperature;
+
+       iwl_hw_reg_init_channel_groups(priv);
+
+       /* initialize Tx power info for each and every channel, 2.4 and 5.x */
+       for (i = 0, ch_info = priv->channel_info; i < priv->channel_count;
+            i++, ch_info++) {
+               a_band = is_channel_a_band(ch_info);
+               if (!is_channel_valid(ch_info))
+                       continue;
+
+               /* find this channel's channel group (*not* "band") index */
+               ch_info->group_index =
+                       iwl_hw_reg_get_ch_grp_index(priv, ch_info);
+
+               /* Get this chnlgrp's rate->max/clip-powers table */
+               clip_pwrs = priv->clip_groups[ch_info->group_index].clip_powers;
+
+               /* calculate power index *adjustment* value according to
+                *  diff between current temperature and factory temperature */
+               delta_index = iwl_hw_reg_adjust_power_by_temp(temperature,
+                               priv->eeprom.groups[ch_info->group_index].
+                               temperature);
+
+               IWL_DEBUG_POWER("Delta index for channel %d: %d [%d]\n",
+                               ch_info->channel, delta_index, temperature +
+                               IWL_TEMP_CONVERT);
+
+               /* set tx power value for all OFDM rates */
+               for (rate_index = 0; rate_index < IWL_OFDM_RATES;
+                    rate_index++) {
+                       s32 power_idx;
+                       int rc;
+
+                       /* use channel group's clip-power table,
+                        *   but don't exceed channel's max power */
+                       s8 pwr = min(ch_info->max_power_avg,
+                                    clip_pwrs[rate_index]);
+
+                       pwr_info = &ch_info->power_info[rate_index];
+
+                       /* get base (i.e. at factory-measured temperature)
+                        *    power table index for this rate's power */
+                       rc = iwl_hw_reg_get_matched_power_index(priv, pwr,
+                                                        ch_info->group_index,
+                                                        &power_idx);
+                       if (rc) {
+                               IWL_ERROR("Invalid power index\n");
+                               return rc;
+                       }
+                       pwr_info->base_power_index = (u8) power_idx;
+
+                       /* temperature compensate */
+                       power_idx += delta_index;
+
+                       /* stay within range of gain table */
+                       power_idx = iwl_hw_reg_fix_power_index(power_idx);
+
+                       /* fill 1 OFDM rate's iwl_channel_power_info struct */
+                       pwr_info->requested_power = pwr;
+                       pwr_info->power_table_index = (u8) power_idx;
+                       pwr_info->tpc.tx_gain =
+                           power_gain_table[a_band][power_idx].tx_gain;
+                       pwr_info->tpc.dsp_atten =
+                           power_gain_table[a_band][power_idx].dsp_atten;
+               }
+
+               /* set tx power for CCK rates, based on OFDM 12 Mbit settings*/
+               pwr_info = &ch_info->power_info[IWL_RATE_12M_INDEX];
+               power = pwr_info->requested_power +
+                       IWL_CCK_FROM_OFDM_POWER_DIFF;
+               pwr_index = pwr_info->power_table_index +
+                       IWL_CCK_FROM_OFDM_INDEX_DIFF;
+               base_pwr_index = pwr_info->base_power_index +
+                       IWL_CCK_FROM_OFDM_INDEX_DIFF;
+
+               /* stay within table range */
+               pwr_index = iwl_hw_reg_fix_power_index(pwr_index);
+               gain = power_gain_table[a_band][pwr_index].tx_gain;
+               dsp_atten = power_gain_table[a_band][pwr_index].dsp_atten;
+
+               /* fill each CCK rate's iwl_channel_power_info structure
+                * NOTE:  All CCK-rate Txpwrs are the same for a given chnl!
+                * NOTE:  CCK rates start at end of OFDM rates! */
+               for (rate_index = IWL_OFDM_RATES;
+                    rate_index < IWL_RATE_COUNT; rate_index++) {
+                       pwr_info = &ch_info->power_info[rate_index];
+                       pwr_info->requested_power = power;
+                       pwr_info->power_table_index = pwr_index;
+                       pwr_info->base_power_index = base_pwr_index;
+                       pwr_info->tpc.tx_gain = gain;
+                       pwr_info->tpc.dsp_atten = dsp_atten;
+               }
+
+               /* set scan tx power, 1Mbit for CCK, 6Mbit for OFDM */
+               for (scan_tbl_index = 0;
+                    scan_tbl_index < IWL_NUM_SCAN_RATES; scan_tbl_index++) {
+                       s32 actual_index = (scan_tbl_index == 0) ?
+                               IWL_RATE_1M_INDEX : IWL_RATE_6M_INDEX;
+                       iwl_hw_reg_set_scan_power(priv, scan_tbl_index,
+                               actual_index, clip_pwrs, ch_info, a_band);
+               }
+       }
+
+       return 0;
+}
+
+int iwl_hw_rxq_stop(struct iwl_priv *priv)
+{
+       int rc;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       rc = iwl_grab_restricted_access(priv);
+       if (rc) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return rc;
+       }
+
+       iwl_write_restricted(priv, FH_RCSR_CONFIG(0), 0);
+       rc = iwl_poll_restricted_bit(priv, FH_RSSR_STATUS, (1 << 24), 1000);
+       if (rc < 0)
+               IWL_ERROR("Can't stop Rx DMA.\n");
+
+       iwl_release_restricted_access(priv);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return 0;
+}
+
+int iwl_hw_tx_queue_init(struct iwl_priv *priv, struct iwl_tx_queue *txq)
+{
+       int rc;
+       unsigned long flags;
+       int txq_id = txq->q.id;
+
+       struct iwl_shared *shared_data = priv->hw_setting.shared_virt;
+
+       shared_data->tx_base_ptr[txq_id] = cpu_to_le32((u32)txq->q.dma_addr);
+
+       spin_lock_irqsave(&priv->lock, flags);
+       rc = iwl_grab_restricted_access(priv);
+       if (rc) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return rc;
+       }
+       iwl_write_restricted(priv, FH_CBCC_CTRL(txq_id), 0);
+       iwl_write_restricted(priv, FH_CBCC_BASE(txq_id), 0);
+
+       iwl_write_restricted(priv, FH_TCSR_CONFIG(txq_id),
+               ALM_FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_NOINT |
+               ALM_FH_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_TXF |
+               ALM_FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_IFTFD |
+               ALM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE_VAL |
+               ALM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE);
+       iwl_release_restricted_access(priv);
+
+       /* fake read to flush all prev. writes */
+       iwl_read32(priv, FH_TSSR_CBB_BASE);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return 0;
+}
+
+int iwl_hw_get_rx_read(struct iwl_priv *priv)
+{
+       struct iwl_shared *shared_data = priv->hw_setting.shared_virt;
+
+       return le32_to_cpu(shared_data->rx_read_ptr[0]);
+}
+
+/**
+ * iwl3945_init_hw_rate_table - Initialize the hardware rate fallback table
+ */
+int iwl3945_init_hw_rate_table(struct iwl_priv *priv)
+{
+       int rc, i;
+       struct iwl_rate_scaling_cmd rate_cmd = {
+               .reserved = {0, 0, 0},
+       };
+       struct iwl_rate_scaling_info *table = rate_cmd.table;
+
+       for (i = 0; i < ARRAY_SIZE(iwl_rates); i++) {
+               table[i].rate_n_flags =
+                       iwl_hw_set_rate_n_flags(iwl_rates[i].plcp, 0);
+               table[i].try_cnt = priv->retry_rate;
+               table[i].next_rate_index = iwl_get_prev_ieee_rate(i);
+       }
+
+       switch (priv->phymode) {
+       case MODE_IEEE80211A:
+               IWL_DEBUG_RATE("Select A mode rate scale\n");
+               /* If one of the following CCK rates is used,
+                * have it fall back to the 6M OFDM rate */
+               for (i = IWL_FIRST_CCK_RATE; i <= IWL_LAST_CCK_RATE; i++)
+                       table[i].next_rate_index = IWL_FIRST_OFDM_RATE;
+
+               /* Don't fall back to CCK rates */
+               table[IWL_RATE_12M_INDEX].next_rate_index = IWL_RATE_9M_INDEX;
+
+               /* Don't drop out of OFDM rates */
+               table[IWL_FIRST_OFDM_RATE].next_rate_index =
+                   IWL_FIRST_OFDM_RATE;
+               break;
+
+       case MODE_IEEE80211B:
+               IWL_DEBUG_RATE("Select B mode rate scale\n");
+               /* If an OFDM rate is used, have it fall back to the
+                * 1M CCK rates */
+               for (i = IWL_FIRST_OFDM_RATE; i <= IWL_LAST_OFDM_RATE; i++)
+                       table[i].next_rate_index = IWL_FIRST_CCK_RATE;
+
+               /* CCK shouldn't fall back to OFDM... */
+               table[IWL_RATE_11M_INDEX].next_rate_index = IWL_RATE_5M_INDEX;
+               break;
+
+       default:
+               IWL_DEBUG_RATE("Select G mode rate scale\n");
+               break;
+       }
+
+       /* Update the rate scaling for control frame Tx */
+       rate_cmd.table_id = 0;
+       rc = iwl_send_cmd_pdu(priv, REPLY_RATE_SCALE, sizeof(rate_cmd),
+                             &rate_cmd);
+       if (rc)
+               return rc;
+
+       /* Update the rate scaling for data frame Tx */
+       rate_cmd.table_id = 1;
+       return iwl_send_cmd_pdu(priv, REPLY_RATE_SCALE, sizeof(rate_cmd),
+                               &rate_cmd);
+}
+
+int iwl_hw_set_hw_setting(struct iwl_priv *priv)
+{
+       memset((void *)&priv->hw_setting, 0,
+              sizeof(struct iwl_driver_hw_info));
+
+       priv->hw_setting.shared_virt =
+           pci_alloc_consistent(priv->pci_dev,
+                                sizeof(struct iwl_shared),
+                                &priv->hw_setting.shared_phys);
+
+       if (!priv->hw_setting.shared_virt) {
+               IWL_ERROR("failed to allocate pci memory\n");
+               mutex_unlock(&priv->mutex);
+               return -ENOMEM;
+       }
+
+       priv->hw_setting.ac_queue_count = AC_NUM;
+       priv->hw_setting.rx_buffer_size = IWL_RX_BUF_SIZE;
+       priv->hw_setting.tx_cmd_len = sizeof(struct iwl_tx_cmd);
+       priv->hw_setting.max_rxq_size = RX_QUEUE_SIZE;
+       priv->hw_setting.max_rxq_log = RX_QUEUE_SIZE_LOG;
+       priv->hw_setting.cck_flag = 0;
+       priv->hw_setting.max_stations = IWL3945_STATION_COUNT;
+       priv->hw_setting.bcast_sta_id = IWL3945_BROADCAST_ID;
+       return 0;
+}
+
+unsigned int iwl_hw_get_beacon_cmd(struct iwl_priv *priv,
+                         struct iwl_frame *frame, u8 rate)
+{
+       struct iwl_tx_beacon_cmd *tx_beacon_cmd;
+       unsigned int frame_size;
+
+       tx_beacon_cmd = (struct iwl_tx_beacon_cmd *)&frame->u;
+       memset(tx_beacon_cmd, 0, sizeof(*tx_beacon_cmd));
+
+       tx_beacon_cmd->tx.sta_id = IWL3945_BROADCAST_ID;
+       tx_beacon_cmd->tx.stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE;
+
+       frame_size = iwl_fill_beacon_frame(priv,
+                               tx_beacon_cmd->frame,
+                               BROADCAST_ADDR,
+                               sizeof(frame->u) - sizeof(*tx_beacon_cmd));
+
+       BUG_ON(frame_size > MAX_MPDU_SIZE);
+       tx_beacon_cmd->tx.len = cpu_to_le16((u16)frame_size);
+
+       tx_beacon_cmd->tx.rate = rate;
+       tx_beacon_cmd->tx.tx_flags = (TX_CMD_FLG_SEQ_CTL_MSK |
+                                     TX_CMD_FLG_TSF_MSK);
+
+       /* supp_rates[0] == OFDM  */
+       tx_beacon_cmd->tx.supp_rates[0] = IWL_OFDM_BASIC_RATES_MASK;
+
+       /* supp_rates[1] == CCK
+        *
+        * NOTE:  IWL_*_RATES_MASK are not in the order that supp_rates
+        * expects so we have to shift them around.
+        *
+        * supp_rates expects:
+        * CCK rates are bit0..3
+        *
+        * However IWL_*_RATES_MASK has:
+        * CCK rates are bit8..11
+        */
+       tx_beacon_cmd->tx.supp_rates[1] =
+               (IWL_CCK_BASIC_RATES_MASK >> 8) & 0xF;
+
+       return (sizeof(struct iwl_tx_beacon_cmd) + frame_size);
+}
+
+void iwl_hw_rx_handler_setup(struct iwl_priv *priv)
+{
+       priv->rx_handlers[REPLY_3945_RX] = iwl3945_rx_reply_rx;
+}
+
+void iwl_hw_setup_deferred_work(struct iwl_priv *priv)
+{
+       INIT_DELAYED_WORK(&priv->thermal_periodic,
+                         iwl3945_bg_reg_txpower_periodic);
+}
+
+void iwl_hw_cancel_deferred_work(struct iwl_priv *priv)
+{
+       cancel_delayed_work(&priv->thermal_periodic);
+}
+
+struct pci_device_id iwl_hw_card_ids[] = {
+       {0x8086, 0x4222, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {0x8086, 0x4227, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {0}
+};
+
+inline int iwl_eeprom_aqcuire_semaphore(struct iwl_priv *priv)
+{
+       _iwl_clear_bit(priv, CSR_EEPROM_GP, CSR_EEPROM_GP_IF_OWNER_MSK);
+       return 0;
+}
+
+MODULE_DEVICE_TABLE(pci, iwl_hw_card_ids);
diff --git a/drivers/net/wireless/iwlwifi/iwl-3945.h b/drivers/net/wireless/iwlwifi/iwl-3945.h
new file mode 100644 (file)
index 0000000..813902e
--- /dev/null
@@ -0,0 +1,41 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2003 - 2007 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:
+ * James P. Ketrenos <ipw2100-admin@linux.intel.com>
+ * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+ *
+ *****************************************************************************/
+
+#ifndef __iwl_3945_h__
+#define __iwl_3945_h__
+
+/*
+ * Forward declare iwl-3945.c functions for iwl-base.c
+ */
+extern int iwl_eeprom_aqcuire_semaphore(struct iwl_priv *priv);
+extern __le32 iwl3945_get_antenna_flags(const struct iwl_priv *priv);
+extern int iwl3945_init_hw_rate_table(struct iwl_priv *priv);
+extern void iwl3945_reg_txpower_periodic(struct iwl_priv *priv);
+extern void iwl3945_bg_reg_txpower_periodic(struct work_struct *work);
+extern int iwl3945_txpower_set_from_eeprom(struct iwl_priv *priv);
+extern u8 iwl3945_sync_sta(struct iwl_priv *priv, int sta_id,
+                u16 tx_rate, u8 flags);
+#endif
diff --git a/drivers/net/wireless/iwlwifi/iwl-4965-hw.h b/drivers/net/wireless/iwlwifi/iwl-4965-hw.h
new file mode 100644 (file)
index 0000000..99a19ef
--- /dev/null
@@ -0,0 +1,581 @@
+/******************************************************************************
+ *
+ * This file is provided under a dual BSD/GPLv2 license.  When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2005 - 2007 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 Geeral 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.GPL.
+ *
+ * Contact Information:
+ * James P. Ketrenos <ipw2100-admin@linux.intel.com>
+ * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2005 - 2007 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name Intel Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *****************************************************************************/
+
+#ifndef __iwl_4965_hw_h__
+#define __iwl_4965_hw_h__
+
+#define IWL_RX_BUF_SIZE (4 * 1024)
+#define IWL_MAX_BSM_SIZE BSM_SRAM_SIZE
+#define KDR_RTC_INST_UPPER_BOUND               (0x018000)
+#define KDR_RTC_DATA_UPPER_BOUND               (0x80A000)
+#define KDR_RTC_INST_SIZE    (KDR_RTC_INST_UPPER_BOUND - RTC_INST_LOWER_BOUND)
+#define KDR_RTC_DATA_SIZE    (KDR_RTC_DATA_UPPER_BOUND - RTC_DATA_LOWER_BOUND)
+
+#define IWL_MAX_INST_SIZE KDR_RTC_INST_SIZE
+#define IWL_MAX_DATA_SIZE KDR_RTC_DATA_SIZE
+
+static inline int iwl_hw_valid_rtc_data_addr(u32 addr)
+{
+       return (addr >= RTC_DATA_LOWER_BOUND) &&
+              (addr < KDR_RTC_DATA_UPPER_BOUND);
+}
+
+/********************* START TXPOWER *****************************************/
+enum {
+       HT_IE_EXT_CHANNEL_NONE = 0,
+       HT_IE_EXT_CHANNEL_ABOVE,
+       HT_IE_EXT_CHANNEL_INVALID,
+       HT_IE_EXT_CHANNEL_BELOW,
+       HT_IE_EXT_CHANNEL_MAX
+};
+
+enum {
+       CALIB_CH_GROUP_1 = 0,
+       CALIB_CH_GROUP_2 = 1,
+       CALIB_CH_GROUP_3 = 2,
+       CALIB_CH_GROUP_4 = 3,
+       CALIB_CH_GROUP_5 = 4,
+       CALIB_CH_GROUP_MAX
+};
+
+/* Temperature calibration offset is 3% 0C in Kelvin */
+#define TEMPERATURE_CALIB_KELVIN_OFFSET 8
+#define TEMPERATURE_CALIB_A_VAL 259
+
+#define IWL_TX_POWER_TEMPERATURE_MIN  (263)
+#define IWL_TX_POWER_TEMPERATURE_MAX  (410)
+
+#define IWL_TX_POWER_TEMPERATURE_OUT_OF_RANGE(t) \
+       (((t) < IWL_TX_POWER_TEMPERATURE_MIN) || \
+        ((t) > IWL_TX_POWER_TEMPERATURE_MAX))
+
+#define IWL_TX_POWER_ILLEGAL_TEMPERATURE (300)
+
+#define IWL_TX_POWER_TEMPERATURE_DIFFERENCE (2)
+
+#define IWL_TX_POWER_MIMO_REGULATORY_COMPENSATION (6)
+
+#define IWL_TX_POWER_TARGET_POWER_MIN       (0)        /* 0 dBm = 1 milliwatt */
+#define IWL_TX_POWER_TARGET_POWER_MAX      (16)        /* 16 dBm */
+
+/* timeout equivalent to 3 minutes */
+#define IWL_TX_POWER_TIMELIMIT_NOCALIB 1800000000
+
+#define IWL_TX_POWER_CCK_COMPENSATION (9)
+
+#define MIN_TX_GAIN_INDEX              (0)
+#define MIN_TX_GAIN_INDEX_52GHZ_EXT    (-9)
+#define MAX_TX_GAIN_INDEX_52GHZ                (98)
+#define MIN_TX_GAIN_52GHZ              (98)
+#define MAX_TX_GAIN_INDEX_24GHZ                (98)
+#define MIN_TX_GAIN_24GHZ              (98)
+#define MAX_TX_GAIN                    (0)
+#define MAX_TX_GAIN_52GHZ_EXT          (-9)
+
+#define IWL_TX_POWER_DEFAULT_REGULATORY_24   (34)
+#define IWL_TX_POWER_DEFAULT_REGULATORY_52   (34)
+#define IWL_TX_POWER_REGULATORY_MIN          (0)
+#define IWL_TX_POWER_REGULATORY_MAX          (34)
+#define IWL_TX_POWER_DEFAULT_SATURATION_24   (38)
+#define IWL_TX_POWER_DEFAULT_SATURATION_52   (38)
+#define IWL_TX_POWER_SATURATION_MIN          (20)
+#define IWL_TX_POWER_SATURATION_MAX          (50)
+
+/* dv *0.4 = dt; so that 5 degrees temperature diff equals
+ * 12.5 in voltage diff */
+#define IWL_TX_TEMPERATURE_UPDATE_LIMIT 9
+
+#define IWL_INVALID_CHANNEL                 (0xffffffff)
+#define IWL_TX_POWER_REGITRY_BIT            (2)
+
+#define MIN_IWL_TX_POWER_CALIB_DUR          (100)
+#define IWL_CCK_FROM_OFDM_POWER_DIFF        (-5)
+#define IWL_CCK_FROM_OFDM_INDEX_DIFF (9)
+
+/* Number of entries in the gain table */
+#define POWER_GAIN_NUM_ENTRIES 78
+#define TX_POW_MAX_SESSION_NUM 5
+/*  timeout equivalent to 3 minutes */
+#define TX_IWL_TIMELIMIT_NOCALIB 1800000000
+
+/* Kedron TX_CALIB_STATES */
+#define IWL_TX_CALIB_STATE_SEND_TX        0x00000001
+#define IWL_TX_CALIB_WAIT_TX_RESPONSE     0x00000002
+#define IWL_TX_CALIB_ENABLED              0x00000004
+#define IWL_TX_CALIB_XVT_ON               0x00000008
+#define IWL_TX_CALIB_TEMPERATURE_CORRECT  0x00000010
+#define IWL_TX_CALIB_WORKING_WITH_XVT     0x00000020
+#define IWL_TX_CALIB_XVT_PERIODICAL       0x00000040
+
+#define NUM_IWL_TX_CALIB_SETTINS 5     /* Number of tx correction groups */
+
+#define IWL_MIN_POWER_IN_VP_TABLE 1    /* 0.5dBm multiplied by 2 */
+#define IWL_MAX_POWER_IN_VP_TABLE 40   /* 20dBm - multiplied by 2 (because
+                                        * entries are for each 0.5dBm) */
+#define IWL_STEP_IN_VP_TABLE 1 /* 0.5dB - multiplied by 2 */
+#define IWL_NUM_POINTS_IN_VPTABLE \
+       (1 + IWL_MAX_POWER_IN_VP_TABLE - IWL_MIN_POWER_IN_VP_TABLE)
+
+#define MIN_TX_GAIN_INDEX         (0)
+#define MAX_TX_GAIN_INDEX_52GHZ   (98)
+#define MIN_TX_GAIN_52GHZ         (98)
+#define MAX_TX_GAIN_INDEX_24GHZ   (98)
+#define MIN_TX_GAIN_24GHZ         (98)
+#define MAX_TX_GAIN               (0)
+
+/* First and last channels of all groups */
+#define CALIB_IWL_TX_ATTEN_GR1_FCH 34
+#define CALIB_IWL_TX_ATTEN_GR1_LCH 43
+#define CALIB_IWL_TX_ATTEN_GR2_FCH 44
+#define CALIB_IWL_TX_ATTEN_GR2_LCH 70
+#define CALIB_IWL_TX_ATTEN_GR3_FCH 71
+#define CALIB_IWL_TX_ATTEN_GR3_LCH 124
+#define CALIB_IWL_TX_ATTEN_GR4_FCH 125
+#define CALIB_IWL_TX_ATTEN_GR4_LCH 200
+#define CALIB_IWL_TX_ATTEN_GR5_FCH 1
+#define CALIB_IWL_TX_ATTEN_GR5_LCH 20
+
+
+union iwl_tx_power_dual_stream {
+       struct {
+               u8 radio_tx_gain[2];
+               u8 dsp_predis_atten[2];
+       } s;
+       u32 dw;
+};
+
+/********************* END TXPOWER *****************************************/
+
+/* HT flags */
+#define RXON_FLG_CTRL_CHANNEL_LOC_POS          (22)
+#define RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK       __constant_cpu_to_le32(0x1<<22)
+
+#define RXON_FLG_HT_OPERATING_MODE_POS         (23)
+
+#define RXON_FLG_HT_PROT_MSK                   __constant_cpu_to_le32(0x1<<23)
+#define RXON_FLG_FAT_PROT_MSK                  __constant_cpu_to_le32(0x2<<23)
+
+#define RXON_FLG_CHANNEL_MODE_POS              (25)
+#define RXON_FLG_CHANNEL_MODE_MSK              __constant_cpu_to_le32(0x3<<25)
+#define RXON_FLG_CHANNEL_MODE_PURE_40_MSK      __constant_cpu_to_le32(0x1<<25)
+#define RXON_FLG_CHANNEL_MODE_MIXED_MSK                __constant_cpu_to_le32(0x2<<25)
+
+#define RXON_RX_CHAIN_DRIVER_FORCE_MSK         __constant_cpu_to_le16(0x1<<0)
+#define RXON_RX_CHAIN_VALID_MSK                        __constant_cpu_to_le16(0x7<<1)
+#define RXON_RX_CHAIN_VALID_POS                        (1)
+#define RXON_RX_CHAIN_FORCE_SEL_MSK            __constant_cpu_to_le16(0x7<<4)
+#define RXON_RX_CHAIN_FORCE_SEL_POS            (4)
+#define RXON_RX_CHAIN_FORCE_MIMO_SEL_MSK       __constant_cpu_to_le16(0x7<<7)
+#define RXON_RX_CHAIN_FORCE_MIMO_SEL_POS       (7)
+#define RXON_RX_CHAIN_CNT_MSK                  __constant_cpu_to_le16(0x3<<10)
+#define RXON_RX_CHAIN_CNT_POS                  (10)
+#define RXON_RX_CHAIN_MIMO_CNT_MSK             __constant_cpu_to_le16(0x3<<12)
+#define RXON_RX_CHAIN_MIMO_CNT_POS             (12)
+#define RXON_RX_CHAIN_MIMO_FORCE_MSK           __constant_cpu_to_le16(0x1<<14)
+#define RXON_RX_CHAIN_MIMO_FORCE_POS           (14)
+
+
+#define MCS_DUP_6M_PLCP 0x20
+
+/* OFDM HT rate masks */
+/* ***************************************** */
+#define R_MCS_6M_MSK 0x1
+#define R_MCS_12M_MSK 0x2
+#define R_MCS_18M_MSK 0x4
+#define R_MCS_24M_MSK 0x8
+#define R_MCS_36M_MSK 0x10
+#define R_MCS_48M_MSK 0x20
+#define R_MCS_54M_MSK 0x40
+#define R_MCS_60M_MSK 0x80
+#define R_MCS_12M_DUAL_MSK 0x100
+#define R_MCS_24M_DUAL_MSK 0x200
+#define R_MCS_36M_DUAL_MSK 0x400
+#define R_MCS_48M_DUAL_MSK 0x800
+
+#define is_legacy(tbl) (((tbl) == LQ_G) || ((tbl) == LQ_A))
+#define is_siso(tbl) (((tbl) == LQ_SISO))
+#define is_mimo(tbl) (((tbl) == LQ_MIMO))
+#define is_Ht(tbl) (is_siso(tbl) || is_mimo(tbl))
+#define is_a_band(tbl) (((tbl) == LQ_A))
+#define is_g_and(tbl) (((tbl) == LQ_G))
+
+/* Flow Handler Definitions */
+
+/**********************/
+/*     Addresses      */
+/**********************/
+
+#define FH_MEM_LOWER_BOUND                   (0x1000)
+#define FH_MEM_UPPER_BOUND                   (0x1EF0)
+
+#define IWL_FH_REGS_LOWER_BOUND                     (0x1000)
+#define IWL_FH_REGS_UPPER_BOUND                     (0x2000)
+
+#define IWL_FH_KW_MEM_ADDR_REG              (FH_MEM_LOWER_BOUND + 0x97C)
+
+/* CBBC Area - Circular buffers base address cache pointers table */
+#define FH_MEM_CBBC_LOWER_BOUND              (FH_MEM_LOWER_BOUND + 0x9D0)
+#define FH_MEM_CBBC_UPPER_BOUND              (FH_MEM_LOWER_BOUND + 0xA10)
+/* queues 0 - 15 */
+#define FH_MEM_CBBC_QUEUE(x)  (FH_MEM_CBBC_LOWER_BOUND + (x) * 0x4)
+
+/* RSCSR Area */
+#define FH_MEM_RSCSR_LOWER_BOUND       (FH_MEM_LOWER_BOUND + 0xBC0)
+#define FH_MEM_RSCSR_UPPER_BOUND       (FH_MEM_LOWER_BOUND + 0xC00)
+#define FH_MEM_RSCSR_CHNL0             (FH_MEM_RSCSR_LOWER_BOUND)
+
+#define FH_RSCSR_CHNL0_STTS_WPTR_REG           (FH_MEM_RSCSR_CHNL0)
+#define FH_RSCSR_CHNL0_RBDCB_BASE_REG          (FH_MEM_RSCSR_CHNL0 + 0x004)
+#define FH_RSCSR_CHNL0_RBDCB_WPTR_REG          (FH_MEM_RSCSR_CHNL0 + 0x008)
+
+/* RCSR Area - Registers address map */
+#define FH_MEM_RCSR_LOWER_BOUND      (FH_MEM_LOWER_BOUND + 0xC00)
+#define FH_MEM_RCSR_UPPER_BOUND      (FH_MEM_LOWER_BOUND + 0xCC0)
+#define FH_MEM_RCSR_CHNL0            (FH_MEM_RCSR_LOWER_BOUND)
+
+#define FH_MEM_RCSR_CHNL0_CONFIG_REG   (FH_MEM_RCSR_CHNL0)
+
+/* RSSR Area - Rx shared ctrl & status registers */
+#define FH_MEM_RSSR_LOWER_BOUND                        (FH_MEM_LOWER_BOUND + 0xC40)
+#define FH_MEM_RSSR_UPPER_BOUND                (FH_MEM_LOWER_BOUND + 0xD00)
+#define FH_MEM_RSSR_SHARED_CTRL_REG            (FH_MEM_RSSR_LOWER_BOUND)
+#define FH_MEM_RSSR_RX_STATUS_REG      (FH_MEM_RSSR_LOWER_BOUND + 0x004)
+#define FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV  (FH_MEM_RSSR_LOWER_BOUND + 0x008)
+
+/* TCSR */
+#define IWL_FH_TCSR_LOWER_BOUND  (IWL_FH_REGS_LOWER_BOUND + 0xD00)
+#define IWL_FH_TCSR_UPPER_BOUND  (IWL_FH_REGS_LOWER_BOUND + 0xE60)
+
+#define IWL_FH_TCSR_CHNL_NUM                            (7)
+#define IWL_FH_TCSR_CHNL_TX_CONFIG_REG(_chnl) \
+       (IWL_FH_TCSR_LOWER_BOUND + 0x20 * _chnl)
+
+/* TSSR Area - Tx shared status registers */
+/* TSSR */
+#define IWL_FH_TSSR_LOWER_BOUND                (IWL_FH_REGS_LOWER_BOUND + 0xEA0)
+#define IWL_FH_TSSR_UPPER_BOUND                (IWL_FH_REGS_LOWER_BOUND + 0xEC0)
+
+#define IWL_FH_TSSR_TX_MSG_CONFIG_REG  (IWL_FH_TSSR_LOWER_BOUND + 0x008)
+#define IWL_FH_TSSR_TX_STATUS_REG      (IWL_FH_TSSR_LOWER_BOUND + 0x010)
+
+#define IWL_FH_TSSR_TX_MSG_CONFIG_REG_VAL_SNOOP_RD_TXPD_ON     (0xFF000000)
+#define IWL_FH_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RD_TXPD_ON     (0x00FF0000)
+
+#define IWL_FH_TSSR_TX_MSG_CONFIG_REG_VAL_MAX_FRAG_SIZE_64B    (0x00000000)
+#define IWL_FH_TSSR_TX_MSG_CONFIG_REG_VAL_MAX_FRAG_SIZE_128B   (0x00000400)
+#define IWL_FH_TSSR_TX_MSG_CONFIG_REG_VAL_MAX_FRAG_SIZE_256B   (0x00000800)
+#define IWL_FH_TSSR_TX_MSG_CONFIG_REG_VAL_MAX_FRAG_SIZE_512B   (0x00000C00)
+
+#define IWL_FH_TSSR_TX_MSG_CONFIG_REG_VAL_SNOOP_RD_TFD_ON      (0x00000100)
+#define IWL_FH_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RD_CBB_ON      (0x00000080)
+
+#define IWL_FH_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RSP_WAIT_TH    (0x00000020)
+#define IWL_FH_TSSR_TX_MSG_CONFIG_REG_VAL_RSP_WAIT_TH          (0x00000005)
+
+#define IWL_FH_TSSR_TX_STATUS_REG_BIT_BUFS_EMPTY(_chnl)        \
+       ((1 << (_chnl)) << 24)
+#define IWL_FH_TSSR_TX_STATUS_REG_BIT_NO_PEND_REQ(_chnl) \
+       ((1 << (_chnl)) << 16)
+
+#define IWL_FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(_chnl) \
+       (IWL_FH_TSSR_TX_STATUS_REG_BIT_BUFS_EMPTY(_chnl) | \
+       IWL_FH_TSSR_TX_STATUS_REG_BIT_NO_PEND_REQ(_chnl))
+
+/* TCSR: tx_config register values */
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_TXF              (0x00000000)
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_DRIVER           (0x00000001)
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_ARC              (0x00000002)
+
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE_VAL    (0x00000000)
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE_VAL     (0x00000008)
+
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_NOINT           (0x00000000)
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD          (0x00100000)
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_IFTFD           (0x00200000)
+
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_NOINT            (0x00000000)
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_ENDTFD           (0x00400000)
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_IFTFD            (0x00800000)
+
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE            (0x00000000)
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE_EOF        (0x40000000)
+#define IWL_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE           (0x80000000)
+
+#define IWL_FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_EMPTY          (0x00000000)
+#define IWL_FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_WAIT           (0x00002000)
+#define IWL_FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID          (0x00000003)
+
+#define IWL_FH_TCSR_CHNL_TX_BUF_STS_REG_BIT_TFDB_WPTR           (0x00000001)
+
+#define IWL_FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_NUM              (20)
+#define IWL_FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_IDX              (12)
+
+/* RCSR:  channel 0 rx_config register defines */
+#define FH_RCSR_CHNL0_RX_CONFIG_DMA_CHNL_EN_MASK  (0xC0000000) /* bits 30-31 */
+#define FH_RCSR_CHNL0_RX_CONFIG_RBDBC_SIZE_MASK   (0x00F00000) /* bits 20-23 */
+#define FH_RCSR_CHNL0_RX_CONFIG_RB_SIZE_MASK     (0x00030000) /* bits 16-17 */
+#define FH_RCSR_CHNL0_RX_CONFIG_SINGLE_FRAME_MASK (0x00008000) /* bit 15 */
+#define FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_MASK     (0x00001000) /* bit 12 */
+#define FH_RCSR_CHNL0_RX_CONFIG_RB_TIMEOUT_MASK   (0x00000FF0) /* bit 4-11 */
+
+#define FH_RCSR_RX_CONFIG_RBDCB_SIZE_BITSHIFT       (20)
+#define FH_RCSR_RX_CONFIG_RB_SIZE_BITSHIFT                     (16)
+
+/* RCSR: rx_config register values */
+#define FH_RCSR_RX_CONFIG_CHNL_EN_PAUSE_VAL         (0x00000000)
+#define FH_RCSR_RX_CONFIG_CHNL_EN_PAUSE_EOF_VAL     (0x40000000)
+#define FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL        (0x80000000)
+
+#define IWL_FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K    (0x00000000)
+
+/* RCSR channel 0 config register values */
+#define FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_NO_INT_VAL       (0x00000000)
+#define FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL     (0x00001000)
+
+/* RSCSR: defs used in normal mode */
+#define FH_RSCSR_CHNL0_RBDCB_WPTR_MASK         (0x00000FFF)    /* bits 0-11 */
+
+#define SCD_WIN_SIZE                           64
+#define SCD_FRAME_LIMIT                                64
+
+/* memory mapped registers */
+#define SCD_START_OFFSET               0xa02c00
+
+#define SCD_SRAM_BASE_ADDR           (SCD_START_OFFSET + 0x0)
+#define SCD_EMPTY_BITS               (SCD_START_OFFSET + 0x4)
+#define SCD_DRAM_BASE_ADDR           (SCD_START_OFFSET + 0x10)
+#define SCD_AIT                      (SCD_START_OFFSET + 0x18)
+#define SCD_TXFACT                   (SCD_START_OFFSET + 0x1c)
+#define SCD_QUEUE_WRPTR(x)           (SCD_START_OFFSET + 0x24 + (x) * 4)
+#define SCD_QUEUE_RDPTR(x)           (SCD_START_OFFSET + 0x64 + (x) * 4)
+#define SCD_SETQUEUENUM              (SCD_START_OFFSET + 0xa4)
+#define SCD_SET_TXSTAT_TXED          (SCD_START_OFFSET + 0xa8)
+#define SCD_SET_TXSTAT_DONE          (SCD_START_OFFSET + 0xac)
+#define SCD_SET_TXSTAT_NOT_SCHD      (SCD_START_OFFSET + 0xb0)
+#define SCD_DECREASE_CREDIT          (SCD_START_OFFSET + 0xb4)
+#define SCD_DECREASE_SCREDIT         (SCD_START_OFFSET + 0xb8)
+#define SCD_LOAD_CREDIT              (SCD_START_OFFSET + 0xbc)
+#define SCD_LOAD_SCREDIT             (SCD_START_OFFSET + 0xc0)
+#define SCD_BAR                      (SCD_START_OFFSET + 0xc4)
+#define SCD_BAR_DW0                  (SCD_START_OFFSET + 0xc8)
+#define SCD_BAR_DW1                  (SCD_START_OFFSET + 0xcc)
+#define SCD_QUEUECHAIN_SEL           (SCD_START_OFFSET + 0xd0)
+#define SCD_QUERY_REQ                (SCD_START_OFFSET + 0xd8)
+#define SCD_QUERY_RES                (SCD_START_OFFSET + 0xdc)
+#define SCD_PENDING_FRAMES           (SCD_START_OFFSET + 0xe0)
+#define SCD_INTERRUPT_MASK           (SCD_START_OFFSET + 0xe4)
+#define SCD_INTERRUPT_THRESHOLD      (SCD_START_OFFSET + 0xe8)
+#define SCD_QUERY_MIN_FRAME_SIZE     (SCD_START_OFFSET + 0x100)
+#define SCD_QUEUE_STATUS_BITS(x)     (SCD_START_OFFSET + 0x104 + (x) * 4)
+
+/* SRAM structures */
+#define SCD_CONTEXT_DATA_OFFSET                        0x380
+#define SCD_TX_STTS_BITMAP_OFFSET              0x400
+#define SCD_TRANSLATE_TBL_OFFSET               0x500
+#define SCD_CONTEXT_QUEUE_OFFSET(x)    (SCD_CONTEXT_DATA_OFFSET + ((x) * 8))
+#define SCD_TRANSLATE_TBL_OFFSET_QUEUE(x) \
+       ((SCD_TRANSLATE_TBL_OFFSET + ((x) * 2)) & 0xfffffffc)
+
+#define SCD_TXFACT_REG_TXFIFO_MASK(lo, hi) \
+       ((1<<(hi))|((1<<(hi))-(1<<(lo))))
+
+
+#define SCD_MODE_REG_BIT_SEARCH_MODE           (1<<0)
+#define SCD_MODE_REG_BIT_SBYP_MODE             (1<<1)
+
+#define SCD_TXFIFO_POS_TID                     (0)
+#define SCD_TXFIFO_POS_RA                      (4)
+#define SCD_QUEUE_STTS_REG_POS_ACTIVE          (0)
+#define SCD_QUEUE_STTS_REG_POS_TXF             (1)
+#define SCD_QUEUE_STTS_REG_POS_WSL             (5)
+#define SCD_QUEUE_STTS_REG_POS_SCD_ACK         (8)
+#define SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN      (10)
+#define SCD_QUEUE_STTS_REG_MSK                 (0x0007FC00)
+
+#define SCD_QUEUE_RA_TID_MAP_RATID_MSK         (0x01FF)
+
+#define SCD_QUEUE_CTX_REG1_WIN_SIZE_POS                (0)
+#define SCD_QUEUE_CTX_REG1_WIN_SIZE_MSK                (0x0000007F)
+#define SCD_QUEUE_CTX_REG1_CREDIT_POS          (8)
+#define SCD_QUEUE_CTX_REG1_CREDIT_MSK          (0x00FFFF00)
+#define SCD_QUEUE_CTX_REG1_SUPER_CREDIT_POS    (24)
+#define SCD_QUEUE_CTX_REG1_SUPER_CREDIT_MSK    (0xFF000000)
+#define SCD_QUEUE_CTX_REG2_FRAME_LIMIT_POS     (16)
+#define SCD_QUEUE_CTX_REG2_FRAME_LIMIT_MSK     (0x007F0000)
+
+#define CSR_HW_IF_CONFIG_REG_BIT_KEDRON_R      (0x00000010)
+#define CSR_HW_IF_CONFIG_REG_MSK_BOARD_VER     (0x00000C00)
+#define CSR_HW_IF_CONFIG_REG_BIT_MAC_SI                (0x00000100)
+#define CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI      (0x00000200)
+
+static inline u8 iwl_hw_get_rate(__le32 rate_n_flags)
+{
+       return le32_to_cpu(rate_n_flags) & 0xFF;
+}
+static inline u16 iwl_hw_get_rate_n_flags(__le32 rate_n_flags)
+{
+       return le32_to_cpu(rate_n_flags) & 0xFFFF;
+}
+static inline __le32 iwl_hw_set_rate_n_flags(u8 rate, u16 flags)
+{
+       return cpu_to_le32(flags|(u16)rate);
+}
+
+struct iwl_tfd_frame_data {
+       __le32 tb1_addr;
+
+       __le32 val1;
+       /* __le32 ptb1_32_35:4; */
+#define IWL_tb1_addr_hi_POS 0
+#define IWL_tb1_addr_hi_LEN 4
+#define IWL_tb1_addr_hi_SYM val1
+       /* __le32 tb_len1:12; */
+#define IWL_tb1_len_POS 4
+#define IWL_tb1_len_LEN 12
+#define IWL_tb1_len_SYM val1
+       /* __le32 ptb2_0_15:16; */
+#define IWL_tb2_addr_lo16_POS 16
+#define IWL_tb2_addr_lo16_LEN 16
+#define IWL_tb2_addr_lo16_SYM val1
+
+       __le32 val2;
+       /* __le32 ptb2_16_35:20; */
+#define IWL_tb2_addr_hi20_POS 0
+#define IWL_tb2_addr_hi20_LEN 20
+#define IWL_tb2_addr_hi20_SYM val2
+       /* __le32 tb_len2:12; */
+#define IWL_tb2_len_POS 20
+#define IWL_tb2_len_LEN 12
+#define IWL_tb2_len_SYM val2
+} __attribute__ ((packed));
+
+struct iwl_tfd_frame {
+       __le32 val0;
+       /* __le32 rsvd1:24; */
+       /* __le32 num_tbs:5; */
+#define IWL_num_tbs_POS 24
+#define IWL_num_tbs_LEN 5
+#define IWL_num_tbs_SYM val0
+       /* __le32 rsvd2:1; */
+       /* __le32 padding:2; */
+       struct iwl_tfd_frame_data pa[10];
+       __le32 reserved;
+} __attribute__ ((packed));
+
+#define IWL4965_MAX_WIN_SIZE              64
+#define IWL4965_QUEUE_SIZE               256
+#define IWL4965_NUM_FIFOS                  7
+#define IWL_MAX_NUM_QUEUES                16
+
+struct iwl4965_queue_byte_cnt_entry {
+       __le16 val;
+       /* __le16 byte_cnt:12; */
+#define IWL_byte_cnt_POS 0
+#define IWL_byte_cnt_LEN 12
+#define IWL_byte_cnt_SYM val
+       /* __le16 rsvd:4; */
+} __attribute__ ((packed));
+
+struct iwl4965_sched_queue_byte_cnt_tbl {
+       struct iwl4965_queue_byte_cnt_entry tfd_offset[IWL4965_QUEUE_SIZE +
+                                                      IWL4965_MAX_WIN_SIZE];
+       u8 dont_care[1024 -
+                    (IWL4965_QUEUE_SIZE + IWL4965_MAX_WIN_SIZE) *
+                    sizeof(__le16)];
+} __attribute__ ((packed));
+
+/* Base physical address of iwl_shared is provided to SCD_DRAM_BASE_ADDR
+ * and &iwl_shared.val0 is provided to FH_RSCSR_CHNL0_STTS_WPTR_REG */
+struct iwl_shared {
+       struct iwl4965_sched_queue_byte_cnt_tbl
+        queues_byte_cnt_tbls[IWL_MAX_NUM_QUEUES];
+       __le32 val0;
+
+       /* __le32 rb_closed_stts_rb_num:12; */
+#define IWL_rb_closed_stts_rb_num_POS 0
+#define IWL_rb_closed_stts_rb_num_LEN 12
+#define IWL_rb_closed_stts_rb_num_SYM val0
+       /* __le32 rsrv1:4; */
+       /* __le32 rb_closed_stts_rx_frame_num:12; */
+#define IWL_rb_closed_stts_rx_frame_num_POS 16
+#define IWL_rb_closed_stts_rx_frame_num_LEN 12
+#define IWL_rb_closed_stts_rx_frame_num_SYM val0
+       /* __le32 rsrv2:4; */
+
+       __le32 val1;
+       /* __le32 frame_finished_stts_rb_num:12; */
+#define IWL_frame_finished_stts_rb_num_POS 0
+#define IWL_frame_finished_stts_rb_num_LEN 12
+#define IWL_frame_finished_stts_rb_num_SYM val1
+       /* __le32 rsrv3:4; */
+       /* __le32 frame_finished_stts_rx_frame_num:12; */
+#define IWL_frame_finished_stts_rx_frame_num_POS 16
+#define IWL_frame_finished_stts_rx_frame_num_LEN 12
+#define IWL_frame_finished_stts_rx_frame_num_SYM val1
+       /* __le32 rsrv4:4; */
+
+       __le32 padding1;  /* so that allocation will be aligned to 16B */
+       __le32 padding2;
+} __attribute__ ((packed));
+
+#endif /* __iwl_4965_hw_h__ */
diff --git a/drivers/net/wireless/iwlwifi/iwl-4965-rs.c b/drivers/net/wireless/iwlwifi/iwl-4965-rs.c
new file mode 100644 (file)
index 0000000..f363860
--- /dev/null
@@ -0,0 +1,2118 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2005 - 2007 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:
+ * James P. Ketrenos <ipw2100-admin@linux.intel.com>
+ * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+ *
+ *****************************************************************************/
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/wireless.h>
+#include <net/mac80211.h>
+#include <net/ieee80211.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/delay.h>
+
+#include <linux/workqueue.h>
+
+#include <net/mac80211.h>
+#include <linux/wireless.h>
+
+#include "../net/mac80211/ieee80211_rate.h"
+
+#include "iwlwifi.h"
+#include "iwl-helpers.h"
+
+#define RS_NAME "iwl-4965-rs"
+
+#define NUM_TRY_BEFORE_ANTENNA_TOGGLE 1
+#define IWL_NUMBER_TRY      1
+#define IWL_HT_NUMBER_TRY   3
+
+#define IWL_RATE_MAX_WINDOW            62
+#define IWL_RATE_HIGH_TH               10880
+#define IWL_RATE_MIN_FAILURE_TH                6
+#define IWL_RATE_MIN_SUCCESS_TH                8
+#define IWL_RATE_DECREASE_TH           1920
+#define IWL_RATE_INCREASE_TH            8960
+#define IWL_RATE_SCALE_FLUSH_INTVL   (2*HZ)        /*2 seconds */
+
+static u8 rs_ht_to_legacy[] = {
+       IWL_RATE_6M_INDEX, IWL_RATE_6M_INDEX,
+       IWL_RATE_6M_INDEX, IWL_RATE_6M_INDEX,
+       IWL_RATE_6M_INDEX,
+       IWL_RATE_6M_INDEX, IWL_RATE_9M_INDEX,
+       IWL_RATE_12M_INDEX, IWL_RATE_18M_INDEX,
+       IWL_RATE_24M_INDEX, IWL_RATE_36M_INDEX,
+       IWL_RATE_48M_INDEX, IWL_RATE_54M_INDEX
+};
+
+struct iwl_rate {
+       u32 rate_n_flags;
+} __attribute__ ((packed));
+
+struct iwl_rate_scale_data {
+       u64 data;
+       s32 success_counter;
+       s32 success_ratio;
+       s32 counter;
+       s32 average_tpt;
+       unsigned long stamp;
+};
+
+struct iwl_scale_tbl_info {
+       enum iwl_table_type lq_type;
+       enum iwl_antenna_type antenna_type;
+       u8 is_SGI;
+       u8 is_fat;
+       u8 is_dup;
+       u8 action;
+       s32 *expected_tpt;
+       struct iwl_rate current_rate;
+       struct iwl_rate_scale_data win[IWL_RATE_COUNT];
+};
+
+struct iwl_rate_scale_priv {
+       u8 active_tbl;
+       u8 enable_counter;
+       u8 stay_in_tbl;
+       u8 search_better_tbl;
+       s32 last_tpt;
+       u32 table_count_limit;
+       u32 max_failure_limit;
+       u32 max_success_limit;
+       u32 table_count;
+       u32 total_failed;
+       u32 total_success;
+       u32 flush_timer;
+       u8 action_counter;
+       u8 antenna;
+       u8 valid_antenna;
+       u8 is_green;
+       u8 is_dup;
+       u8 phymode;
+       u8 ibss_sta_added;
+       u16 active_rate;
+       u16 active_siso_rate;
+       u16 active_mimo_rate;
+       u16 active_rate_basic;
+       struct iwl_link_quality_cmd lq;
+       struct iwl_scale_tbl_info lq_info[LQ_SIZE];
+};
+
+static void rs_rate_scale_perform(struct iwl_priv *priv,
+                                  struct net_device *dev,
+                                  struct ieee80211_hdr *hdr,
+                                  struct sta_info *sta);
+static int rs_fill_link_cmd(struct iwl_rate_scale_priv *lq_data,
+                            struct iwl_rate *tx_mcs,
+                            struct iwl_link_quality_cmd *tbl,
+                            struct sta_info *sta);
+
+
+static s32 expected_tpt_A[IWL_RATE_COUNT] = {
+       0, 0, 0, 0, 40, 57, 72, 98, 121, 154, 177, 186, 186
+};
+
+static s32 expected_tpt_G[IWL_RATE_COUNT] = {
+       7, 13, 35, 58, 40, 57, 72, 98, 121, 154, 177, 186, 186
+};
+
+static s32 expected_tpt_siso20MHz[IWL_RATE_COUNT] = {
+       0, 0, 0, 0, 42, 42, 76, 102, 124, 159, 183, 193, 202
+};
+
+static s32 expected_tpt_siso20MHzSGI[IWL_RATE_COUNT] = {
+       0, 0, 0, 0, 46, 46, 82, 110, 132, 168, 192, 202, 211
+};
+
+static s32 expected_tpt_mimo20MHz[IWL_RATE_COUNT] = {
+       0, 0, 0, 0, 74, 74, 123, 155, 179, 214, 236, 244, 251
+};
+
+static s32 expected_tpt_mimo20MHzSGI[IWL_RATE_COUNT] = {
+       0, 0, 0, 0, 81, 81, 131, 164, 188, 222, 243, 251, 257
+};
+
+static s32 expected_tpt_siso40MHz[IWL_RATE_COUNT] = {
+       0, 0, 0, 0, 77, 77, 127, 160, 184, 220, 242, 250, 257
+};
+
+static s32 expected_tpt_siso40MHzSGI[IWL_RATE_COUNT] = {
+       0, 0, 0, 0, 83, 83, 135, 169, 193, 229, 250, 257, 264
+};
+
+static s32 expected_tpt_mimo40MHz[IWL_RATE_COUNT] = {
+       0, 0, 0, 0, 123, 123, 182, 214, 235, 264, 279, 285, 289
+};
+
+static s32 expected_tpt_mimo40MHzSGI[IWL_RATE_COUNT] = {
+       0, 0, 0, 0, 131, 131, 191, 222, 242, 270, 284, 289, 293
+};
+
+static int iwl_lq_sync_callback(struct iwl_priv *priv,
+                               struct iwl_cmd *cmd, struct sk_buff *skb)
+{
+       /*We didn't cache the SKB; let the caller free it */
+       return 1;
+}
+
+static inline u8 iwl_rate_get_rate(u32 rate_n_flags)
+{
+       return (u8)(rate_n_flags & 0xFF);
+}
+
+static int rs_send_lq_cmd(struct iwl_priv *priv,
+                         struct iwl_link_quality_cmd *lq, u8 flags)
+{
+#ifdef CONFIG_IWLWIFI_DEBUG
+       int i;
+#endif
+       int rc = -1;
+
+       struct iwl_host_cmd cmd = {
+               .id = REPLY_TX_LINK_QUALITY_CMD,
+               .len = sizeof(struct iwl_link_quality_cmd),
+               .meta.flags = flags,
+               .data = lq,
+       };
+
+       if ((lq->sta_id == 0xFF) &&
+           (priv->iw_mode == IEEE80211_IF_TYPE_IBSS))
+               return rc;
+
+       if (lq->sta_id == 0xFF)
+               lq->sta_id = IWL_AP_ID;
+
+       IWL_DEBUG_RATE("lq station id 0x%x\n", lq->sta_id);
+       IWL_DEBUG_RATE("lq dta 0x%X 0x%X\n",
+                      lq->general_params.single_stream_ant_msk,
+                      lq->general_params.dual_stream_ant_msk);
+#ifdef CONFIG_IWLWIFI_DEBUG
+       for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++)
+               IWL_DEBUG_RATE("lq index %d 0x%X\n",
+                               i, lq->rs_table[i].rate_n_flags);
+#endif
+
+       if (flags & CMD_ASYNC)
+               cmd.meta.u.callback = iwl_lq_sync_callback;
+
+       if (iwl_is_associated(priv) && priv->assoc_station_added &&
+           priv->lq_mngr.lq_ready)
+               rc = iwl_send_cmd(priv, &cmd);
+
+       return rc;
+}
+
+static int rs_rate_scale_clear_window(struct iwl_rate_scale_data *window)
+{
+       window->data = 0;
+       window->success_counter = 0;
+       window->success_ratio = IWL_INVALID_VALUE;
+       window->counter = 0;
+       window->average_tpt = IWL_INVALID_VALUE;
+       window->stamp = 0;
+
+       return 0;
+}
+
+static int rs_collect_tx_data(struct iwl_rate_scale_data *windows,
+                             int scale_index, s32 tpt, u32 status)
+{
+       int rc = 0;
+       struct iwl_rate_scale_data *window = NULL;
+       u64 mask;
+       u8 win_size = IWL_RATE_MAX_WINDOW;
+       s32 fail_count;
+
+       if (scale_index < 0)
+               return -1;
+
+       if (scale_index >= IWL_RATE_COUNT)
+               return -1;
+
+       window = &(windows[scale_index]);
+
+       if (window->counter >= win_size) {
+
+               window->counter = win_size - 1;
+               mask = 1;
+               mask = (mask << (win_size - 1));
+               if ((window->data & mask)) {
+                       window->data &= ~mask;
+                       window->success_counter = window->success_counter - 1;
+               }
+       }
+
+       window->counter = window->counter + 1;
+       mask = window->data;
+       window->data = (mask << 1);
+       if (status != 0) {
+               window->success_counter = window->success_counter + 1;
+               window->data |= 0x1;
+       }
+
+       if (window->counter > 0)
+               window->success_ratio = 128 * (100 * window->success_counter)
+                                       / window->counter;
+       else
+               window->success_ratio = IWL_INVALID_VALUE;
+
+       fail_count = window->counter - window->success_counter;
+
+       if ((fail_count >= IWL_RATE_MIN_FAILURE_TH) ||
+           (window->success_counter >= IWL_RATE_MIN_SUCCESS_TH))
+               window->average_tpt = (window->success_ratio * tpt + 64) / 128;
+       else
+               window->average_tpt = IWL_INVALID_VALUE;
+
+       window->stamp = jiffies;
+
+       return rc;
+}
+
+int static rs_mcs_from_tbl(struct iwl_rate *mcs_rate,
+                          struct iwl_scale_tbl_info *tbl,
+                          int index, u8 use_green)
+{
+       int rc = 0;
+
+       if (is_legacy(tbl->lq_type)) {
+               mcs_rate->rate_n_flags = iwl_rates[index].plcp;
+               if (index >= IWL_FIRST_CCK_RATE && index <= IWL_LAST_CCK_RATE)
+                       mcs_rate->rate_n_flags |= RATE_MCS_CCK_MSK;
+
+       } else if (is_siso(tbl->lq_type)) {
+               if (index > IWL_LAST_OFDM_RATE)
+                       index = IWL_LAST_OFDM_RATE;
+                mcs_rate->rate_n_flags = iwl_rates[index].plcp_siso |
+                                         RATE_MCS_HT_MSK;
+       } else {
+               if (index > IWL_LAST_OFDM_RATE)
+                       index = IWL_LAST_OFDM_RATE;
+               mcs_rate->rate_n_flags = iwl_rates[index].plcp_mimo |
+                                        RATE_MCS_HT_MSK;
+       }
+
+       switch (tbl->antenna_type) {
+       case ANT_BOTH:
+               mcs_rate->rate_n_flags |= RATE_MCS_ANT_AB_MSK;
+               break;
+       case ANT_MAIN:
+               mcs_rate->rate_n_flags |= RATE_MCS_ANT_A_MSK;
+               break;
+       case ANT_AUX:
+               mcs_rate->rate_n_flags |= RATE_MCS_ANT_B_MSK;
+               break;
+       case ANT_NONE:
+               break;
+       }
+
+       if (is_legacy(tbl->lq_type))
+               return rc;
+
+       if (tbl->is_fat) {
+               if (tbl->is_dup)
+                       mcs_rate->rate_n_flags |= RATE_MCS_DUP_MSK;
+               else
+                       mcs_rate->rate_n_flags |= RATE_MCS_FAT_MSK;
+       }
+       if (tbl->is_SGI)
+               mcs_rate->rate_n_flags |= RATE_MCS_SGI_MSK;
+
+       if (use_green) {
+               mcs_rate->rate_n_flags |= RATE_MCS_GF_MSK;
+               if (is_siso(tbl->lq_type))
+                       mcs_rate->rate_n_flags &= ~RATE_MCS_SGI_MSK;
+       }
+       return rc;
+}
+
+static int rs_get_tbl_info_from_mcs(const struct iwl_rate *mcs_rate,
+                                   int phymode, struct iwl_scale_tbl_info *tbl,
+                                   int *rate_idx)
+{
+       int index;
+       u32 ant_msk;
+
+       index = iwl_rate_index_from_plcp(mcs_rate->rate_n_flags);
+
+       if (index  == IWL_RATE_INVALID) {
+               *rate_idx = -1;
+               return -1;
+       }
+       tbl->is_SGI = 0;
+       tbl->is_fat = 0;
+       tbl->is_dup = 0;
+       tbl->antenna_type = ANT_BOTH;
+
+       if (!(mcs_rate->rate_n_flags & RATE_MCS_HT_MSK)) {
+               ant_msk = (mcs_rate->rate_n_flags & RATE_MCS_ANT_AB_MSK);
+
+               if (ant_msk == RATE_MCS_ANT_AB_MSK)
+                       tbl->lq_type = LQ_NONE;
+               else {
+
+                       if (phymode == MODE_IEEE80211A)
+                               tbl->lq_type = LQ_A;
+                       else
+                               tbl->lq_type = LQ_G;
+
+                       if (mcs_rate->rate_n_flags & RATE_MCS_ANT_A_MSK)
+                               tbl->antenna_type = ANT_MAIN;
+                       else
+                               tbl->antenna_type = ANT_AUX;
+               }
+               *rate_idx = index;
+
+       } else if (iwl_rate_get_rate(mcs_rate->rate_n_flags)
+                                       <= IWL_RATE_SISO_60M_PLCP) {
+               tbl->lq_type = LQ_SISO;
+
+               ant_msk = (mcs_rate->rate_n_flags & RATE_MCS_ANT_AB_MSK);
+               if (ant_msk == RATE_MCS_ANT_AB_MSK)
+                       tbl->lq_type = LQ_NONE;
+               else {
+                       if (mcs_rate->rate_n_flags & RATE_MCS_ANT_A_MSK)
+                               tbl->antenna_type = ANT_MAIN;
+                       else
+                               tbl->antenna_type = ANT_AUX;
+               }
+               if (mcs_rate->rate_n_flags & RATE_MCS_SGI_MSK)
+                       tbl->is_SGI = 1;
+
+               if ((mcs_rate->rate_n_flags & RATE_MCS_FAT_MSK) ||
+                   (mcs_rate->rate_n_flags & RATE_MCS_DUP_MSK))
+                       tbl->is_fat = 1;
+
+               if (mcs_rate->rate_n_flags & RATE_MCS_DUP_MSK)
+                       tbl->is_dup = 1;
+
+               *rate_idx = index;
+       } else {
+               tbl->lq_type = LQ_MIMO;
+               if (mcs_rate->rate_n_flags & RATE_MCS_SGI_MSK)
+                       tbl->is_SGI = 1;
+
+               if ((mcs_rate->rate_n_flags & RATE_MCS_FAT_MSK) ||
+                   (mcs_rate->rate_n_flags & RATE_MCS_DUP_MSK))
+                       tbl->is_fat = 1;
+
+               if (mcs_rate->rate_n_flags & RATE_MCS_DUP_MSK)
+                       tbl->is_dup = 1;
+               *rate_idx = index;
+       }
+       return 0;
+}
+
+static inline void rs_toggle_antenna(struct iwl_rate *new_rate,
+                                    struct iwl_scale_tbl_info *tbl)
+{
+       if (tbl->antenna_type == ANT_AUX) {
+               tbl->antenna_type = ANT_MAIN;
+               new_rate->rate_n_flags &= ~RATE_MCS_ANT_B_MSK;
+               new_rate->rate_n_flags |= RATE_MCS_ANT_A_MSK;
+       } else {
+               tbl->antenna_type = ANT_AUX;
+               new_rate->rate_n_flags &= ~RATE_MCS_ANT_A_MSK;
+               new_rate->rate_n_flags |= RATE_MCS_ANT_B_MSK;
+       }
+}
+
+static inline s8 rs_use_green(struct iwl_priv *priv)
+{
+       s8 rc = 0;
+#ifdef CONFIG_IWLWIFI_HT
+       if (!priv->is_ht_enabled || !priv->current_assoc_ht.is_ht)
+               return 0;
+
+       if ((priv->current_assoc_ht.is_green_field) &&
+           !(priv->current_assoc_ht.operating_mode & 0x4))
+               rc = 1;
+#endif /*CONFIG_IWLWIFI_HT */
+       return rc;
+}
+
+/**
+ * rs_get_supported_rates - get the available rates
+ *
+ * if management frame or broadcast frame only return
+ * basic available rates.
+ *
+ */
+static void rs_get_supported_rates(struct iwl_rate_scale_priv *lq_data,
+                                  struct ieee80211_hdr *hdr,
+                                  enum iwl_table_type rate_type,
+                                  u16 *data_rate)
+{
+       if (is_legacy(rate_type))
+               *data_rate = lq_data->active_rate;
+       else {
+               if (is_siso(rate_type))
+                       *data_rate = lq_data->active_siso_rate;
+               else
+                       *data_rate = lq_data->active_mimo_rate;
+       }
+
+       if (hdr && is_multicast_ether_addr(hdr->addr1) &&
+           lq_data->active_rate_basic)
+               *data_rate = lq_data->active_rate_basic;
+}
+
+static u16 rs_get_adjacent_rate(u8 index, u16 rate_mask, int rate_type)
+{
+       u8 high = IWL_RATE_INVALID;
+       u8 low = IWL_RATE_INVALID;
+
+       /* 802.11A or ht walks to the next literal adjascent rate in
+        * the rate table */
+       if (is_a_band(rate_type) || !is_legacy(rate_type)) {
+               int i;
+               u32 mask;
+
+               /* Find the previous rate that is in the rate mask */
+               i = index - 1;
+               for (mask = (1 << i); i >= 0; i--, mask >>= 1) {
+                       if (rate_mask & mask) {
+                               low = i;
+                               break;
+                       }
+               }
+
+               /* Find the next rate that is in the rate mask */
+               i = index + 1;
+               for (mask = (1 << i); i < IWL_RATE_COUNT; i++, mask <<= 1) {
+                       if (rate_mask & mask) {
+                               high = i;
+                               break;
+                       }
+               }
+
+               return (high << 8) | low;
+       }
+
+       low = index;
+       while (low != IWL_RATE_INVALID) {
+               low = iwl_rates[low].prev_rs;
+               if (low == IWL_RATE_INVALID)
+                       break;
+               if (rate_mask & (1 << low))
+                       break;
+               IWL_DEBUG_RATE("Skipping masked lower rate: %d\n", low);
+       }
+
+       high = index;
+       while (high != IWL_RATE_INVALID) {
+               high = iwl_rates[high].next_rs;
+               if (high == IWL_RATE_INVALID)
+                       break;
+               if (rate_mask & (1 << high))
+                       break;
+               IWL_DEBUG_RATE("Skipping masked higher rate: %d\n", high);
+       }
+
+       return (high << 8) | low;
+}
+
+static int rs_get_lower_rate(struct iwl_rate_scale_priv *lq_data,
+                            struct iwl_scale_tbl_info *tbl, u8 scale_index,
+                            u8 ht_possible, struct iwl_rate *mcs_rate,
+                            struct sta_info *sta)
+{
+       u8 is_green = lq_data->is_green;
+       s32 low;
+       u16 rate_mask;
+       u16 high_low;
+       u8 switch_to_legacy = 0;
+
+       /* check if we need to switch from HT to legacy rates.
+        * assumption is that mandatory rates (1Mbps or 6Mbps)
+        * are always supported (spec demand) */
+       if (!is_legacy(tbl->lq_type) && (!ht_possible || !scale_index)) {
+               switch_to_legacy = 1;
+               scale_index = rs_ht_to_legacy[scale_index];
+               if (lq_data->phymode == MODE_IEEE80211A)
+                       tbl->lq_type = LQ_A;
+               else
+                       tbl->lq_type = LQ_G;
+
+               if ((tbl->antenna_type == ANT_BOTH) ||
+                   (tbl->antenna_type == ANT_NONE))
+                       tbl->antenna_type = ANT_MAIN;
+
+               tbl->is_fat = 0;
+               tbl->is_SGI = 0;
+       }
+
+       rs_get_supported_rates(lq_data, NULL, tbl->lq_type, &rate_mask);
+
+       /* mask with station rate restriction */
+       if (is_legacy(tbl->lq_type)) {
+               if (lq_data->phymode == (u8) MODE_IEEE80211A)
+                       rate_mask  = (u16)(rate_mask &
+                          (sta->supp_rates << IWL_FIRST_OFDM_RATE));
+               else
+                       rate_mask = (u16)(rate_mask & sta->supp_rates);
+       }
+
+       /* if we did switched from HT to legacy check current rate */
+       if ((switch_to_legacy) &&
+           (rate_mask & (1 << scale_index))) {
+               rs_mcs_from_tbl(mcs_rate, tbl, scale_index, is_green);
+               return 0;
+       }
+
+       high_low = rs_get_adjacent_rate(scale_index, rate_mask, tbl->lq_type);
+       low = high_low & 0xff;
+
+       if (low != IWL_RATE_INVALID)
+               rs_mcs_from_tbl(mcs_rate, tbl, low, is_green);
+       else
+               rs_mcs_from_tbl(mcs_rate, tbl, scale_index, is_green);
+
+       return 0;
+}
+
+static void rs_tx_status(void *priv_rate,
+                        struct net_device *dev,
+                        struct sk_buff *skb,
+                        struct ieee80211_tx_status *tx_resp)
+{
+       int status;
+       u8 retries;
+       int rs_index, index = 0;
+       struct iwl_rate_scale_priv *lq;
+       struct iwl_link_quality_cmd *table;
+       struct sta_info *sta;
+       struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+       struct iwl_priv *priv = (struct iwl_priv *)priv_rate;
+       struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
+       struct iwl_rate_scale_data *window = NULL;
+       struct iwl_rate_scale_data *search_win = NULL;
+       struct iwl_rate tx_mcs;
+       struct iwl_scale_tbl_info tbl_type;
+       struct iwl_scale_tbl_info *curr_tbl, *search_tbl;
+       u8 active_index = 0;
+       u16 fc = le16_to_cpu(hdr->frame_control);
+       s32 tpt = 0;
+
+       IWL_DEBUG_RATE("get frame ack response, update rate scale window\n");
+
+       if (!ieee80211_is_data(fc) || is_multicast_ether_addr(hdr->addr1))
+               return;
+
+       retries = tx_resp->retry_count;
+
+       if (retries > 15)
+               retries = 15;
+
+
+       sta = sta_info_get(local, hdr->addr1);
+
+       if (!sta || !sta->rate_ctrl_priv) {
+               if (sta)
+                       sta_info_put(sta);
+               return;
+       }
+
+       lq = (struct iwl_rate_scale_priv *)sta->rate_ctrl_priv;
+
+       if (!priv->lq_mngr.lq_ready)
+               return;
+
+       if ((priv->iw_mode == IEEE80211_IF_TYPE_IBSS) && !lq->ibss_sta_added)
+               return;
+
+       table = &lq->lq;
+       active_index = lq->active_tbl;
+
+       lq->antenna = (lq->valid_antenna & local->hw.conf.antenna_sel_tx);
+       if (!lq->antenna)
+               lq->antenna = lq->valid_antenna;
+
+       lq->antenna = lq->valid_antenna;
+       curr_tbl = &(lq->lq_info[active_index]);
+       search_tbl = &(lq->lq_info[(1 - active_index)]);
+       window = (struct iwl_rate_scale_data *)
+           &(curr_tbl->win[0]);
+       search_win = (struct iwl_rate_scale_data *)
+           &(search_tbl->win[0]);
+
+       tx_mcs.rate_n_flags = tx_resp->control.tx_rate;
+
+       rs_get_tbl_info_from_mcs(&tx_mcs, priv->phymode,
+                                 &tbl_type, &rs_index);
+       if ((rs_index < 0) || (rs_index >= IWL_RATE_COUNT)) {
+               IWL_DEBUG_RATE("bad rate index at: %d rate 0x%X\n",
+                            rs_index, tx_mcs.rate_n_flags);
+               sta_info_put(sta);
+               return;
+       }
+
+       if (retries &&
+           (tx_mcs.rate_n_flags !=
+                               le32_to_cpu(table->rs_table[0].rate_n_flags))) {
+               IWL_DEBUG_RATE("initial rate does not match 0x%x 0x%x\n",
+                               tx_mcs.rate_n_flags,
+                               le32_to_cpu(table->rs_table[0].rate_n_flags));
+               sta_info_put(sta);
+               return;
+       }
+
+       while (retries) {
+               tx_mcs.rate_n_flags =
+                   le32_to_cpu(table->rs_table[index].rate_n_flags);
+               rs_get_tbl_info_from_mcs(&tx_mcs, priv->phymode,
+                                         &tbl_type, &rs_index);
+
+               if ((tbl_type.lq_type == search_tbl->lq_type) &&
+                   (tbl_type.antenna_type == search_tbl->antenna_type) &&
+                   (tbl_type.is_SGI == search_tbl->is_SGI)) {
+                       if (search_tbl->expected_tpt)
+                               tpt = search_tbl->expected_tpt[rs_index];
+                       else
+                               tpt = 0;
+                       rs_collect_tx_data(search_win,
+                                           rs_index, tpt, 0);
+               } else if ((tbl_type.lq_type == curr_tbl->lq_type) &&
+                          (tbl_type.antenna_type == curr_tbl->antenna_type) &&
+                          (tbl_type.is_SGI == curr_tbl->is_SGI)) {
+                       if (curr_tbl->expected_tpt)
+                               tpt = curr_tbl->expected_tpt[rs_index];
+                       else
+                               tpt = 0;
+                       rs_collect_tx_data(window, rs_index, tpt, 0);
+               }
+               if (lq->stay_in_tbl)
+                       lq->total_failed++;
+               --retries;
+               index++;
+
+       }
+
+       if (!tx_resp->retry_count)
+               tx_mcs.rate_n_flags = tx_resp->control.tx_rate;
+       else
+               tx_mcs.rate_n_flags =
+                       le32_to_cpu(table->rs_table[index].rate_n_flags);
+
+       rs_get_tbl_info_from_mcs(&tx_mcs, priv->phymode,
+                                 &tbl_type, &rs_index);
+
+       if (tx_resp->flags & IEEE80211_TX_STATUS_ACK)
+               status = 1;
+       else
+               status = 0;
+
+       if ((tbl_type.lq_type == search_tbl->lq_type) &&
+           (tbl_type.antenna_type == search_tbl->antenna_type) &&
+           (tbl_type.is_SGI == search_tbl->is_SGI)) {
+               if (search_tbl->expected_tpt)
+                       tpt = search_tbl->expected_tpt[rs_index];
+               else
+                       tpt = 0;
+               rs_collect_tx_data(search_win,
+                                   rs_index, tpt, status);
+       } else if ((tbl_type.lq_type == curr_tbl->lq_type) &&
+                  (tbl_type.antenna_type == curr_tbl->antenna_type) &&
+                  (tbl_type.is_SGI == curr_tbl->is_SGI)) {
+               if (curr_tbl->expected_tpt)
+                       tpt = curr_tbl->expected_tpt[rs_index];
+               else
+                       tpt = 0;
+               rs_collect_tx_data(window, rs_index, tpt, status);
+       }
+
+       if (lq->stay_in_tbl) {
+               if (status)
+                       lq->total_success++;
+               else
+                       lq->total_failed++;
+       }
+
+       rs_rate_scale_perform(priv, dev, hdr, sta);
+       sta_info_put(sta);
+       return;
+}
+
+static u8 rs_is_ant_connected(u8 valid_antenna,
+                             enum iwl_antenna_type antenna_type)
+{
+       if (antenna_type == ANT_AUX)
+               return ((valid_antenna & 0x2) ? 1:0);
+       else if (antenna_type == ANT_MAIN)
+               return ((valid_antenna & 0x1) ? 1:0);
+       else if (antenna_type == ANT_BOTH) {
+               if ((valid_antenna & 0x3) == 0x3)
+                       return 1;
+               else
+                       return 0;
+       }
+
+       return 1;
+}
+
+static u8 rs_is_other_ant_connected(u8 valid_antenna,
+                                   enum iwl_antenna_type antenna_type)
+{
+       if (antenna_type == ANT_AUX)
+               return (rs_is_ant_connected(valid_antenna, ANT_MAIN));
+       else
+               return (rs_is_ant_connected(valid_antenna, ANT_AUX));
+
+       return 0;
+}
+
+static void rs_set_stay_in_table(u8 is_legacy,
+                                struct iwl_rate_scale_priv *lq_data)
+{
+       IWL_DEBUG_HT("we are staying in the same table\n");
+       lq_data->stay_in_tbl = 1;
+       if (is_legacy) {
+               lq_data->table_count_limit = IWL_LEGACY_TABLE_COUNT;
+               lq_data->max_failure_limit = IWL_LEGACY_FAILURE_LIMIT;
+               lq_data->max_success_limit = IWL_LEGACY_TABLE_COUNT;
+       } else {
+               lq_data->table_count_limit = IWL_NONE_LEGACY_TABLE_COUNT;
+               lq_data->max_failure_limit = IWL_NONE_LEGACY_FAILURE_LIMIT;
+               lq_data->max_success_limit = IWL_NONE_LEGACY_SUCCESS_LIMIT;
+       }
+       lq_data->table_count = 0;
+       lq_data->total_failed = 0;
+       lq_data->total_success = 0;
+}
+
+static void rs_get_expected_tpt_table(struct iwl_rate_scale_priv *lq_data,
+                                     struct iwl_scale_tbl_info *tbl)
+{
+       if (is_legacy(tbl->lq_type)) {
+               if (!is_a_band(tbl->lq_type))
+                       tbl->expected_tpt = expected_tpt_G;
+               else
+                       tbl->expected_tpt = expected_tpt_A;
+       } else if (is_siso(tbl->lq_type)) {
+               if (tbl->is_fat && !lq_data->is_dup)
+                       if (tbl->is_SGI)
+                               tbl->expected_tpt = expected_tpt_siso40MHzSGI;
+                       else
+                               tbl->expected_tpt = expected_tpt_siso40MHz;
+               else if (tbl->is_SGI)
+                       tbl->expected_tpt = expected_tpt_siso20MHzSGI;
+               else
+                       tbl->expected_tpt = expected_tpt_siso20MHz;
+
+       } else if (is_mimo(tbl->lq_type)) {
+               if (tbl->is_fat && !lq_data->is_dup)
+                       if (tbl->is_SGI)
+                               tbl->expected_tpt = expected_tpt_mimo40MHzSGI;
+                       else
+                               tbl->expected_tpt = expected_tpt_mimo40MHz;
+               else if (tbl->is_SGI)
+                       tbl->expected_tpt = expected_tpt_mimo20MHzSGI;
+               else
+                       tbl->expected_tpt = expected_tpt_mimo20MHz;
+       } else
+               tbl->expected_tpt = expected_tpt_G;
+}
+
+#ifdef CONFIG_IWLWIFI_HT
+static s32 rs_get_best_rate(struct iwl_priv *priv,
+                           struct iwl_rate_scale_priv *lq_data,
+                           struct iwl_scale_tbl_info *tbl,
+                           u16 rate_mask, s8 index, s8 rate)
+{
+       struct iwl_scale_tbl_info *active_tbl =
+           &(lq_data->lq_info[lq_data->active_tbl]);
+       s32 new_rate, high, low, start_hi;
+       s32 active_sr = active_tbl->win[index].success_ratio;
+       s32 *tpt_tbl = tbl->expected_tpt;
+       s32 active_tpt = active_tbl->expected_tpt[index];
+       u16 high_low;
+
+       new_rate = high = low = start_hi = IWL_RATE_INVALID;
+
+       for (; ;) {
+               high_low = rs_get_adjacent_rate(rate, rate_mask, tbl->lq_type);
+
+               low = high_low & 0xff;
+               high = (high_low >> 8) & 0xff;
+
+               if ((((100 * tpt_tbl[rate]) > lq_data->last_tpt) &&
+                    ((active_sr > IWL_RATE_DECREASE_TH) &&
+                     (active_sr <= IWL_RATE_HIGH_TH) &&
+                     (tpt_tbl[rate] <= active_tpt))) ||
+                   ((active_sr >= IWL_RATE_SCALE_SWITCH) &&
+                    (tpt_tbl[rate] > active_tpt))) {
+
+                       if (start_hi != IWL_RATE_INVALID) {
+                               new_rate = start_hi;
+                               break;
+                       }
+                       new_rate = rate;
+                       if (low != IWL_RATE_INVALID)
+                               rate = low;
+                       else
+                               break;
+               } else {
+                       if (new_rate != IWL_RATE_INVALID)
+                               break;
+                       else if (high != IWL_RATE_INVALID) {
+                               start_hi = high;
+                               rate = high;
+                       } else {
+                               new_rate = rate;
+                               break;
+                       }
+               }
+       }
+
+       return new_rate;
+}
+#endif                         /* CONFIG_IWLWIFI_HT */
+
+static inline u8 rs_is_both_ant_supp(u8 valid_antenna)
+{
+       return (rs_is_ant_connected(valid_antenna, ANT_BOTH));
+}
+
+static int rs_switch_to_mimo(struct iwl_priv *priv,
+                            struct iwl_rate_scale_priv *lq_data,
+                            struct iwl_scale_tbl_info *tbl, int index)
+{
+       int rc = -1;
+#ifdef CONFIG_IWLWIFI_HT
+       u16 rate_mask;
+       s32 rate;
+       s8 is_green = lq_data->is_green;
+
+       if (!priv->is_ht_enabled || !priv->current_assoc_ht.is_ht)
+               return -1;
+
+       IWL_DEBUG_HT("LQ: try to switch to MIMO\n");
+       tbl->lq_type = LQ_MIMO;
+       rs_get_supported_rates(lq_data, NULL, tbl->lq_type,
+                               &rate_mask);
+
+       if (priv->current_assoc_ht.tx_mimo_ps_mode == IWL_MIMO_PS_STATIC)
+               return -1;
+
+       if (!rs_is_both_ant_supp(lq_data->antenna))
+               return -1;
+
+       rc = 0;
+       tbl->is_dup = lq_data->is_dup;
+       tbl->action = 0;
+       if (priv->current_channel_width == IWL_CHANNEL_WIDTH_40MHZ)
+               tbl->is_fat = 1;
+       else
+               tbl->is_fat = 0;
+
+       if (tbl->is_fat) {
+               if (priv->current_assoc_ht.sgf & HT_SHORT_GI_40MHZ_ONLY)
+                       tbl->is_SGI = 1;
+               else
+                       tbl->is_SGI = 0;
+       } else if (priv->current_assoc_ht.sgf & HT_SHORT_GI_20MHZ_ONLY)
+               tbl->is_SGI = 1;
+       else
+               tbl->is_SGI = 0;
+
+       rs_get_expected_tpt_table(lq_data, tbl);
+
+       rate = rs_get_best_rate(priv, lq_data, tbl, rate_mask, index, index);
+
+       IWL_DEBUG_HT("LQ: MIMO best rate %d mask %X\n", rate, rate_mask);
+       if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask))
+               return -1;
+       rs_mcs_from_tbl(&tbl->current_rate, tbl, rate, is_green);
+
+       IWL_DEBUG_HT("LQ: Switch to new mcs %X index is green %X\n",
+                    tbl->current_rate.rate_n_flags, is_green);
+
+#endif                         /*CONFIG_IWLWIFI_HT */
+       return rc;
+}
+
+static int rs_switch_to_siso(struct iwl_priv *priv,
+                            struct iwl_rate_scale_priv *lq_data,
+                            struct iwl_scale_tbl_info *tbl, int index)
+{
+       int rc = -1;
+#ifdef CONFIG_IWLWIFI_HT
+       u16 rate_mask;
+       u8 is_green = lq_data->is_green;
+       s32 rate;
+
+       IWL_DEBUG_HT("LQ: try to switch to SISO\n");
+       if (!priv->is_ht_enabled || !priv->current_assoc_ht.is_ht)
+               return -1;
+
+       rc = 0;
+       tbl->is_dup = lq_data->is_dup;
+       tbl->lq_type = LQ_SISO;
+       tbl->action = 0;
+       rs_get_supported_rates(lq_data, NULL, tbl->lq_type,
+                               &rate_mask);
+
+       if (priv->current_channel_width == IWL_CHANNEL_WIDTH_40MHZ)
+               tbl->is_fat = 1;
+       else
+               tbl->is_fat = 0;
+
+       if (tbl->is_fat) {
+               if (priv->current_assoc_ht.sgf & HT_SHORT_GI_40MHZ_ONLY)
+                       tbl->is_SGI = 1;
+               else
+                       tbl->is_SGI = 0;
+       } else if (priv->current_assoc_ht.sgf & HT_SHORT_GI_20MHZ_ONLY)
+               tbl->is_SGI = 1;
+       else
+               tbl->is_SGI = 0;
+
+       if (is_green)
+               tbl->is_SGI = 0;
+
+       rs_get_expected_tpt_table(lq_data, tbl);
+       rate = rs_get_best_rate(priv, lq_data, tbl, rate_mask, index, index);
+
+       IWL_DEBUG_HT("LQ: get best rate %d mask %X\n", rate, rate_mask);
+       if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) {
+               IWL_DEBUG_HT("can not switch with index %d rate mask %x\n",
+                            rate, rate_mask);
+               return -1;
+       }
+       rs_mcs_from_tbl(&tbl->current_rate, tbl, rate, is_green);
+       IWL_DEBUG_HT("LQ: Switch to new mcs %X index is green %X\n",
+                    tbl->current_rate.rate_n_flags, is_green);
+
+#endif                         /*CONFIG_IWLWIFI_HT */
+       return rc;
+}
+
+static int rs_move_legacy_other(struct iwl_priv *priv,
+                               struct iwl_rate_scale_priv *lq_data,
+                               int index)
+{
+       int rc = 0;
+       struct iwl_scale_tbl_info *tbl =
+           &(lq_data->lq_info[lq_data->active_tbl]);
+       struct iwl_scale_tbl_info *search_tbl =
+           &(lq_data->lq_info[(1 - lq_data->active_tbl)]);
+       struct iwl_rate_scale_data *window = &(tbl->win[index]);
+       u32 sz = (sizeof(struct iwl_scale_tbl_info) -
+                 (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT));
+       u8 start_action = tbl->action;
+
+       for (; ;) {
+               switch (tbl->action) {
+               case IWL_LEGACY_SWITCH_ANTENNA:
+                       IWL_DEBUG_HT("LQ Legacy switch Antenna\n");
+
+                       search_tbl->lq_type = LQ_NONE;
+                       lq_data->action_counter++;
+                       if (window->success_ratio >= IWL_RS_GOOD_RATIO)
+                               break;
+                       if (!rs_is_other_ant_connected(lq_data->antenna,
+                                                       tbl->antenna_type))
+                               break;
+
+                       memcpy(search_tbl, tbl, sz);
+
+                       rs_toggle_antenna(&(search_tbl->current_rate),
+                                          search_tbl);
+                       rs_get_expected_tpt_table(lq_data, search_tbl);
+                       lq_data->search_better_tbl = 1;
+                       goto out;
+
+               case IWL_LEGACY_SWITCH_SISO:
+                       IWL_DEBUG_HT("LQ: Legacy switch to SISO\n");
+                       memcpy(search_tbl, tbl, sz);
+                       search_tbl->lq_type = LQ_SISO;
+                       search_tbl->is_SGI = 0;
+                       search_tbl->is_fat = 0;
+                       rc = rs_switch_to_siso(priv, lq_data, search_tbl,
+                                              index);
+                       if (!rc) {
+                               lq_data->search_better_tbl = 1;
+                               lq_data->action_counter = 0;
+                       }
+                       if (!rc)
+                               goto out;
+
+                       break;
+               case IWL_LEGACY_SWITCH_MIMO:
+                       IWL_DEBUG_HT("LQ: Legacy switch MIMO\n");
+                       memcpy(search_tbl, tbl, sz);
+                       search_tbl->lq_type = LQ_MIMO;
+                       search_tbl->is_SGI = 0;
+                       search_tbl->is_fat = 0;
+                       search_tbl->antenna_type = ANT_BOTH;
+                       rc = rs_switch_to_mimo(priv, lq_data, search_tbl,
+                                              index);
+                       if (!rc) {
+                               lq_data->search_better_tbl = 1;
+                               lq_data->action_counter = 0;
+                       }
+                       if (!rc)
+                               goto out;
+                       break;
+               }
+               tbl->action++;
+               if (tbl->action > IWL_LEGACY_SWITCH_MIMO)
+                       tbl->action = IWL_LEGACY_SWITCH_ANTENNA;
+
+               if (tbl->action == start_action)
+                       break;
+
+       }
+       return 0;
+
+ out:
+       tbl->action++;
+       if (tbl->action > IWL_LEGACY_SWITCH_MIMO)
+               tbl->action = IWL_LEGACY_SWITCH_ANTENNA;
+       return 0;
+
+}
+
+static int rs_move_siso_to_other(struct iwl_priv *priv,
+                                struct iwl_rate_scale_priv *lq_data,
+                                int index)
+{
+       int rc = -1;
+       u8 is_green = lq_data->is_green;
+       struct iwl_scale_tbl_info *tbl =
+           &(lq_data->lq_info[lq_data->active_tbl]);
+       struct iwl_scale_tbl_info *search_tbl =
+           &(lq_data->lq_info[(1 - lq_data->active_tbl)]);
+       struct iwl_rate_scale_data *window = &(tbl->win[index]);
+       u32 sz = (sizeof(struct iwl_scale_tbl_info) -
+                 (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT));
+       u8 start_action = tbl->action;
+
+       for (;;) {
+               lq_data->action_counter++;
+               switch (tbl->action) {
+               case IWL_SISO_SWITCH_ANTENNA:
+                       IWL_DEBUG_HT("LQ: SISO SWITCH ANTENNA SISO\n");
+                       search_tbl->lq_type = LQ_NONE;
+                       if (window->success_ratio >= IWL_RS_GOOD_RATIO)
+                               break;
+                       if (!rs_is_other_ant_connected(lq_data->antenna,
+                                                      tbl->antenna_type))
+                               break;
+
+                       memcpy(search_tbl, tbl, sz);
+                       search_tbl->action = IWL_SISO_SWITCH_MIMO;
+                       rs_toggle_antenna(&(search_tbl->current_rate),
+                                          search_tbl);
+                       lq_data->search_better_tbl = 1;
+
+                       goto out;
+
+               case IWL_SISO_SWITCH_MIMO:
+                       IWL_DEBUG_HT("LQ: SISO SWITCH TO MIMO FROM SISO\n");
+                       memcpy(search_tbl, tbl, sz);
+                       search_tbl->lq_type = LQ_MIMO;
+                       search_tbl->is_SGI = 0;
+                       search_tbl->is_fat = 0;
+                       search_tbl->antenna_type = ANT_BOTH;
+                       rc = rs_switch_to_mimo(priv, lq_data, search_tbl,
+                                              index);
+                       if (!rc)
+                               lq_data->search_better_tbl = 1;
+
+                       if (!rc)
+                               goto out;
+                       break;
+               case IWL_SISO_SWITCH_GI:
+                       IWL_DEBUG_HT("LQ: SISO SWITCH TO GI\n");
+                       memcpy(search_tbl, tbl, sz);
+                       search_tbl->action = 0;
+                       if (search_tbl->is_SGI)
+                               search_tbl->is_SGI = 0;
+                       else if (!is_green)
+                               search_tbl->is_SGI = 1;
+                       else
+                               break;
+                       lq_data->search_better_tbl = 1;
+                       if ((tbl->lq_type == LQ_SISO) &&
+                           (tbl->is_SGI)) {
+                               s32 tpt = lq_data->last_tpt / 100;
+                               if (((!tbl->is_fat) &&
+                                    (tpt >= expected_tpt_siso20MHz[index])) ||
+                                   ((tbl->is_fat) &&
+                                    (tpt >= expected_tpt_siso40MHz[index])))
+                                       lq_data->search_better_tbl = 0;
+                       }
+                       rs_get_expected_tpt_table(lq_data, search_tbl);
+                       rs_mcs_from_tbl(&search_tbl->current_rate,
+                                            search_tbl, index, is_green);
+                       goto out;
+               }
+               tbl->action++;
+               if (tbl->action > IWL_SISO_SWITCH_GI)
+                       tbl->action = IWL_SISO_SWITCH_ANTENNA;
+
+               if (tbl->action == start_action)
+                       break;
+       }
+       return 0;
+
+ out:
+       tbl->action++;
+       if (tbl->action > IWL_SISO_SWITCH_GI)
+               tbl->action = IWL_SISO_SWITCH_ANTENNA;
+       return 0;
+}
+
+static int rs_move_mimo_to_other(struct iwl_priv *priv,
+                                struct iwl_rate_scale_priv *lq_data,
+                                int index)
+{
+       int rc = -1;
+       s8 is_green = lq_data->is_green;
+       struct iwl_scale_tbl_info *tbl =
+           &(lq_data->lq_info[lq_data->active_tbl]);
+       struct iwl_scale_tbl_info *search_tbl =
+           &(lq_data->lq_info[(1 - lq_data->active_tbl)]);
+       u32 sz = (sizeof(struct iwl_scale_tbl_info) -
+                 (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT));
+       u8 start_action = tbl->action;
+
+       for (;;) {
+               lq_data->action_counter++;
+               switch (tbl->action) {
+               case IWL_MIMO_SWITCH_ANTENNA_A:
+               case IWL_MIMO_SWITCH_ANTENNA_B:
+                       IWL_DEBUG_HT("LQ: MIMO SWITCH TO SISO\n");
+                       memcpy(search_tbl, tbl, sz);
+                       search_tbl->lq_type = LQ_SISO;
+                       search_tbl->is_SGI = 0;
+                       search_tbl->is_fat = 0;
+                       if (tbl->action == IWL_MIMO_SWITCH_ANTENNA_A)
+                               search_tbl->antenna_type = ANT_MAIN;
+                       else
+                               search_tbl->antenna_type = ANT_AUX;
+
+                       rc = rs_switch_to_siso(priv, lq_data, search_tbl,
+                                              index);
+                       if (!rc) {
+                               lq_data->search_better_tbl = 1;
+                               goto out;
+                       }
+                       break;
+
+               case IWL_MIMO_SWITCH_GI:
+                       IWL_DEBUG_HT("LQ: MIMO SWITCH TO GI\n");
+                       memcpy(search_tbl, tbl, sz);
+                       search_tbl->lq_type = LQ_MIMO;
+                       search_tbl->antenna_type = ANT_BOTH;
+                       search_tbl->action = 0;
+                       if (search_tbl->is_SGI)
+                               search_tbl->is_SGI = 0;
+                       else
+                               search_tbl->is_SGI = 1;
+                       lq_data->search_better_tbl = 1;
+                       if ((tbl->lq_type == LQ_MIMO) &&
+                           (tbl->is_SGI)) {
+                               s32 tpt = lq_data->last_tpt / 100;
+                               if (((!tbl->is_fat) &&
+                                    (tpt >= expected_tpt_mimo20MHz[index])) ||
+                                   ((tbl->is_fat) &&
+                                    (tpt >= expected_tpt_mimo40MHz[index])))
+                                       lq_data->search_better_tbl = 0;
+                       }
+                       rs_get_expected_tpt_table(lq_data, search_tbl);
+                       rs_mcs_from_tbl(&search_tbl->current_rate,
+                                            search_tbl, index, is_green);
+                       goto out;
+
+               }
+               tbl->action++;
+               if (tbl->action > IWL_MIMO_SWITCH_GI)
+                       tbl->action = IWL_MIMO_SWITCH_ANTENNA_A;
+
+               if (tbl->action == start_action)
+                       break;
+       }
+
+       return 0;
+ out:
+       tbl->action++;
+       if (tbl->action > IWL_MIMO_SWITCH_GI)
+               tbl->action = IWL_MIMO_SWITCH_ANTENNA_A;
+       return 0;
+
+}
+
+static void rs_stay_in_table(struct iwl_rate_scale_priv *lq_data)
+{
+       struct iwl_scale_tbl_info *tbl;
+       int i;
+       int active_tbl;
+       int flush_interval_passed = 0;
+
+       active_tbl = lq_data->active_tbl;
+
+       tbl = &(lq_data->lq_info[active_tbl]);
+
+       if (lq_data->stay_in_tbl) {
+
+               if (lq_data->flush_timer)
+                       flush_interval_passed =
+                           time_after(jiffies,
+                                      (unsigned long)(lq_data->flush_timer +
+                                       IWL_RATE_SCALE_FLUSH_INTVL));
+
+               flush_interval_passed = 0;
+               if ((lq_data->total_failed > lq_data->max_failure_limit) ||
+                   (lq_data->total_success > lq_data->max_success_limit) ||
+                   ((!lq_data->search_better_tbl) && (lq_data->flush_timer)
+                    && (flush_interval_passed))) {
+                       IWL_DEBUG_HT("LQ: stay is expired %d %d %d\n:",
+                                    lq_data->total_failed,
+                                    lq_data->total_success,
+                                    flush_interval_passed);
+                       lq_data->stay_in_tbl = 0;
+                       lq_data->total_failed = 0;
+                       lq_data->total_success = 0;
+                       lq_data->flush_timer = 0;
+               } else if (lq_data->table_count > 0) {
+                       lq_data->table_count++;
+                       if (lq_data->table_count >=
+                           lq_data->table_count_limit) {
+                               lq_data->table_count = 0;
+
+                               IWL_DEBUG_HT("LQ: stay in table clear win\n");
+                               for (i = 0; i < IWL_RATE_COUNT; i++)
+                                       rs_rate_scale_clear_window(
+                                               &(tbl->win[i]));
+                       }
+               }
+
+               if (!lq_data->stay_in_tbl) {
+                       for (i = 0; i < IWL_RATE_COUNT; i++)
+                               rs_rate_scale_clear_window(&(tbl->win[i]));
+               }
+       }
+}
+
+static void rs_rate_scale_perform(struct iwl_priv *priv,
+                                 struct net_device *dev,
+                                 struct ieee80211_hdr *hdr,
+                                 struct sta_info *sta)
+{
+       int low = IWL_RATE_INVALID;
+       int high = IWL_RATE_INVALID;
+       int index;
+       int i;
+       struct iwl_rate_scale_data *window = NULL;
+       int current_tpt = IWL_INVALID_VALUE;
+       int low_tpt = IWL_INVALID_VALUE;
+       int high_tpt = IWL_INVALID_VALUE;
+       u32 fail_count;
+       s8 scale_action = 0;
+       u16 fc, rate_mask;
+       u8 update_lq = 0;
+       struct iwl_rate_scale_priv *lq_data;
+       struct iwl_scale_tbl_info *tbl, *tbl1;
+       u16 rate_scale_index_msk = 0;
+       struct iwl_rate mcs_rate;
+       u8 is_green = 0;
+       u8 active_tbl = 0;
+       u8 done_search = 0;
+       u16 high_low;
+
+       IWL_DEBUG_RATE("rate scale calculate new rate for skb\n");
+
+       fc = le16_to_cpu(hdr->frame_control);
+       if (!ieee80211_is_data(fc) || is_multicast_ether_addr(hdr->addr1)) {
+               /* Send management frames and broadcast/multicast data using
+                * lowest rate. */
+               /* TODO: this could probably be improved.. */
+               return;
+       }
+
+       if (!sta || !sta->rate_ctrl_priv)
+               return;
+
+       if (!priv->lq_mngr.lq_ready) {
+               IWL_DEBUG_RATE("still rate scaling not ready\n");
+               return;
+       }
+       lq_data = (struct iwl_rate_scale_priv *)sta->rate_ctrl_priv;
+
+       if (!lq_data->search_better_tbl)
+               active_tbl = lq_data->active_tbl;
+       else
+               active_tbl = 1 - lq_data->active_tbl;
+
+       tbl = &(lq_data->lq_info[active_tbl]);
+       is_green = lq_data->is_green;
+
+       index = sta->last_txrate;
+
+       IWL_DEBUG_RATE("Rate scale index %d for type %d\n", index,
+                      tbl->lq_type);
+
+       rs_get_supported_rates(lq_data, hdr, tbl->lq_type,
+                               &rate_mask);
+
+       IWL_DEBUG_RATE("mask 0x%04X \n", rate_mask);
+
+       /* mask with station rate restriction */
+       if (is_legacy(tbl->lq_type)) {
+               if (lq_data->phymode == (u8) MODE_IEEE80211A)
+                       rate_scale_index_msk = (u16) (rate_mask &
+                               (sta->supp_rates << IWL_FIRST_OFDM_RATE));
+               else
+                       rate_scale_index_msk = (u16) (rate_mask &
+                                                     sta->supp_rates);
+
+       } else
+               rate_scale_index_msk = rate_mask;
+
+       if (!rate_scale_index_msk)
+               rate_scale_index_msk = rate_mask;
+
+       if (index < 0 || !((1 << index) & rate_scale_index_msk)) {
+               index = IWL_INVALID_VALUE;
+               update_lq = 1;
+
+               /* get the lowest availabe rate */
+               for (i = 0; i <= IWL_RATE_COUNT; i++) {
+                       if ((1 << i) & rate_scale_index_msk)
+                               index = i;
+               }
+
+               if (index == IWL_INVALID_VALUE) {
+                       IWL_WARNING("Can not find a suitable rate\n");
+                       return;
+               }
+       }
+
+       if (!tbl->expected_tpt)
+               rs_get_expected_tpt_table(lq_data, tbl);
+
+       window = &(tbl->win[index]);
+
+       fail_count = window->counter - window->success_counter;
+       if (((fail_count < IWL_RATE_MIN_FAILURE_TH) &&
+            (window->success_counter < IWL_RATE_MIN_SUCCESS_TH))
+           || (tbl->expected_tpt == NULL)) {
+               IWL_DEBUG_RATE("LQ: still below TH succ %d total %d "
+                              "for index %d\n",
+                              window->success_counter, window->counter, index);
+               window->average_tpt = IWL_INVALID_VALUE;
+               rs_stay_in_table(lq_data);
+               if (update_lq) {
+                       rs_mcs_from_tbl(&mcs_rate, tbl, index, is_green);
+                       rs_fill_link_cmd(lq_data, &mcs_rate, &lq_data->lq, sta);
+                       rs_send_lq_cmd(priv, &lq_data->lq, CMD_ASYNC);
+               }
+               goto out;
+
+       } else
+               window->average_tpt = ((window->success_ratio *
+                                       tbl->expected_tpt[index] + 64) / 128);
+
+       if (lq_data->search_better_tbl) {
+               int success_limit = IWL_RATE_SCALE_SWITCH;
+
+               if ((window->success_ratio > success_limit) ||
+                   (window->average_tpt > lq_data->last_tpt)) {
+                       if (!is_legacy(tbl->lq_type)) {
+                               IWL_DEBUG_HT("LQ: we are switching to HT"
+                                            " rate suc %d current tpt %d"
+                                            " old tpt %d\n",
+                                            window->success_ratio,
+                                            window->average_tpt,
+                                            lq_data->last_tpt);
+                               lq_data->enable_counter = 1;
+                       }
+                       lq_data->active_tbl = active_tbl;
+                       current_tpt = window->average_tpt;
+               } else {
+                       tbl->lq_type = LQ_NONE;
+                       active_tbl = lq_data->active_tbl;
+                       tbl = &(lq_data->lq_info[active_tbl]);
+
+                       index = iwl_rate_index_from_plcp(
+                               tbl->current_rate.rate_n_flags);
+
+                       update_lq = 1;
+                       current_tpt = lq_data->last_tpt;
+                       IWL_DEBUG_HT("XXY GO BACK TO OLD TABLE\n");
+               }
+               lq_data->search_better_tbl = 0;
+               done_search = 1;
+               goto lq_update;
+       }
+
+       high_low = rs_get_adjacent_rate(index, rate_scale_index_msk,
+                                       tbl->lq_type);
+       low = high_low & 0xff;
+       high = (high_low >> 8) & 0xff;
+
+       current_tpt = window->average_tpt;
+
+       if (low != IWL_RATE_INVALID)
+               low_tpt = tbl->win[low].average_tpt;
+
+       if (high != IWL_RATE_INVALID)
+               high_tpt = tbl->win[high].average_tpt;
+
+
+       scale_action = 1;
+
+       if ((window->success_ratio <= IWL_RATE_DECREASE_TH) ||
+           (current_tpt == 0)) {
+               IWL_DEBUG_RATE("decrease rate because of low success_ratio\n");
+               scale_action = -1;
+       } else if ((low_tpt == IWL_INVALID_VALUE) &&
+                  (high_tpt == IWL_INVALID_VALUE))
+               scale_action = 1;
+       else if ((low_tpt != IWL_INVALID_VALUE) &&
+                (high_tpt != IWL_INVALID_VALUE) &&
+                (low_tpt < current_tpt) &&
+                (high_tpt < current_tpt))
+               scale_action = 0;
+       else {
+               if (high_tpt != IWL_INVALID_VALUE) {
+                       if (high_tpt > current_tpt)
+                               scale_action = 1;
+                       else {
+                               IWL_DEBUG_RATE
+                                   ("decrease rate because of high tpt\n");
+                               scale_action = -1;
+                       }
+               } else if (low_tpt != IWL_INVALID_VALUE) {
+                       if (low_tpt > current_tpt) {
+                               IWL_DEBUG_RATE
+                                   ("decrease rate because of low tpt\n");
+                               scale_action = -1;
+                       } else
+                               scale_action = 1;
+               }
+       }
+
+       if (scale_action == -1) {
+               if ((low != IWL_RATE_INVALID) &&
+                   ((window->success_ratio > IWL_RATE_HIGH_TH) ||
+                    (current_tpt > (100 * tbl->expected_tpt[low]))))
+                       scale_action = 0;
+       } else if ((scale_action == 1) &&
+                  (window->success_ratio < IWL_RATE_INCREASE_TH))
+               scale_action = 0;
+
+       switch (scale_action) {
+       case -1:
+               if (low != IWL_RATE_INVALID) {
+                       update_lq = 1;
+                       index = low;
+               }
+               break;
+       case 1:
+               if (high != IWL_RATE_INVALID) {
+                       update_lq = 1;
+                       index = high;
+               }
+
+               break;
+       case 0:
+       default:
+               break;
+       }
+
+       IWL_DEBUG_HT("choose rate scale index %d action %d low %d "
+                   "high %d type %d\n",
+                    index, scale_action, low, high, tbl->lq_type);
+
+ lq_update:
+       if (update_lq) {
+               rs_mcs_from_tbl(&mcs_rate, tbl, index, is_green);
+               rs_fill_link_cmd(lq_data, &mcs_rate, &lq_data->lq, sta);
+               rs_send_lq_cmd(priv, &lq_data->lq, CMD_ASYNC);
+       }
+       rs_stay_in_table(lq_data);
+
+       if (!update_lq && !done_search && !lq_data->stay_in_tbl) {
+               lq_data->last_tpt = current_tpt;
+
+               if (is_legacy(tbl->lq_type))
+                       rs_move_legacy_other(priv, lq_data, index);
+               else if (is_siso(tbl->lq_type))
+                       rs_move_siso_to_other(priv, lq_data, index);
+               else
+                       rs_move_mimo_to_other(priv, lq_data, index);
+
+               if (lq_data->search_better_tbl) {
+                       tbl = &(lq_data->lq_info[(1 - lq_data->active_tbl)]);
+                       for (i = 0; i < IWL_RATE_COUNT; i++)
+                               rs_rate_scale_clear_window(&(tbl->win[i]));
+
+                       index = iwl_rate_index_from_plcp(
+                                       tbl->current_rate.rate_n_flags);
+
+                       IWL_DEBUG_HT("Switch current  mcs: %X index: %d\n",
+                                    tbl->current_rate.rate_n_flags, index);
+                       rs_fill_link_cmd(lq_data, &tbl->current_rate,
+                                        &(lq_data->lq), sta);
+                       rs_send_lq_cmd(priv, &lq_data->lq, CMD_ASYNC);
+               }
+               tbl1 = &(lq_data->lq_info[lq_data->active_tbl]);
+
+               if (is_legacy(tbl1->lq_type) &&
+#ifdef CONFIG_IWLWIFI_HT
+                   !priv->current_assoc_ht.is_ht &&
+#endif
+                   (lq_data->action_counter >= 1)) {
+                       lq_data->action_counter = 0;
+                       IWL_DEBUG_HT("LQ: STAY in legacy table\n");
+                       rs_set_stay_in_table(1, lq_data);
+               }
+
+               if (lq_data->enable_counter &&
+                   (lq_data->action_counter >= IWL_ACTION_LIMIT)) {
+#ifdef CONFIG_IWLWIFI_HT_AGG
+                       if ((lq_data->last_tpt > TID_AGG_TPT_THREHOLD) &&
+                           (priv->lq_mngr.agg_ctrl.auto_agg)) {
+                               priv->lq_mngr.agg_ctrl.tid_retry =
+                                   TID_ALL_SPECIFIED;
+                               schedule_work(&priv->agg_work);
+                       }
+#endif /*CONFIG_IWLWIFI_HT_AGG */
+                       lq_data->action_counter = 0;
+                       rs_set_stay_in_table(0, lq_data);
+               }
+       } else {
+               if ((!update_lq) && (!done_search) && (!lq_data->flush_timer))
+                       lq_data->flush_timer = jiffies;
+       }
+
+out:
+       rs_mcs_from_tbl(&tbl->current_rate, tbl, index, is_green);
+       i = index;
+       sta->last_txrate = i;
+
+       /* sta->txrate is an index to A mode rates which start
+        * at IWL_FIRST_OFDM_RATE
+        */
+       if (lq_data->phymode == (u8) MODE_IEEE80211A)
+               sta->txrate = i - IWL_FIRST_OFDM_RATE;
+       else
+               sta->txrate = i;
+
+       return;
+}
+
+
+static void rs_initialize_lq(struct iwl_priv *priv,
+                            struct sta_info *sta)
+{
+       int i;
+       struct iwl_rate_scale_priv *lq;
+       struct iwl_scale_tbl_info *tbl;
+       u8 active_tbl = 0;
+       int rate_idx;
+       u8 use_green = rs_use_green(priv);
+       struct iwl_rate mcs_rate;
+
+       if (!sta || !sta->rate_ctrl_priv)
+               goto out;
+
+       lq = (struct iwl_rate_scale_priv *)sta->rate_ctrl_priv;
+       i = sta->last_txrate;
+
+       if ((lq->lq.sta_id == 0xff) &&
+           (priv->iw_mode == IEEE80211_IF_TYPE_IBSS))
+               goto out;
+
+       if (!lq->search_better_tbl)
+               active_tbl = lq->active_tbl;
+       else
+               active_tbl = 1 - lq->active_tbl;
+
+       tbl = &(lq->lq_info[active_tbl]);
+
+       if ((i < 0) || (i >= IWL_RATE_COUNT))
+               i = 0;
+
+       mcs_rate.rate_n_flags = iwl_rates[i].plcp ;
+       mcs_rate.rate_n_flags |= RATE_MCS_ANT_B_MSK;
+       mcs_rate.rate_n_flags &= ~RATE_MCS_ANT_A_MSK;
+
+       if (i >= IWL_FIRST_CCK_RATE && i <= IWL_LAST_CCK_RATE)
+               mcs_rate.rate_n_flags |= RATE_MCS_CCK_MSK;
+
+       tbl->antenna_type = ANT_AUX;
+       rs_get_tbl_info_from_mcs(&mcs_rate, priv->phymode, tbl, &rate_idx);
+       if (!rs_is_ant_connected(priv->valid_antenna, tbl->antenna_type))
+           rs_toggle_antenna(&mcs_rate, tbl),
+
+       rs_mcs_from_tbl(&mcs_rate, tbl, rate_idx, use_green);
+       tbl->current_rate.rate_n_flags = mcs_rate.rate_n_flags;
+       rs_get_expected_tpt_table(lq, tbl);
+       rs_fill_link_cmd(lq, &mcs_rate, &(lq->lq), sta);
+       rs_send_lq_cmd(priv, &lq->lq, CMD_ASYNC);
+ out:
+       return;
+}
+
+static struct ieee80211_rate *rs_get_lowest_rate(struct ieee80211_local
+                                                *local)
+{
+       struct ieee80211_hw_mode *mode = local->oper_hw_mode;
+       int i;
+
+       for (i = 0; i < mode->num_rates; i++) {
+               struct ieee80211_rate *rate = &mode->rates[i];
+
+               if (rate->flags & IEEE80211_RATE_SUPPORTED)
+                       return rate;
+       }
+
+       return &mode->rates[0];
+}
+
+static struct ieee80211_rate *rs_get_rate(void *priv_rate,
+                                              struct net_device *dev,
+                                              struct sk_buff *skb,
+                                              struct rate_control_extra
+                                              *extra)
+{
+
+       int i;
+       struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
+       struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+       struct sta_info *sta;
+       u16 fc;
+       struct iwl_priv *priv = (struct iwl_priv *)priv_rate;
+       struct iwl_rate_scale_priv *lq;
+
+       IWL_DEBUG_RATE("rate scale calculate new rate for skb\n");
+
+       memset(extra, 0, sizeof(*extra));
+
+       fc = le16_to_cpu(hdr->frame_control);
+       if (!ieee80211_is_data(fc) || is_multicast_ether_addr(hdr->addr1)) {
+               /* Send management frames and broadcast/multicast data using
+                * lowest rate. */
+               /* TODO: this could probably be improved.. */
+               return rs_get_lowest_rate(local);
+       }
+
+       sta = sta_info_get(local, hdr->addr1);
+
+       if (!sta || !sta->rate_ctrl_priv) {
+               if (sta)
+                       sta_info_put(sta);
+               return rs_get_lowest_rate(local);
+       }
+
+       lq = (struct iwl_rate_scale_priv *)sta->rate_ctrl_priv;
+       i = sta->last_txrate;
+
+       if ((priv->iw_mode == IEEE80211_IF_TYPE_IBSS) && !lq->ibss_sta_added) {
+               u8 sta_id = iwl_hw_find_station(priv, hdr->addr1);
+
+               if (sta_id == IWL_INVALID_STATION) {
+                       IWL_DEBUG_RATE("LQ: ADD station " MAC_FMT "\n",
+                                       MAC_ARG(hdr->addr1));
+                       sta_id = iwl_add_station(priv,
+                                                hdr->addr1, 0, CMD_ASYNC);
+               }
+               if ((sta_id != IWL_INVALID_STATION)) {
+                       lq->lq.sta_id = sta_id;
+                       lq->lq.rs_table[0].rate_n_flags = 0;
+                       lq->ibss_sta_added = 1;
+                       rs_initialize_lq(priv, sta);
+               }
+               if (!lq->ibss_sta_added)
+                       goto done;
+       }
+
+ done:
+       sta_info_put(sta);
+       if ((i < 0) || (i > IWL_RATE_COUNT))
+               return rs_get_lowest_rate(local);
+
+       return &priv->ieee_rates[i];
+}
+
+static void *rs_alloc_sta(void *priv, gfp_t gfp)
+{
+       struct iwl_rate_scale_priv *crl;
+       int i, j;
+
+       IWL_DEBUG_RATE("create station rate scale window\n");
+
+       crl = kzalloc(sizeof(struct iwl_rate_scale_priv), gfp);
+
+       if (crl == NULL)
+               return NULL;
+
+       memset(crl, 0, sizeof(struct iwl_rate_scale_priv));
+       crl->lq.sta_id = 0xff;
+
+       for (j = 0; j < LQ_SIZE; j++)
+               for (i = 0; i < IWL_RATE_COUNT; i++)
+                       rs_rate_scale_clear_window(&(crl->lq_info[j].win[i]));
+
+       return crl;
+}
+
+static void rs_rate_init(void *priv_rate, void *priv_sta,
+                        struct ieee80211_local *local,
+                        struct sta_info *sta)
+{
+       int i, j;
+       struct ieee80211_hw_mode *mode = local->oper_hw_mode;
+       struct iwl_priv *priv = (struct iwl_priv *)priv_rate;
+       struct iwl_rate_scale_priv *crl = priv_sta;
+
+       memset(crl, 0, sizeof(struct iwl_rate_scale_priv));
+
+       crl->lq.sta_id = 0xff;
+       crl->flush_timer = 0;
+       sta->txrate = 3;
+       for (j = 0; j < LQ_SIZE; j++)
+               for (i = 0; i < IWL_RATE_COUNT; i++)
+                       rs_rate_scale_clear_window(&(crl->lq_info[j].win[i]));
+
+       IWL_DEBUG_RATE("rate scale global init\n");
+       /* TODO: what is a good starting rate for STA? About middle? Maybe not
+        * the lowest or the highest rate.. Could consider using RSSI from
+        * previous packets? Need to have IEEE 802.1X auth succeed immediately
+        * after assoc.. */
+
+       crl->ibss_sta_added = 0;
+       if (priv->iw_mode == IEEE80211_IF_TYPE_AP) {
+               u8 sta_id = iwl_hw_find_station(priv, sta->addr);
+               /* for IBSS the call are from tasklet */
+               IWL_DEBUG_HT("LQ: ADD station " MAC_FMT " \n",
+                            MAC_ARG(sta->addr));
+
+               if (sta_id == IWL_INVALID_STATION) {
+                       IWL_DEBUG_RATE("LQ: ADD station " MAC_FMT "\n",
+                                       MAC_ARG(sta->addr));
+                                       sta_id = iwl_add_station(priv,
+                                                sta->addr, 0, CMD_ASYNC);
+               }
+               if ((sta_id != IWL_INVALID_STATION)) {
+                       crl->lq.sta_id = sta_id;
+                       crl->lq.rs_table[0].rate_n_flags = 0;
+               }
+               /* FIXME: this is w/a remove it later */
+               priv->assoc_station_added = 1;
+       }
+
+       for (i = 0; i < mode->num_rates; i++) {
+               if ((sta->supp_rates & BIT(i)) &&
+                   (mode->rates[i].flags & IEEE80211_RATE_SUPPORTED))
+                       sta->txrate = i;
+       }
+       sta->last_txrate = sta->txrate;
+       /* For MODE_IEEE80211A mode cck rate are at end
+        * rate table
+        */
+       if (local->hw.conf.phymode == MODE_IEEE80211A)
+               sta->last_txrate += IWL_FIRST_OFDM_RATE;
+
+       crl->is_dup = priv->is_dup;
+       crl->valid_antenna = priv->valid_antenna;
+       crl->antenna = priv->antenna;
+       crl->is_green = rs_use_green(priv);
+       crl->active_rate = priv->active_rate;
+       crl->active_rate &= ~(0x1000);
+       crl->active_rate_basic = priv->active_rate_basic;
+       crl->phymode = priv->phymode;
+#ifdef CONFIG_IWLWIFI_HT
+       crl->active_siso_rate = (priv->current_assoc_ht.supp_rates[0] << 1);
+       crl->active_siso_rate |= (priv->current_assoc_ht.supp_rates[0] & 0x1);
+       crl->active_siso_rate &= ~((u16)0x2);
+       crl->active_siso_rate = crl->active_siso_rate << IWL_FIRST_OFDM_RATE;
+
+       crl->active_mimo_rate = (priv->current_assoc_ht.supp_rates[1] << 1);
+       crl->active_mimo_rate |= (priv->current_assoc_ht.supp_rates[1] & 0x1);
+       crl->active_mimo_rate &= ~((u16)0x2);
+       crl->active_mimo_rate = crl->active_mimo_rate << IWL_FIRST_OFDM_RATE;
+       IWL_DEBUG_HT("MIMO RATE 0x%X SISO MASK 0x%X\n", crl->active_siso_rate,
+                    crl->active_mimo_rate);
+#endif /*CONFIG_IWLWIFI_HT*/
+
+       if (priv->assoc_station_added)
+               priv->lq_mngr.lq_ready = 1;
+
+       rs_initialize_lq(priv, sta);
+}
+
+static int rs_fill_link_cmd(struct iwl_rate_scale_priv *lq_data,
+                           struct iwl_rate *tx_mcs,
+                           struct iwl_link_quality_cmd *lq_cmd,
+                           struct sta_info *sta)
+{
+       int index = 0;
+       int rc = 0;
+       int rate_idx;
+       u8 ant_toggle_count = 0;
+       u8 use_ht_possible = 1;
+       u8 repeat_cur_rate = 0;
+       struct iwl_rate new_rate;
+       struct iwl_scale_tbl_info tbl_type = { 0 };
+
+       rs_get_tbl_info_from_mcs(tx_mcs, lq_data->phymode,
+                                 &tbl_type, &rate_idx);
+
+       if (is_legacy(tbl_type.lq_type)) {
+               ant_toggle_count = 1;
+               repeat_cur_rate = IWL_NUMBER_TRY;
+       } else
+               repeat_cur_rate = IWL_HT_NUMBER_TRY;
+
+       lq_cmd->general_params.mimo_delimiter =
+                       is_mimo(tbl_type.lq_type) ? 1 : 0;
+       lq_cmd->rs_table[index].rate_n_flags =
+                       cpu_to_le32(tx_mcs->rate_n_flags);
+       new_rate.rate_n_flags = tx_mcs->rate_n_flags;
+
+       if (is_mimo(tbl_type.lq_type) || (tbl_type.antenna_type == ANT_MAIN))
+               lq_cmd->general_params.single_stream_ant_msk = 1;
+       else
+               lq_cmd->general_params.single_stream_ant_msk = 2;
+
+       index++;
+       repeat_cur_rate--;
+
+       while (index < LINK_QUAL_MAX_RETRY_NUM) {
+               while (repeat_cur_rate && (index < LINK_QUAL_MAX_RETRY_NUM)) {
+                       if (is_legacy(tbl_type.lq_type)) {
+                               if (ant_toggle_count <
+                                   NUM_TRY_BEFORE_ANTENNA_TOGGLE)
+                                       ant_toggle_count++;
+                               else {
+                                       rs_toggle_antenna(&new_rate, &tbl_type);
+                                       ant_toggle_count = 1;
+                               }
+                       }
+                       lq_cmd->rs_table[index].rate_n_flags =
+                                       cpu_to_le32(new_rate.rate_n_flags);
+                       repeat_cur_rate--;
+                       index++;
+               }
+
+               rs_get_tbl_info_from_mcs(&new_rate, lq_data->phymode, &tbl_type,
+                                               &rate_idx);
+
+               if (is_mimo(tbl_type.lq_type))
+                       lq_cmd->general_params.mimo_delimiter = index;
+
+               rs_get_lower_rate(lq_data, &tbl_type, rate_idx,
+                                 use_ht_possible, &new_rate, sta);
+
+               if (is_legacy(tbl_type.lq_type)) {
+                       if (ant_toggle_count < NUM_TRY_BEFORE_ANTENNA_TOGGLE)
+                               ant_toggle_count++;
+                       else {
+                               rs_toggle_antenna(&new_rate, &tbl_type);
+                               ant_toggle_count = 1;
+                       }
+                       repeat_cur_rate = IWL_NUMBER_TRY;
+               } else
+                       repeat_cur_rate = IWL_HT_NUMBER_TRY;
+
+               use_ht_possible = 0;
+
+               lq_cmd->rs_table[index].rate_n_flags =
+                               cpu_to_le32(new_rate.rate_n_flags);
+               /* lq_cmd->rs_table[index].rate_n_flags = 0x800d; */
+
+               index++;
+               repeat_cur_rate--;
+       }
+
+       /* lq_cmd->rs_table[0].rate_n_flags = 0x800d; */
+
+       lq_cmd->general_params.dual_stream_ant_msk = 3;
+       lq_cmd->agg_params.agg_dis_start_th = 3;
+       lq_cmd->agg_params.agg_time_limit = cpu_to_le16(4000);
+       return rc;
+}
+
+static void *rs_alloc(struct ieee80211_local *local)
+{
+       return local->hw.priv;
+}
+/* rate scale requires free function to be implemented */
+static void rs_free(void *priv_rate)
+{
+       return;
+}
+
+static void rs_clear(void *priv_rate)
+{
+       struct iwl_priv *priv = (struct iwl_priv *) priv_rate;
+
+       IWL_DEBUG_RATE("enter\n");
+
+       priv->lq_mngr.lq_ready = 0;
+#ifdef CONFIG_IWLWIFI_HT
+#ifdef CONFIG_IWLWIFI_HT_AGG
+       if (priv->lq_mngr.agg_ctrl.granted_ba)
+               iwl4965_turn_off_agg(priv, TID_ALL_SPECIFIED);
+#endif /*CONFIG_IWLWIFI_HT_AGG */
+#endif /* CONFIG_IWLWIFI_HT */
+
+       IWL_DEBUG_RATE("leave\n");
+}
+
+static void rs_free_sta(void *priv, void *priv_sta)
+{
+       struct iwl_rate_scale_priv *rs_priv = priv_sta;
+
+       IWL_DEBUG_RATE("enter\n");
+       kfree(rs_priv);
+       IWL_DEBUG_RATE("leave\n");
+}
+
+
+static struct rate_control_ops rs_ops = {
+       .module = NULL,
+       .name = RS_NAME,
+       .tx_status = rs_tx_status,
+       .get_rate = rs_get_rate,
+       .rate_init = rs_rate_init,
+       .clear = rs_clear,
+       .alloc = rs_alloc,
+       .free = rs_free,
+       .alloc_sta = rs_alloc_sta,
+       .free_sta = rs_free_sta,
+};
+
+int iwl_fill_rs_info(struct ieee80211_hw *hw, char *buf, u8 sta_id)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+       struct iwl_priv *priv = hw->priv;
+       struct iwl_rate_scale_priv *rs_priv;
+       struct sta_info *sta;
+       int count = 0, i;
+       u32 samples = 0, success = 0, good = 0;
+       unsigned long now = jiffies;
+       u32 max_time = 0;
+       u8 lq_type, antenna;
+
+       sta = sta_info_get(local, priv->stations[sta_id].sta.sta.addr);
+       if (!sta || !sta->rate_ctrl_priv) {
+               if (sta) {
+                       sta_info_put(sta);
+                       IWL_DEBUG_RATE("leave - no private rate data!\n");
+               } else
+                       IWL_DEBUG_RATE("leave - no station!\n");
+               return sprintf(buf, "station %d not found\n", sta_id);
+       }
+
+       rs_priv = (void *)sta->rate_ctrl_priv;
+
+       lq_type = rs_priv->lq_info[rs_priv->active_tbl].lq_type;
+       antenna = rs_priv->lq_info[rs_priv->active_tbl].antenna_type;
+
+       if (is_legacy(lq_type))
+               i = IWL_RATE_54M_INDEX;
+       else
+               i = IWL_RATE_60M_INDEX;
+       while (1) {
+               u64 mask;
+               int j;
+               int active = rs_priv->active_tbl;
+
+               count +=
+                   sprintf(&buf[count], " %2dMbs: ", iwl_rates[i].ieee / 2);
+
+               mask = (1ULL << (IWL_RATE_MAX_WINDOW - 1));
+               for (j = 0; j < IWL_RATE_MAX_WINDOW; j++, mask >>= 1)
+                       buf[count++] =
+                               (rs_priv->lq_info[active].win[i].data & mask)
+                               ? '1' : '0';
+
+               samples += rs_priv->lq_info[active].win[i].counter;
+               good += rs_priv->lq_info[active].win[i].success_counter;
+               success += rs_priv->lq_info[active].win[i].success_counter *
+                          iwl_rates[i].ieee;
+
+               if (rs_priv->lq_info[active].win[i].stamp) {
+                       int delta =
+                                  jiffies_to_msecs(now -
+                                  rs_priv->lq_info[active].win[i].stamp);
+
+                       if (delta > max_time)
+                               max_time = delta;
+
+                       count += sprintf(&buf[count], "%5dms\n", delta);
+               } else
+                       buf[count++] = '\n';
+
+               j = iwl_get_prev_ieee_rate(i);
+               if (j == i)
+                       break;
+               i = j;
+       }
+
+       /* Display the average rate of all samples taken.
+        *
+        * NOTE:  We multiple # of samples by 2 since the IEEE measurement
+        * added from iwl_rates is actually 2X the rate */
+       if (samples)
+               count += sprintf(&buf[count],
+                        "\nAverage rate is %3d.%02dMbs over last %4dms\n"
+                        "%3d%% success (%d good packets over %d tries)\n",
+                        success / (2 * samples), (success * 5 / samples) % 10,
+                        max_time, good * 100 / samples, good, samples);
+       else
+               count += sprintf(&buf[count], "\nAverage rate: 0Mbs\n");
+       count += sprintf(&buf[count], "\nrate scale type %d anntena %d "
+                        "active_search %d rate index %d\n", lq_type, antenna,
+                        rs_priv->search_better_tbl, sta->last_txrate);
+
+       sta_info_put(sta);
+       return count;
+}
+
+void iwl_rate_scale_init(struct ieee80211_hw *hw, s32 sta_id)
+{
+       struct iwl_priv *priv = hw->priv;
+
+       priv->lq_mngr.lq_ready = 1;
+}
+
+void iwl_rate_control_register(struct ieee80211_hw *hw)
+{
+       ieee80211_rate_control_register(&rs_ops);
+}
+
+void iwl_rate_control_unregister(struct ieee80211_hw *hw)
+{
+       ieee80211_rate_control_unregister(&rs_ops);
+}
+
diff --git a/drivers/net/wireless/iwlwifi/iwl-4965-rs.h b/drivers/net/wireless/iwlwifi/iwl-4965-rs.h
new file mode 100644 (file)
index 0000000..c6325f7
--- /dev/null
@@ -0,0 +1,266 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2003 - 2007 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:
+ * James P. Ketrenos <ipw2100-admin@linux.intel.com>
+ * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+ *
+ *****************************************************************************/
+
+#ifndef __iwl_4965_rs_h__
+#define __iwl_4965_rs_h__
+
+#include "iwl-4965.h"
+
+struct iwl_rate_info {
+       u8 plcp;
+       u8 plcp_siso;
+       u8 plcp_mimo;
+       u8 ieee;
+       u8 prev_ieee;    /* previous rate in IEEE speeds */
+       u8 next_ieee;    /* next rate in IEEE speeds */
+       u8 prev_rs;      /* previous rate used in rs algo */
+       u8 next_rs;      /* next rate used in rs algo */
+       u8 prev_rs_tgg;  /* previous rate used in TGG rs algo */
+       u8 next_rs_tgg;  /* next rate used in TGG rs algo */
+};
+
+enum {
+       IWL_RATE_1M_INDEX = 0,
+       IWL_RATE_2M_INDEX,
+       IWL_RATE_5M_INDEX,
+       IWL_RATE_11M_INDEX,
+       IWL_RATE_6M_INDEX,
+       IWL_RATE_9M_INDEX,
+       IWL_RATE_12M_INDEX,
+       IWL_RATE_18M_INDEX,
+       IWL_RATE_24M_INDEX,
+       IWL_RATE_36M_INDEX,
+       IWL_RATE_48M_INDEX,
+       IWL_RATE_54M_INDEX,
+       IWL_RATE_60M_INDEX,
+       IWL_RATE_COUNT,
+       IWL_RATE_INVM_INDEX = IWL_RATE_COUNT,
+       IWL_RATE_INVALID = IWL_RATE_INVM_INDEX
+};
+
+enum {
+       IWL_FIRST_OFDM_RATE = IWL_RATE_6M_INDEX,
+       IWL_LAST_OFDM_RATE = IWL_RATE_60M_INDEX,
+       IWL_FIRST_CCK_RATE = IWL_RATE_1M_INDEX,
+       IWL_LAST_CCK_RATE = IWL_RATE_11M_INDEX,
+};
+
+/* #define vs. enum to keep from defaulting to 'large integer' */
+#define        IWL_RATE_6M_MASK   (1<<IWL_RATE_6M_INDEX)
+#define        IWL_RATE_9M_MASK   (1<<IWL_RATE_9M_INDEX)
+#define        IWL_RATE_12M_MASK  (1<<IWL_RATE_12M_INDEX)
+#define        IWL_RATE_18M_MASK  (1<<IWL_RATE_18M_INDEX)
+#define        IWL_RATE_24M_MASK  (1<<IWL_RATE_24M_INDEX)
+#define        IWL_RATE_36M_MASK  (1<<IWL_RATE_36M_INDEX)
+#define        IWL_RATE_48M_MASK  (1<<IWL_RATE_48M_INDEX)
+#define        IWL_RATE_54M_MASK  (1<<IWL_RATE_54M_INDEX)
+#define IWL_RATE_60M_MASK  (1<<IWL_RATE_60M_INDEX)
+#define        IWL_RATE_1M_MASK   (1<<IWL_RATE_1M_INDEX)
+#define        IWL_RATE_2M_MASK   (1<<IWL_RATE_2M_INDEX)
+#define        IWL_RATE_5M_MASK   (1<<IWL_RATE_5M_INDEX)
+#define        IWL_RATE_11M_MASK  (1<<IWL_RATE_11M_INDEX)
+
+enum {
+       IWL_RATE_6M_PLCP  = 13,
+       IWL_RATE_9M_PLCP  = 15,
+       IWL_RATE_12M_PLCP = 5,
+       IWL_RATE_18M_PLCP = 7,
+       IWL_RATE_24M_PLCP = 9,
+       IWL_RATE_36M_PLCP = 11,
+       IWL_RATE_48M_PLCP = 1,
+       IWL_RATE_54M_PLCP = 3,
+       IWL_RATE_60M_PLCP = 3,
+       IWL_RATE_1M_PLCP  = 10,
+       IWL_RATE_2M_PLCP  = 20,
+       IWL_RATE_5M_PLCP  = 55,
+       IWL_RATE_11M_PLCP = 110,
+};
+
+/* OFDM HT rate plcp */
+enum {
+       IWL_RATE_SISO_6M_PLCP = 0,
+       IWL_RATE_SISO_12M_PLCP = 1,
+       IWL_RATE_SISO_18M_PLCP = 2,
+       IWL_RATE_SISO_24M_PLCP = 3,
+       IWL_RATE_SISO_36M_PLCP = 4,
+       IWL_RATE_SISO_48M_PLCP = 5,
+       IWL_RATE_SISO_54M_PLCP = 6,
+       IWL_RATE_SISO_60M_PLCP = 7,
+       IWL_RATE_MIMO_6M_PLCP  = 0x8,
+       IWL_RATE_MIMO_12M_PLCP = 0x9,
+       IWL_RATE_MIMO_18M_PLCP = 0xa,
+       IWL_RATE_MIMO_24M_PLCP = 0xb,
+       IWL_RATE_MIMO_36M_PLCP = 0xc,
+       IWL_RATE_MIMO_48M_PLCP = 0xd,
+       IWL_RATE_MIMO_54M_PLCP = 0xe,
+       IWL_RATE_MIMO_60M_PLCP = 0xf,
+       IWL_RATE_SISO_INVM_PLCP,
+       IWL_RATE_MIMO_INVM_PLCP = IWL_RATE_SISO_INVM_PLCP,
+};
+
+enum {
+       IWL_RATE_6M_IEEE  = 12,
+       IWL_RATE_9M_IEEE  = 18,
+       IWL_RATE_12M_IEEE = 24,
+       IWL_RATE_18M_IEEE = 36,
+       IWL_RATE_24M_IEEE = 48,
+       IWL_RATE_36M_IEEE = 72,
+       IWL_RATE_48M_IEEE = 96,
+       IWL_RATE_54M_IEEE = 108,
+       IWL_RATE_60M_IEEE = 120,
+       IWL_RATE_1M_IEEE  = 2,
+       IWL_RATE_2M_IEEE  = 4,
+       IWL_RATE_5M_IEEE  = 11,
+       IWL_RATE_11M_IEEE = 22,
+};
+
+#define IWL_CCK_BASIC_RATES_MASK    \
+       (IWL_RATE_1M_MASK          | \
+       IWL_RATE_2M_MASK)
+
+#define IWL_CCK_RATES_MASK          \
+       (IWL_BASIC_RATES_MASK      | \
+       IWL_RATE_5M_MASK          | \
+       IWL_RATE_11M_MASK)
+
+#define IWL_OFDM_BASIC_RATES_MASK   \
+       (IWL_RATE_6M_MASK         | \
+       IWL_RATE_12M_MASK         | \
+       IWL_RATE_24M_MASK)
+
+#define IWL_OFDM_RATES_MASK         \
+       (IWL_OFDM_BASIC_RATES_MASK | \
+       IWL_RATE_9M_MASK          | \
+       IWL_RATE_18M_MASK         | \
+       IWL_RATE_36M_MASK         | \
+       IWL_RATE_48M_MASK         | \
+       IWL_RATE_54M_MASK)
+
+#define IWL_BASIC_RATES_MASK         \
+       (IWL_OFDM_BASIC_RATES_MASK | \
+        IWL_CCK_BASIC_RATES_MASK)
+
+#define IWL_RATES_MASK ((1<<IWL_RATE_COUNT)-1)
+
+#define IWL_INVALID_VALUE    -1
+
+#define IWL_MIN_RSSI_VAL                 -100
+#define IWL_MAX_RSSI_VAL                    0
+
+#define IWL_LEGACY_SWITCH_ANTENNA      0
+#define IWL_LEGACY_SWITCH_SISO         1
+#define IWL_LEGACY_SWITCH_MIMO         2
+
+#define IWL_RS_GOOD_RATIO              12800
+
+#define IWL_ACTION_LIMIT               3
+#define IWL_LEGACY_FAILURE_LIMIT       160
+#define IWL_LEGACY_SUCCESS_LIMIT       480
+#define IWL_LEGACY_TABLE_COUNT         160
+
+#define IWL_NONE_LEGACY_FAILURE_LIMIT  400
+#define IWL_NONE_LEGACY_SUCCESS_LIMIT  4500
+#define IWL_NONE_LEGACY_TABLE_COUNT    1500
+
+#define IWL_RATE_SCALE_SWITCH          (10880)
+
+#define IWL_SISO_SWITCH_ANTENNA                0
+#define IWL_SISO_SWITCH_MIMO           1
+#define IWL_SISO_SWITCH_GI             2
+
+#define IWL_MIMO_SWITCH_ANTENNA_A      0
+#define IWL_MIMO_SWITCH_ANTENNA_B      1
+#define IWL_MIMO_SWITCH_GI             2
+
+#define LQ_SIZE                2
+
+extern const struct iwl_rate_info iwl_rates[IWL_RATE_COUNT];
+
+enum iwl_table_type {
+       LQ_NONE,
+       LQ_G,
+       LQ_A,
+       LQ_SISO,
+       LQ_MIMO,
+       LQ_MAX,
+};
+
+enum iwl_antenna_type {
+       ANT_NONE,
+       ANT_MAIN,
+       ANT_AUX,
+       ANT_BOTH,
+};
+
+static inline u8 iwl_get_prev_ieee_rate(u8 rate_index)
+{
+       u8 rate = iwl_rates[rate_index].prev_ieee;
+
+       if (rate == IWL_RATE_INVALID)
+               rate = rate_index;
+       return rate;
+}
+
+extern int iwl_rate_index_from_plcp(int plcp);
+
+/**
+ * iwl_fill_rs_info - Fill an output text buffer with the rate representation
+ *
+ * NOTE:  This is provided as a quick mechanism for a user to visualize
+ * the performance of the rate control alogirthm and is not meant to be
+ * parsed software.
+ */
+extern int iwl_fill_rs_info(struct ieee80211_hw *, char *buf, u8 sta_id);
+
+/**
+ * iwl_rate_scale_init - Initialize the rate scale table based on assoc info
+ *
+ * The specific througput table used is based on the type of network
+ * the associated with, including A, B, G, and G w/ TGG protection
+ */
+extern void iwl_rate_scale_init(struct ieee80211_hw *hw, s32 sta_id);
+
+/**
+ * iwl_rate_control_register - Register the rate control algorithm callbacks
+ *
+ * Since the rate control algorithm is hardware specific, there is no need
+ * or reason to place it as a stand alone module.  The driver can call
+ * iwl_rate_control_register in order to register the rate control callbacks
+ * with the mac80211 subsystem.  This should be performed prior to calling
+ * ieee80211_register_hw
+ *
+ */
+extern void iwl_rate_control_register(struct ieee80211_hw *hw);
+
+/**
+ * iwl_rate_control_unregister - Unregister the rate control callbacks
+ *
+ * This should be called after calling ieee80211_unregister_hw, but before
+ * the driver is unloaded.
+ */
+extern void iwl_rate_control_unregister(struct ieee80211_hw *hw);
+
+#endif
diff --git a/drivers/net/wireless/iwlwifi/iwl-4965.c b/drivers/net/wireless/iwlwifi/iwl-4965.c
new file mode 100644 (file)
index 0000000..ba35b3a
--- /dev/null
@@ -0,0 +1,4719 @@
+/******************************************************************************
+ *
+ * Copyright(c) 2003 - 2007 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:
+ * James P. Ketrenos <ipw2100-admin@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/version.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/wireless.h>
+#include <net/mac80211.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/delay.h>
+
+#include "iwlwifi.h"
+#include "iwl-4965.h"
+#include "iwl-helpers.h"
+
+#define IWL_DECLARE_RATE_INFO(r, s, ip, in, rp, rn, pp, np)    \
+       [IWL_RATE_##r##M_INDEX] = { IWL_RATE_##r##M_PLCP,      \
+                                   IWL_RATE_SISO_##s##M_PLCP, \
+                                   IWL_RATE_MIMO_##s##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 }
+
+/*
+ * Parameter order:
+ *   rate, ht 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 iwl_rate_info iwl_rates[IWL_RATE_COUNT] = {
+       IWL_DECLARE_RATE_INFO(1, INV, INV, 2, INV, 2, INV, 2),    /*  1mbps */
+       IWL_DECLARE_RATE_INFO(2, INV, 1, 5, 1, 5, 1, 5),          /*  2mbps */
+       IWL_DECLARE_RATE_INFO(5, INV, 2, 6, 2, 11, 2, 11),        /*5.5mbps */
+       IWL_DECLARE_RATE_INFO(11, INV, 9, 12, 9, 12, 5, 18),      /* 11mbps */
+       IWL_DECLARE_RATE_INFO(6, 6, 5, 9, 5, 11, 5, 11),        /*  6mbps */
+       IWL_DECLARE_RATE_INFO(9, 6, 6, 11, 6, 11, 5, 11),       /*  9mbps */
+       IWL_DECLARE_RATE_INFO(12, 12, 11, 18, 11, 18, 11, 18),   /* 12mbps */
+       IWL_DECLARE_RATE_INFO(18, 18, 12, 24, 12, 24, 11, 24),   /* 18mbps */
+       IWL_DECLARE_RATE_INFO(24, 24, 18, 36, 18, 36, 18, 36),   /* 24mbps */
+       IWL_DECLARE_RATE_INFO(36, 36, 24, 48, 24, 48, 24, 48),   /* 36mbps */
+       IWL_DECLARE_RATE_INFO(48, 48, 36, 54, 36, 54, 36, 54),   /* 48mbps */
+       IWL_DECLARE_RATE_INFO(54, 54, 48, INV, 48, INV, 48, INV),/* 54mbps */
+       IWL_DECLARE_RATE_INFO(60, 60, 48, INV, 48, INV, 48, INV),/* 60mbps */
+};
+
+static int is_fat_channel(__le32 rxon_flags)
+{
+       return (rxon_flags & RXON_FLG_CHANNEL_MODE_PURE_40_MSK) ||
+               (rxon_flags & RXON_FLG_CHANNEL_MODE_MIXED_MSK);
+}
+
+static u8 is_single_stream(struct iwl_priv *priv)
+{
+#ifdef CONFIG_IWLWIFI_HT
+       if (!priv->is_ht_enabled || !priv->current_assoc_ht.is_ht ||
+           (priv->active_rate_ht[1] == 0) ||
+           (priv->ps_mode == IWL_MIMO_PS_STATIC))
+               return 1;
+#else
+       return 1;
+#endif /*CONFIG_IWLWIFI_HT */
+       return 0;
+}
+
+/*
+ * Determine how many receiver/antenna chains to use.
+ * More provides better reception via diversity.  Fewer saves power.
+ * MIMO (dual stream) requires at least 2, but works better with 3.
+ * This does not determine *which* chains to use, just how many.
+ */
+static int iwl4965_get_rx_chain_counter(struct iwl_priv *priv,
+                                       u8 *idle_state, u8 *rx_state)
+{
+       u8 is_single = is_single_stream(priv);
+       u8 is_cam = test_bit(STATUS_POWER_PMI, &priv->status) ? 0 : 1;
+
+       /* # of Rx chains to use when expecting MIMO. */
+       if (is_single || (!is_cam && (priv->ps_mode == IWL_MIMO_PS_STATIC)))
+               *rx_state = 2;
+       else
+               *rx_state = 3;
+
+       /* # Rx chains when idling and maybe trying to save power */
+       switch (priv->ps_mode) {
+       case IWL_MIMO_PS_STATIC:
+       case IWL_MIMO_PS_DYNAMIC:
+               *idle_state = (is_cam) ? 2 : 1;
+               break;
+       case IWL_MIMO_PS_NONE:
+               *idle_state = (is_cam) ? *rx_state : 1;
+               break;
+       default:
+               *idle_state = 1;
+               break;
+       }
+
+       return 0;
+}
+
+int iwl_hw_rxq_stop(struct iwl_priv *priv)
+{
+       int rc;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       rc = iwl_grab_restricted_access(priv);
+       if (rc) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return rc;
+       }
+
+       /* stop HW */
+       iwl_write_restricted(priv, FH_MEM_RCSR_CHNL0_CONFIG_REG, 0);
+       rc = iwl_poll_restricted_bit(priv, FH_MEM_RSSR_RX_STATUS_REG,
+                                    (1 << 24), 1000);
+       if (rc < 0)
+               IWL_ERROR("Can't stop Rx DMA.\n");
+
+       iwl_release_restricted_access(priv);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return 0;
+}
+
+u8 iwl_hw_find_station(struct iwl_priv *priv, const u8 *addr)
+{
+       int i;
+       int start = 0;
+       int ret = IWL_INVALID_STATION;
+       unsigned long flags;
+
+       if ((priv->iw_mode == IEEE80211_IF_TYPE_IBSS) ||
+           (priv->iw_mode == IEEE80211_IF_TYPE_AP))
+               start = IWL_STA_ID;
+
+       if (is_broadcast_ether_addr(addr))
+               return IWL4965_BROADCAST_ID;
+
+       spin_lock_irqsave(&priv->sta_lock, flags);
+       for (i = start; i < priv->hw_setting.max_stations; i++)
+               if ((priv->stations[i].used) &&
+                   (!compare_ether_addr
+                    (priv->stations[i].sta.sta.addr, addr))) {
+                       ret = i;
+                       goto out;
+               }
+
+       IWL_DEBUG_ASSOC("can not find STA " MAC_FMT " total %d\n",
+                       MAC_ARG(addr), priv->num_stations);
+
+ out:
+       spin_unlock_irqrestore(&priv->sta_lock, flags);
+       return ret;
+}
+
+static int iwl4965_nic_set_pwr_src(struct iwl_priv *priv, int pwr_max)
+{
+       int rc = 0;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       rc = iwl_grab_restricted_access(priv);
+       if (rc) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return rc;
+       }
+
+       if (!pwr_max) {
+               u32 val;
+
+               rc = pci_read_config_dword(priv->pci_dev, PCI_POWER_SOURCE,
+                                          &val);
+
+               if (val & PCI_CFG_PMC_PME_FROM_D3COLD_SUPPORT)
+                       iwl_set_bits_mask_restricted_reg(
+                               priv, APMG_PS_CTRL_REG,
+                               APMG_PS_CTRL_VAL_PWR_SRC_VAUX,
+                               ~APMG_PS_CTRL_MSK_PWR_SRC);
+       } else
+               iwl_set_bits_mask_restricted_reg(
+                       priv, APMG_PS_CTRL_REG,
+                       APMG_PS_CTRL_VAL_PWR_SRC_VMAIN,
+                       ~APMG_PS_CTRL_MSK_PWR_SRC);
+
+       iwl_release_restricted_access(priv);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return rc;
+}
+
+static int iwl4965_rx_init(struct iwl_priv *priv, struct iwl_rx_queue *rxq)
+{
+       int rc;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       rc = iwl_grab_restricted_access(priv);
+       if (rc) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return rc;
+       }
+
+       /* stop HW */
+       iwl_write_restricted(priv, FH_MEM_RCSR_CHNL0_CONFIG_REG, 0);
+
+       iwl_write_restricted(priv, FH_RSCSR_CHNL0_RBDCB_WPTR_REG, 0);
+       iwl_write_restricted(priv, FH_RSCSR_CHNL0_RBDCB_BASE_REG,
+                            rxq->dma_addr >> 8);
+
+       iwl_write_restricted(priv, FH_RSCSR_CHNL0_STTS_WPTR_REG,
+                            (priv->hw_setting.shared_phys +
+                             offsetof(struct iwl_shared, val0)) >> 4);
+
+       iwl_write_restricted(priv, FH_MEM_RCSR_CHNL0_CONFIG_REG,
+                            FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL |
+                            FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL |
+                            IWL_FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K |
+                            /*0x10 << 4 | */
+                            (RX_QUEUE_SIZE_LOG <<
+                             FH_RCSR_RX_CONFIG_RBDCB_SIZE_BITSHIFT));
+
+       /*
+        * iwl_write32(priv,CSR_INT_COAL_REG,0);
+        */
+
+       iwl_release_restricted_access(priv);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return 0;
+}
+
+static int iwl4965_kw_init(struct iwl_priv *priv)
+{
+       unsigned long flags;
+       int rc;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       rc = iwl_grab_restricted_access(priv);
+       if (rc)
+               goto out;
+
+       iwl_write_restricted(priv, IWL_FH_KW_MEM_ADDR_REG,
+                            priv->kw.dma_addr >> 4);
+       iwl_release_restricted_access(priv);
+out:
+       spin_unlock_irqrestore(&priv->lock, flags);
+       return rc;
+}
+
+static int iwl4965_kw_alloc(struct iwl_priv *priv)
+{
+       struct pci_dev *dev = priv->pci_dev;
+       struct iwl_kw *kw = &priv->kw;
+
+       kw->size = IWL4965_KW_SIZE;     /* TBW need set somewhere else */
+       kw->v_addr = pci_alloc_consistent(dev, kw->size, &kw->dma_addr);
+       if (!kw->v_addr)
+               return -ENOMEM;
+
+       return 0;
+}
+
+#define CHECK_AND_PRINT(x) ((eeprom_ch->flags & EEPROM_CHANNEL_##x) \
+                           ? # x " " : "")
+
+int iwl4965_set_fat_chan_info(struct iwl_priv *priv, int phymode, u16 channel,
+                             const struct iwl_eeprom_channel *eeprom_ch,
+                             u8 fat_extension_channel)
+{
+       struct iwl_channel_info *ch_info;
+
+       ch_info = (struct iwl_channel_info *)
+                       iwl_get_channel_info(priv, phymode, channel);
+
+       if (!is_channel_valid(ch_info))
+               return -1;
+
+       IWL_DEBUG_INFO("FAT Ch. %d [%sGHz] %s%s%s%s%s%s(0x%02x"
+                       " %ddBm): Ad-Hoc %ssupported\n",
+                       ch_info->channel,
+                       is_channel_a_band(ch_info) ?
+                       "5.2" : "2.4",
+                       CHECK_AND_PRINT(IBSS),
+                       CHECK_AND_PRINT(ACTIVE),
+                       CHECK_AND_PRINT(RADAR),
+                       CHECK_AND_PRINT(WIDE),
+                       CHECK_AND_PRINT(NARROW),
+                       CHECK_AND_PRINT(DFS),
+                       eeprom_ch->flags,
+                       eeprom_ch->max_power_avg,
+                       ((eeprom_ch->flags & EEPROM_CHANNEL_IBSS)
+                        && !(eeprom_ch->flags & EEPROM_CHANNEL_RADAR)) ?
+                       "" : "not ");
+
+       ch_info->fat_eeprom = *eeprom_ch;
+       ch_info->fat_max_power_avg = eeprom_ch->max_power_avg;
+       ch_info->fat_curr_txpow = eeprom_ch->max_power_avg;
+       ch_info->fat_min_power = 0;
+       ch_info->fat_scan_power = eeprom_ch->max_power_avg;
+       ch_info->fat_flags = eeprom_ch->flags;
+       ch_info->fat_extension_channel = fat_extension_channel;
+
+       return 0;
+}
+
+static void iwl4965_kw_free(struct iwl_priv *priv)
+{
+       struct pci_dev *dev = priv->pci_dev;
+       struct iwl_kw *kw = &priv->kw;
+
+       if (kw->v_addr) {
+               pci_free_consistent(dev, kw->size, kw->v_addr, kw->dma_addr);
+               memset(kw, 0, sizeof(*kw));
+       }
+}
+
+/**
+ * iwl4965_txq_ctx_reset - Reset TX queue context
+ * Destroys all DMA structures and initialise them again
+ *
+ * @param priv
+ * @return error code
+ */
+static int iwl4965_txq_ctx_reset(struct iwl_priv *priv)
+{
+       int rc = 0;
+       int txq_id, slots_num;
+       unsigned long flags;
+
+       iwl4965_kw_free(priv);
+
+       iwl_hw_txq_ctx_free(priv);
+
+       /* Tx CMD queue */
+       rc = iwl4965_kw_alloc(priv);
+       if (rc) {
+               IWL_ERROR("Keep Warm allocation failed");
+               goto error_kw;
+       }
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       rc = iwl_grab_restricted_access(priv);
+       if (unlikely(rc)) {
+               IWL_ERROR("TX reset failed");
+               spin_unlock_irqrestore(&priv->lock, flags);
+               goto error_reset;
+       }
+
+       iwl_write_restricted_reg(priv, SCD_TXFACT, 0);
+       iwl_release_restricted_access(priv);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       rc = iwl4965_kw_init(priv);
+       if (rc) {
+               IWL_ERROR("kw_init failed\n");
+               goto error_reset;
+       }
+
+       /* Tx queue(s) */
+       for (txq_id = 0; txq_id < priv->hw_setting.max_txq_num; txq_id++) {
+               slots_num = (txq_id == IWL_CMD_QUEUE_NUM) ?
+                                       TFD_CMD_SLOTS : TFD_TX_CMD_SLOTS;
+               rc = iwl_tx_queue_init(priv, &priv->txq[txq_id], slots_num,
+                                      txq_id);
+               if (rc) {
+                       IWL_ERROR("Tx %d queue init failed\n", txq_id);
+                       goto error;
+               }
+       }
+
+       return rc;
+
+ error:
+       iwl_hw_txq_ctx_free(priv);
+ error_reset:
+       iwl4965_kw_free(priv);
+ error_kw:
+       return rc;
+}
+
+int iwl_hw_nic_init(struct iwl_priv *priv)
+{
+       int rc;
+       unsigned long flags;
+       struct iwl_rx_queue *rxq = &priv->rxq;
+       u8 rev_id;
+       u32 val;
+       u8 val_link;
+
+       iwl_power_init_handle(priv);
+
+       /* nic_init */
+       spin_lock_irqsave(&priv->lock, flags);
+
+       iwl_set_bit(priv, CSR_GIO_CHICKEN_BITS,
+                   CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER);
+
+       iwl_set_bit(priv, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
+       rc = iwl_poll_bit(priv, CSR_GP_CNTRL,
+                         CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY,
+                         CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, 25000);
+       if (rc < 0) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               IWL_DEBUG_INFO("Failed to init the card\n");
+               return rc;
+       }
+
+       rc = iwl_grab_restricted_access(priv);
+       if (rc) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return rc;
+       }
+
+       iwl_read_restricted_reg(priv, APMG_CLK_CTRL_REG);
+
+       iwl_write_restricted_reg(priv, APMG_CLK_CTRL_REG,
+                                APMG_CLK_VAL_DMA_CLK_RQT |
+                                APMG_CLK_VAL_BSM_CLK_RQT);
+       iwl_read_restricted_reg(priv, APMG_CLK_CTRL_REG);
+
+       udelay(20);
+
+       iwl_set_bits_restricted_reg(priv, APMG_PCIDEV_STT_REG,
+                                   APMG_PCIDEV_STT_VAL_L1_ACT_DIS);
+
+       iwl_release_restricted_access(priv);
+       iwl_write32(priv, CSR_INT_COALESCING, 512 / 32);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       /* Determine HW type */
+       rc = pci_read_config_byte(priv->pci_dev, PCI_REVISION_ID, &rev_id);
+       if (rc)
+               return rc;
+
+       IWL_DEBUG_INFO("HW Revision ID = 0x%X\n", rev_id);
+
+       iwl4965_nic_set_pwr_src(priv, 1);
+       spin_lock_irqsave(&priv->lock, flags);
+
+       if ((rev_id & 0x80) == 0x80 && (rev_id & 0x7f) < 8) {
+               pci_read_config_dword(priv->pci_dev, PCI_REG_WUM8, &val);
+               /* Enable No Snoop field */
+               pci_write_config_dword(priv->pci_dev, PCI_REG_WUM8,
+                                      val & ~(1 << 11));
+       }
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       /* Read the EEPROM */
+       rc = iwl_eeprom_init(priv);
+       if (rc)
+               return rc;
+
+       if (priv->eeprom.calib_version < EEPROM_TX_POWER_VERSION_NEW) {
+               IWL_ERROR("Older EEPROM detected!  Aborting.\n");
+               return -EINVAL;
+       }
+
+       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);
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       /* set CSR_HW_CONFIG_REG for uCode use */
+
+       iwl_set_bit(priv, CSR_SW_VER, CSR_HW_IF_CONFIG_REG_BIT_KEDRON_R |
+                   CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI |
+                   CSR_HW_IF_CONFIG_REG_BIT_MAC_SI);
+
+       rc = iwl_grab_restricted_access(priv);
+       if (rc < 0) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               IWL_DEBUG_INFO("Failed to init the card\n");
+               return rc;
+       }
+
+       iwl_read_restricted_reg(priv, APMG_PS_CTRL_REG);
+       iwl_set_bits_restricted_reg(priv, APMG_PS_CTRL_REG,
+                                   APMG_PS_CTRL_VAL_RESET_REQ);
+       udelay(5);
+       iwl_clear_bits_restricted_reg(priv, APMG_PS_CTRL_REG,
+                                     APMG_PS_CTRL_VAL_RESET_REQ);
+
+       iwl_release_restricted_access(priv);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       iwl_hw_card_show_info(priv);
+
+       /* end nic_init */
+
+       /* Allocate the RX queue, or reset if it is already allocated */
+       if (!rxq->bd) {
+               rc = iwl_rx_queue_alloc(priv);
+               if (rc) {
+                       IWL_ERROR("Unable to initialize Rx queue\n");
+                       return -ENOMEM;
+               }
+       } else
+               iwl_rx_queue_reset(priv, rxq);
+
+       iwl_rx_replenish(priv);
+
+       iwl4965_rx_init(priv, rxq);
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       rxq->need_update = 1;
+       iwl_rx_queue_update_write_ptr(priv, rxq);
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+       rc = iwl4965_txq_ctx_reset(priv);
+       if (rc)
+               return rc;
+
+       if (priv->eeprom.sku_cap & EEPROM_SKU_CAP_SW_RF_KILL_ENABLE)
+               IWL_DEBUG_RF_KILL("SW RF KILL supported in EEPROM.\n");
+
+       if (priv->eeprom.sku_cap & EEPROM_SKU_CAP_HW_RF_KILL_ENABLE)
+               IWL_DEBUG_RF_KILL("HW RF KILL supported in EEPROM.\n");
+
+       set_bit(STATUS_INIT, &priv->status);
+
+       return 0;
+}
+
+int iwl_hw_nic_stop_master(struct iwl_priv *priv)
+{
+       int rc = 0;
+       u32 reg_val;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       /* set stop master bit */
+       iwl_set_bit(priv, CSR_RESET, CSR_RESET_REG_FLAG_STOP_MASTER);
+
+       reg_val = iwl_read32(priv, CSR_GP_CNTRL);
+
+       if (CSR_GP_CNTRL_REG_FLAG_MAC_POWER_SAVE ==
+           (reg_val & CSR_GP_CNTRL_REG_MSK_POWER_SAVE_TYPE))
+               IWL_DEBUG_INFO("Card in power save, master is already "
+                              "stopped\n");
+       else {
+               rc = iwl_poll_bit(priv, CSR_RESET,
+                                 CSR_RESET_REG_FLAG_MASTER_DISABLED,
+                                 CSR_RESET_REG_FLAG_MASTER_DISABLED, 100);
+               if (rc < 0) {
+                       spin_unlock_irqrestore(&priv->lock, flags);
+                       return rc;
+               }
+       }
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+       IWL_DEBUG_INFO("stop master\n");
+
+       return rc;
+}
+
+void iwl_hw_txq_ctx_stop(struct iwl_priv *priv)
+{
+
+       int txq_id;
+       unsigned long flags;
+
+       /* reset TFD queues */
+       for (txq_id = 0; txq_id < priv->hw_setting.max_txq_num; txq_id++) {
+               spin_lock_irqsave(&priv->lock, flags);
+               if (iwl_grab_restricted_access(priv)) {
+                       spin_unlock_irqrestore(&priv->lock, flags);
+                       continue;
+               }
+
+               iwl_write_restricted(priv,
+                                    IWL_FH_TCSR_CHNL_TX_CONFIG_REG(txq_id),
+                                    0x0);
+               iwl_poll_restricted_bit(priv, IWL_FH_TSSR_TX_STATUS_REG,
+                                       IWL_FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE
+                                       (txq_id), 200);
+               iwl_release_restricted_access(priv);
+               spin_unlock_irqrestore(&priv->lock, flags);
+       }
+
+       iwl_hw_txq_ctx_free(priv);
+}
+
+int iwl_hw_nic_reset(struct iwl_priv *priv)
+{
+       int rc = 0;
+       unsigned long flags;
+
+       iwl_hw_nic_stop_master(priv);
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       iwl_set_bit(priv, CSR_RESET, CSR_RESET_REG_FLAG_SW_RESET);
+
+       udelay(10);
+
+       iwl_set_bit(priv, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
+       rc = iwl_poll_bit(priv, CSR_RESET,
+                         CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY,
+                         CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, 25);
+
+       udelay(10);
+
+       rc = iwl_grab_restricted_access(priv);
+       if (!rc) {
+               iwl_write_restricted_reg(priv, APMG_CLK_EN_REG,
+                                        APMG_CLK_VAL_DMA_CLK_RQT |
+                                        APMG_CLK_VAL_BSM_CLK_RQT);
+
+               udelay(10);
+
+               iwl_set_bits_restricted_reg(priv, APMG_PCIDEV_STT_REG,
+                               APMG_PCIDEV_STT_VAL_L1_ACT_DIS);
+
+               iwl_release_restricted_access(priv);
+       }
+
+       clear_bit(STATUS_HCMD_ACTIVE, &priv->status);
+       wake_up_interruptible(&priv->wait_command_queue);
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return rc;
+
+}
+
+#define REG_RECALIB_PERIOD (60)
+
+/**
+ * iwl4965_bg_statistics_periodic - Timer callback to queue statistics
+ *
+ * This callback is provided in order to queue the statistics_work
+ * in work_queue context (v. softirq)
+ *
+ * This timer function is continually reset to execute within
+ * REG_RECALIB_PERIOD seconds since the last STATISTICS_NOTIFICATION
+ * was received.  We need to ensure we receive the statistics in order
+ * to update the temperature used for calibrating the TXPOWER.  However,
+ * we can't send the statistics command from softirq context (which
+ * is the context which timers run at) so we have to queue off the
+ * statistics_work to actually send the command to the hardware.
+ */
+static void iwl4965_bg_statistics_periodic(unsigned long data)
+{
+       struct iwl_priv *priv = (struct iwl_priv *)data;
+
+       queue_work(priv->workqueue, &priv->statistics_work);
+}
+
+/**
+ * iwl4965_bg_statistics_work - Send the statistics request to the hardware.
+ *
+ * This is queued by iwl_bg_statistics_periodic.
+ */
+static void iwl4965_bg_statistics_work(struct work_struct *work)
+{
+       struct iwl_priv *priv = container_of(work, struct iwl_priv,
+                                            statistics_work);
+
+       if (test_bit(STATUS_EXIT_PENDING, &priv->status))
+               return;
+
+       mutex_lock(&priv->mutex);
+       iwl_send_statistics_request(priv);
+       mutex_unlock(&priv->mutex);
+}
+
+#define CT_LIMIT_CONST         259
+#define TM_CT_KILL_THRESHOLD   110
+
+void iwl4965_rf_kill_ct_config(struct iwl_priv *priv)
+{
+       struct iwl_ct_kill_config cmd;
+       u32 R1, R2, R3;
+       u32 temp_th;
+       u32 crit_temperature;
+       unsigned long flags;
+       int rc = 0;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       iwl_write32(priv, CSR_UCODE_DRV_GP1_CLR,
+                   CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       if (priv->statistics.flag & STATISTICS_REPLY_FLG_FAT_MODE_MSK) {
+               R1 = (s32)le32_to_cpu(priv->card_alive_init.therm_r1[1]);
+               R2 = (s32)le32_to_cpu(priv->card_alive_init.therm_r2[1]);
+               R3 = (s32)le32_to_cpu(priv->card_alive_init.therm_r3[1]);
+       } else {
+               R1 = (s32)le32_to_cpu(priv->card_alive_init.therm_r1[0]);
+               R2 = (s32)le32_to_cpu(priv->card_alive_init.therm_r2[0]);
+               R3 = (s32)le32_to_cpu(priv->card_alive_init.therm_r3[0]);
+       }
+
+       temp_th = CELSIUS_TO_KELVIN(TM_CT_KILL_THRESHOLD);
+
+       crit_temperature = ((temp_th * (R3-R1))/CT_LIMIT_CONST) + R2;
+       cmd.critical_temperature_R =  cpu_to_le32(crit_temperature);
+       rc = iwl_send_cmd_pdu(priv,
+                             REPLY_CT_KILL_CONFIG_CMD, sizeof(cmd), &cmd);
+       if (rc)
+               IWL_ERROR("REPLY_CT_KILL_CONFIG_CMD failed\n");
+       else
+               IWL_DEBUG_INFO("REPLY_CT_KILL_CONFIG_CMD succeeded\n");
+}
+
+#ifdef CONFIG_IWLWIFI_SENSITIVITY
+
+/* "false alarms" are signals that our DSP tries to lock onto,
+ *   but then determines that they are either noise, or transmissions
+ *   from a distant wireless network (also "noise", really) that get
+ *   "stepped on" by stronger transmissions within our own network.
+ * This algorithm attempts to set a sensitivity level that is high
+ *   enough to receive all of our own network traffic, but not so
+ *   high that our DSP gets too busy trying to lock onto non-network
+ *   activity/noise. */
+static int iwl4965_sens_energy_cck(struct iwl_priv *priv,
+                                  u32 norm_fa,
+                                  u32 rx_enable_time,
+                                  struct statistics_general_data *rx_info)
+{
+       u32 max_nrg_cck = 0;
+       int i = 0;
+       u8 max_silence_rssi = 0;
+       u32 silence_ref = 0;
+       u8 silence_rssi_a = 0;
+       u8 silence_rssi_b = 0;
+       u8 silence_rssi_c = 0;
+       u32 val;
+
+       /* "false_alarms" values below are cross-multiplications to assess the
+        *   numbers of false alarms within the measured period of actual Rx
+        *   (Rx is off when we're txing), vs the min/max expected false alarms
+        *   (some should be expected if rx is sensitive enough) in a
+        *   hypothetical listening period of 200 time units (TU), 204.8 msec:
+        *
+        * MIN_FA/fixed-time < false_alarms/actual-rx-time < MAX_FA/beacon-time
+        *
+        * */
+       u32 false_alarms = norm_fa * 200 * 1024;
+       u32 max_false_alarms = MAX_FA_CCK * rx_enable_time;
+       u32 min_false_alarms = MIN_FA_CCK * rx_enable_time;
+       struct iwl_sensitivity_data *data = NULL;
+
+       data = &(priv->sensitivity_data);
+
+       data->nrg_auto_corr_silence_diff = 0;
+
+       /* Find max silence rssi among all 3 receivers.
+        * This is background noise, which may include transmissions from other
+        *    networks, measured during silence before our network's beacon */
+       silence_rssi_a = (u8)((rx_info->beacon_silence_rssi_a &
+                           ALL_BAND_FILTER)>>8);
+       silence_rssi_b = (u8)((rx_info->beacon_silence_rssi_b &
+                           ALL_BAND_FILTER)>>8);
+       silence_rssi_c = (u8)((rx_info->beacon_silence_rssi_c &
+                           ALL_BAND_FILTER)>>8);
+
+       val = max(silence_rssi_b, silence_rssi_c);
+       max_silence_rssi = max(silence_rssi_a, (u8) val);
+
+       /* Store silence rssi in 20-beacon history table */
+       data->nrg_silence_rssi[data->nrg_silence_idx] = max_silence_rssi;
+       data->nrg_silence_idx++;
+       if (data->nrg_silence_idx >= NRG_NUM_PREV_STAT_L)
+               data->nrg_silence_idx = 0;
+
+       /* Find max silence rssi across 20 beacon history */
+       for (i = 0; i < NRG_NUM_PREV_STAT_L; i++) {
+               val = data->nrg_silence_rssi[i];
+               silence_ref = max(silence_ref, val);
+       }
+       IWL_DEBUG_CALIB("silence a %u, b %u, c %u, 20-bcn max %u\n",
+                       silence_rssi_a, silence_rssi_b, silence_rssi_c,
+                       silence_ref);
+
+       /* Find max rx energy (min value!) among all 3 receivers,
+        *   measured during beacon frame.
+        * Save it in 10-beacon history table. */
+       i = data->nrg_energy_idx;
+       val = min(rx_info->beacon_energy_b, rx_info->beacon_energy_c);
+       data->nrg_value[i] = min(rx_info->beacon_energy_a, val);
+
+       data->nrg_energy_idx++;
+       if (data->nrg_energy_idx >= 10)
+               data->nrg_energy_idx = 0;
+
+       /* Find min rx energy (max value) across 10 beacon history.
+        * This is the minimum signal level that we want to receive well.
+        * Add backoff (margin so we don't miss slightly lower energy frames).
+        * This establishes an upper bound (min value) for energy threshold. */
+       max_nrg_cck = data->nrg_value[0];
+       for (i = 1; i < 10; i++)
+               max_nrg_cck = (u32) max(max_nrg_cck, (data->nrg_value[i]));
+       max_nrg_cck += 6;
+
+       IWL_DEBUG_CALIB("rx energy a %u, b %u, c %u, 10-bcn max/min %u\n",
+                       rx_info->beacon_energy_a, rx_info->beacon_energy_b,
+                       rx_info->beacon_energy_c, max_nrg_cck - 6);
+
+       /* Count number of consecutive beacons with fewer-than-desired
+        *   false alarms. */
+       if (false_alarms < min_false_alarms)
+               data->num_in_cck_no_fa++;
+       else
+               data->num_in_cck_no_fa = 0;
+       IWL_DEBUG_CALIB("consecutive bcns with few false alarms = %u\n",
+                       data->num_in_cck_no_fa);
+
+       /* If we got too many false alarms this time, reduce sensitivity */
+       if (false_alarms > max_false_alarms) {
+               IWL_DEBUG_CALIB("norm FA %u > max FA %u\n",
+                            false_alarms, max_false_alarms);
+               IWL_DEBUG_CALIB("... reducing sensitivity\n");
+               data->nrg_curr_state = IWL_FA_TOO_MANY;
+
+               if (data->auto_corr_cck > AUTO_CORR_MAX_TH_CCK) {
+                       /* Store for "fewer than desired" on later beacon */
+                       data->nrg_silence_ref = silence_ref;
+
+                       /* increase energy threshold (reduce nrg value)
+                        *   to decrease sensitivity */
+                       if (data->nrg_th_cck > (NRG_MAX_CCK + NRG_STEP_CCK))
+                               data->nrg_th_cck = data->nrg_th_cck
+                                                        - NRG_STEP_CCK;
+               }
+
+               /* increase auto_corr values to decrease sensitivity */
+               if (data->auto_corr_cck < AUTO_CORR_MAX_TH_CCK)
+                       data->auto_corr_cck = AUTO_CORR_MAX_TH_CCK + 1;
+               else {
+                       val = data->auto_corr_cck + AUTO_CORR_STEP_CCK;
+                       data->auto_corr_cck = min((u32)AUTO_CORR_MAX_CCK, val);
+               }
+               val = data->auto_corr_cck_mrc + AUTO_CORR_STEP_CCK;
+               data->auto_corr_cck_mrc = min((u32)AUTO_CORR_MAX_CCK_MRC, val);
+
+       /* Else if we got fewer than desired, increase sensitivity */
+       } else if (false_alarms < min_false_alarms) {
+               data->nrg_curr_state = IWL_FA_TOO_FEW;
+
+               /* Compare silence level with silence level for most recent
+                *   healthy number or too many false alarms */
+               data->nrg_auto_corr_silence_diff = (s32)data->nrg_silence_ref -
+                                                  (s32)silence_ref;
+
+               IWL_DEBUG_CALIB("norm FA %u < min FA %u, silence diff %d\n",
+                        false_alarms, min_false_alarms,
+                        data->nrg_auto_corr_silence_diff);
+
+               /* Increase value to increase sensitivity, but only if:
+                * 1a) previous beacon did *not* have *too many* false alarms
+                * 1b) AND there's a significant difference in Rx levels
+                *      from a previous beacon with too many, or healthy # FAs
+                * OR 2) We've seen a lot of beacons (100) with too few
+                *       false alarms */
+               if ((data->nrg_prev_state != IWL_FA_TOO_MANY) &&
+                       ((data->nrg_auto_corr_silence_diff > NRG_DIFF) ||
+                       (data->num_in_cck_no_fa > MAX_NUMBER_CCK_NO_FA))) {
+
+                       IWL_DEBUG_CALIB("... increasing sensitivity\n");
+                       /* Increase nrg value to increase sensitivity */
+                       val = data->nrg_th_cck + NRG_STEP_CCK;
+                       data->nrg_th_cck = min((u32)NRG_MIN_CCK, val);
+
+                       /* Decrease auto_corr values to increase sensitivity */
+                       val = data->auto_corr_cck - AUTO_CORR_STEP_CCK;
+                       data->auto_corr_cck = max((u32)AUTO_CORR_MIN_CCK, val);
+
+                       val = data->auto_corr_cck_mrc - AUTO_CORR_STEP_CCK;
+                       data->auto_corr_cck_mrc =
+                                        max((u32)AUTO_CORR_MIN_CCK_MRC, val);
+
+               } else
+                       IWL_DEBUG_CALIB("... but not changing sensitivity\n");
+
+       /* Else we got a healthy number of false alarms, keep status quo */
+       } else {
+               IWL_DEBUG_CALIB(" FA in safe zone\n");
+               data->nrg_curr_state = IWL_FA_GOOD_RANGE;
+
+               /* Store for use in "fewer than desired" with later beacon */
+               data->nrg_silence_ref = silence_ref;
+
+               /* If previous beacon had too many false alarms,
+                *   give it some extra margin by reducing sensitivity again
+                *   (but don't go below measured energy of desired Rx) */
+               if (IWL_FA_TOO_MANY == data->nrg_prev_state) {
+                       IWL_DEBUG_CALIB("... increasing margin\n");
+                       data->nrg_th_cck -= NRG_MARGIN;
+               }
+       }
+
+       /* Make sure the energy threshold does not go above the measured
+        * energy of the desired Rx signals (reduced by backoff margin),
+        * or else we might start missing Rx frames.
+        * Lower value is higher energy, so we use max()!
+        */
+       data->nrg_th_cck = max(max_nrg_cck, data->nrg_th_cck);
+       IWL_DEBUG_CALIB("new nrg_th_cck %u\n", data->nrg_th_cck);
+
+       data->nrg_prev_state = data->nrg_curr_state;
+
+       return 0;
+}
+
+
+static int iwl4965_sens_auto_corr_ofdm(struct iwl_priv *priv,
+                                      u32 norm_fa,
+                                      u32 rx_enable_time)
+{
+       u32 val;
+       u32 false_alarms = norm_fa * 200 * 1024;
+       u32 max_false_alarms = MAX_FA_OFDM * rx_enable_time;
+       u32 min_false_alarms = MIN_FA_OFDM * rx_enable_time;
+       struct iwl_sensitivity_data *data = NULL;
+
+       data = &(priv->sensitivity_data);
+
+       /* If we got too many false alarms this time, reduce sensitivity */
+       if (false_alarms > max_false_alarms) {
+
+               IWL_DEBUG_CALIB("norm FA %u > max FA %u)\n",
+                            false_alarms, max_false_alarms);
+
+               val = data->auto_corr_ofdm + AUTO_CORR_STEP_OFDM;
+               data->auto_corr_ofdm =
+                               min((u32)AUTO_CORR_MAX_OFDM, val);
+
+               val = data->auto_corr_ofdm_mrc + AUTO_CORR_STEP_OFDM;
+               data->auto_corr_ofdm_mrc =
+                               min((u32)AUTO_CORR_MAX_OFDM_MRC, val);
+
+               val = data->auto_corr_ofdm_x1 + AUTO_CORR_STEP_OFDM;
+               data->auto_corr_ofdm_x1 =
+                               min((u32)AUTO_CORR_MAX_OFDM_X1, val);
+
+               val = data->auto_corr_ofdm_mrc_x1 + AUTO_CORR_STEP_OFDM;
+               data->auto_corr_ofdm_mrc_x1 =
+                               min((u32)AUTO_CORR_MAX_OFDM_MRC_X1, val);
+       }
+
+       /* Else if we got fewer than desired, increase sensitivity */
+       else if (false_alarms < min_false_alarms) {
+
+               IWL_DEBUG_CALIB("norm FA %u < min FA %u\n",
+                            false_alarms, min_false_alarms);
+
+               val = data->auto_corr_ofdm - AUTO_CORR_STEP_OFDM;
+               data->auto_corr_ofdm =
+                               max((u32)AUTO_CORR_MIN_OFDM, val);
+
+               val = data->auto_corr_ofdm_mrc - AUTO_CORR_STEP_OFDM;
+               data->auto_corr_ofdm_mrc =
+                               max((u32)AUTO_CORR_MIN_OFDM_MRC, val);
+
+               val = data->auto_corr_ofdm_x1 - AUTO_CORR_STEP_OFDM;
+               data->auto_corr_ofdm_x1 =
+                               max((u32)AUTO_CORR_MIN_OFDM_X1, val);
+
+               val = data->auto_corr_ofdm_mrc_x1 - AUTO_CORR_STEP_OFDM;
+               data->auto_corr_ofdm_mrc_x1 =
+                               max((u32)AUTO_CORR_MIN_OFDM_MRC_X1, val);
+       }
+
+       else
+               IWL_DEBUG_CALIB("min FA %u < norm FA %u < max FA %u OK\n",
+                        min_false_alarms, false_alarms, max_false_alarms);
+
+       return 0;
+}
+
+static int iwl_sensitivity_callback(struct iwl_priv *priv,
+                                   struct iwl_cmd *cmd, struct sk_buff *skb)
+{
+       /* We didn't cache the SKB; let the caller free it */
+       return 1;
+}
+
+/* Prepare a SENSITIVITY_CMD, send to uCode if values have changed */
+static int iwl4965_sensitivity_write(struct iwl_priv *priv, u8 flags)
+{
+       int rc = 0;
+       struct iwl_sensitivity_cmd cmd ;
+       struct iwl_sensitivity_data *data = NULL;
+       struct iwl_host_cmd cmd_out = {
+               .id = SENSITIVITY_CMD,
+               .len = sizeof(struct iwl_sensitivity_cmd),
+               .meta.flags = flags,
+               .data = &cmd,
+       };
+
+       data = &(priv->sensitivity_data);
+
+       memset(&cmd, 0, sizeof(cmd));
+
+       cmd.table[HD_AUTO_CORR32_X4_TH_ADD_MIN_INDEX] =
+                               cpu_to_le16((u16)data->auto_corr_ofdm);
+       cmd.table[HD_AUTO_CORR32_X4_TH_ADD_MIN_MRC_INDEX] =
+                               cpu_to_le16((u16)data->auto_corr_ofdm_mrc);
+       cmd.table[HD_AUTO_CORR32_X1_TH_ADD_MIN_INDEX] =
+                               cpu_to_le16((u16)data->auto_corr_ofdm_x1);
+       cmd.table[HD_AUTO_CORR32_X1_TH_ADD_MIN_MRC_INDEX] =
+                               cpu_to_le16((u16)data->auto_corr_ofdm_mrc_x1);
+
+       cmd.table[HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX] =
+                               cpu_to_le16((u16)data->auto_corr_cck);
+       cmd.table[HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_INDEX] =
+                               cpu_to_le16((u16)data->auto_corr_cck_mrc);
+
+       cmd.table[HD_MIN_ENERGY_CCK_DET_INDEX] =
+                               cpu_to_le16((u16)data->nrg_th_cck);
+       cmd.table[HD_MIN_ENERGY_OFDM_DET_INDEX] =
+                               cpu_to_le16((u16)data->nrg_th_ofdm);
+
+       cmd.table[HD_BARKER_CORR_TH_ADD_MIN_INDEX] =
+                               __constant_cpu_to_le16(190);
+       cmd.table[HD_BARKER_CORR_TH_ADD_MIN_MRC_INDEX] =
+                               __constant_cpu_to_le16(390);
+       cmd.table[HD_OFDM_ENERGY_TH_IN_INDEX] =
+                               __constant_cpu_to_le16(62);
+
+       IWL_DEBUG_CALIB("ofdm: ac %u mrc %u x1 %u mrc_x1 %u thresh %u\n",
+                       data->auto_corr_ofdm, data->auto_corr_ofdm_mrc,
+                       data->auto_corr_ofdm_x1, data->auto_corr_ofdm_mrc_x1,
+                       data->nrg_th_ofdm);
+
+       IWL_DEBUG_CALIB("cck: ac %u mrc %u thresh %u\n",
+                       data->auto_corr_cck, data->auto_corr_cck_mrc,
+                       data->nrg_th_cck);
+
+       cmd.control = SENSITIVITY_CMD_CONTROL_WORK_TABLE;
+
+       if (flags & CMD_ASYNC)
+               cmd_out.meta.u.callback = iwl_sensitivity_callback;
+
+       /* Don't send command to uCode if nothing has changed */
+       if (!memcmp(&cmd.table[0], &(priv->sensitivity_tbl[0]),
+                   sizeof(u16)*HD_TABLE_SIZE)) {
+               IWL_DEBUG_CALIB("No change in SENSITIVITY_CMD\n");
+               return 0;
+       }
+
+       /* Copy table for comparison next time */
+       memcpy(&(priv->sensitivity_tbl[0]), &(cmd.table[0]),
+              sizeof(u16)*HD_TABLE_SIZE);
+
+       rc = iwl_send_cmd(priv, &cmd_out);
+       if (!rc) {
+               IWL_DEBUG_CALIB("SENSITIVITY_CMD succeeded\n");
+               return rc;
+       }
+
+       return 0;
+}
+
+void iwl4965_init_sensitivity(struct iwl_priv *priv, u8 flags, u8 force)
+{
+       int rc = 0;
+       int i;
+       struct iwl_sensitivity_data *data = NULL;
+
+       IWL_DEBUG_CALIB("Start iwl4965_init_sensitivity\n");
+
+       if (force)
+               memset(&(priv->sensitivity_tbl[0]), 0,
+                       sizeof(u16)*HD_TABLE_SIZE);
+
+       /* Clear driver's sensitivity algo data */
+       data = &(priv->sensitivity_data);
+       memset(data, 0, sizeof(struct iwl_sensitivity_data));
+
+       data->num_in_cck_no_fa = 0;
+       data->nrg_curr_state = IWL_FA_TOO_MANY;
+       data->nrg_prev_state = IWL_FA_TOO_MANY;
+       data->nrg_silence_ref = 0;
+       data->nrg_silence_idx = 0;
+       data->nrg_energy_idx = 0;
+
+       for (i = 0; i < 10; i++)
+               data->nrg_value[i] = 0;
+
+       for (i = 0; i < NRG_NUM_PREV_STAT_L; i++)
+               data->nrg_silence_rssi[i] = 0;
+
+       data->auto_corr_ofdm = 90;
+       data->auto_corr_ofdm_mrc = 170;
+       data->auto_corr_ofdm_x1  = 105;
+       data->auto_corr_ofdm_mrc_x1 = 220;
+       data->auto_corr_cck = AUTO_CORR_CCK_MIN_VAL_DEF;
+       data->auto_corr_cck_mrc = 200;
+       data->nrg_th_cck = 100;
+       data->nrg_th_ofdm = 100;
+
+       data->last_bad_plcp_cnt_ofdm = 0;
+       data->last_fa_cnt_ofdm = 0;
+       data->last_bad_plcp_cnt_cck = 0;
+       data->last_fa_cnt_cck = 0;
+
+       /* Clear prior Sensitivity command data to force send to uCode */
+       if (force)
+               memset(&(priv->sensitivity_tbl[0]), 0,
+                   sizeof(u16)*HD_TABLE_SIZE);
+
+       rc |= iwl4965_sensitivity_write(priv, flags);
+       IWL_DEBUG_CALIB("<<return 0x%X\n", rc);
+
+       return;
+}
+
+
+/* Reset differential Rx gains in NIC to prepare for chain noise calibration.
+ * Called after every association, but this runs only once!
+ *  ... once chain noise is calibrated the first time, it's good forever.  */
+void iwl4965_chain_noise_reset(struct iwl_priv *priv)
+{
+       struct iwl_chain_noise_data *data = NULL;
+       int rc = 0;
+
+       data = &(priv->chain_noise_data);
+       if ((data->state == IWL_CHAIN_NOISE_ALIVE) && iwl_is_associated(priv)) {
+               struct iwl_calibration_cmd cmd;
+
+               memset(&cmd, 0, sizeof(cmd));
+               cmd.opCode = PHY_CALIBRATE_DIFF_GAIN_CMD;
+               cmd.diff_gain_a = 0;
+               cmd.diff_gain_b = 0;
+               cmd.diff_gain_c = 0;
+               rc = iwl_send_cmd_pdu(priv, REPLY_PHY_CALIBRATION_CMD,
+                                sizeof(cmd), &cmd);
+               msleep(4);
+               data->state = IWL_CHAIN_NOISE_ACCUMULATE;
+               IWL_DEBUG_CALIB("Run chain_noise_calibrate\n");
+       }
+       return;
+}
+
+/*
+ * Accumulate 20 beacons of signal and noise statistics for each of
+ *   3 receivers/antennas/rx-chains, then figure out:
+ * 1)  Which antennas are connected.
+ * 2)  Differential rx gain settings to balance the 3 receivers.
+ */
+static void iwl4965_noise_calibration(struct iwl_priv *priv,
+                                     struct iwl_notif_statistics *stat_resp)
+{
+       struct iwl_chain_noise_data *data = NULL;
+       int rc = 0;
+
+       u32 chain_noise_a;
+       u32 chain_noise_b;
+       u32 chain_noise_c;
+       u32 chain_sig_a;
+       u32 chain_sig_b;
+       u32 chain_sig_c;
+       u32 average_sig[NUM_RX_CHAINS] = {INITIALIZATION_VALUE};
+       u32 average_noise[NUM_RX_CHAINS] = {INITIALIZATION_VALUE};
+       u32 max_average_sig;
+       u16 max_average_sig_antenna_i;
+       u32 min_average_noise = MIN_AVERAGE_NOISE_MAX_VALUE;
+       u16 min_average_noise_antenna_i = INITIALIZATION_VALUE;
+       u16 i = 0;
+       u16 chan_num = INITIALIZATION_VALUE;
+       u32 band = INITIALIZATION_VALUE;
+       u32 active_chains = 0;
+       unsigned long flags;
+       struct statistics_rx_non_phy *rx_info = &(stat_resp->rx.general);
+
+       data = &(priv->chain_noise_data);
+
+       /* Accumulate just the first 20 beacons after the first association,
+        *   then we're done forever. */
+       if (data->state != IWL_CHAIN_NOISE_ACCUMULATE) {
+               if (data->state == IWL_CHAIN_NOISE_ALIVE)
+                       IWL_DEBUG_CALIB("Wait for noise calib reset\n");
+               return;
+       }
+
+       spin_lock_irqsave(&priv->lock, flags);
+       if (rx_info->interference_data_flag != INTERFERENCE_DATA_AVAILABLE) {
+               IWL_DEBUG_CALIB(" << Interference data unavailable\n");
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return;
+       }
+
+       band = (priv->staging_rxon.flags & RXON_FLG_BAND_24G_MSK) ? 0 : 1;
+       chan_num = le16_to_cpu(priv->staging_rxon.channel);
+
+       /* Make sure we accumulate data for just the associated channel
+        *   (even if scanning). */
+       if ((chan_num != (le32_to_cpu(stat_resp->flag) >> 16)) ||
+           ((STATISTICS_REPLY_FLG_BAND_24G_MSK ==
+            (stat_resp->flag & STATISTICS_REPLY_FLG_BAND_24G_MSK)) && band)) {
+               IWL_DEBUG_CALIB("Stats not from chan=%d, band=%d\n",
+                               chan_num, band);
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return;
+       }
+
+       /* Accumulate beacon statistics values across 20 beacons */
+       chain_noise_a = le32_to_cpu(rx_info->beacon_silence_rssi_a) &
+                               IN_BAND_FILTER;
+       chain_noise_b = le32_to_cpu(rx_info->beacon_silence_rssi_b) &
+                               IN_BAND_FILTER;
+       chain_noise_c = le32_to_cpu(rx_info->beacon_silence_rssi_c) &
+                               IN_BAND_FILTER;
+
+       chain_sig_a = le32_to_cpu(rx_info->beacon_rssi_a) & IN_BAND_FILTER;
+       chain_sig_b = le32_to_cpu(rx_info->beacon_rssi_b) & IN_BAND_FILTER;
+       chain_sig_c = le32_to_cpu(rx_info->beacon_rssi_c) & IN_BAND_FILTER;
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       data->beacon_count++;
+
+       data->chain_noise_a = (chain_noise_a + data->chain_noise_a);
+       data->chain_noise_b = (chain_noise_b + data->chain_noise_b);
+       data->chain_noise_c = (chain_noise_c + data->chain_noise_c);
+
+       data->chain_signal_a = (chain_sig_a + data->chain_signal_a);
+       data->chain_signal_b = (chain_sig_b + data->chain_signal_b);
+       data->chain_signal_c = (chain_sig_c + data->chain_signal_c);
+
+       IWL_DEBUG_CALIB("chan=%d, band=%d, beacon=%d\n", chan_num, band,
+                       data->beacon_count);
+       IWL_DEBUG_CALIB("chain_sig: a %d b %d c %d\n",
+                       chain_sig_a, chain_sig_b, chain_sig_c);
+       IWL_DEBUG_CALIB("chain_noise: a %d b %d c %d\n",
+                       chain_noise_a, chain_noise_b, chain_noise_c);
+
+       /* If this is the 20th beacon, determine:
+        * 1)  Disconnected antennas (using signal strengths)
+        * 2)  Differential gain (using silence noise) to balance receivers */
+       if (data->beacon_count == CAL_NUM_OF_BEACONS) {
+
+               /* Analyze signal for disconnected antenna */
+               average_sig[0] = (data->chain_signal_a) / CAL_NUM_OF_BEACONS;
+               average_sig[1] = (data->chain_signal_b) / CAL_NUM_OF_BEACONS;
+               average_sig[2] = (data->chain_signal_c) / CAL_NUM_OF_BEACONS;
+
+               if (average_sig[0] >= average_sig[1]) {
+                       max_average_sig = average_sig[0];
+                       max_average_sig_antenna_i = 0;
+                       active_chains = (1 << max_average_sig_antenna_i);
+               } else {
+                       max_average_sig = average_sig[1];
+                       max_average_sig_antenna_i = 1;
+                       active_chains = (1 << max_average_sig_antenna_i);
+               }
+
+               if (average_sig[2] >= max_average_sig) {
+                       max_average_sig = average_sig[2];
+                       max_average_sig_antenna_i = 2;
+                       active_chains = (1 << max_average_sig_antenna_i);
+               }
+
+               IWL_DEBUG_CALIB("average_sig: a %d b %d c %d\n",
+                            average_sig[0], average_sig[1], average_sig[2]);
+               IWL_DEBUG_CALIB("max_average_sig = %d, antenna %d\n",
+                            max_average_sig, max_average_sig_antenna_i);
+
+               /* Compare signal strengths for all 3 receivers. */
+               for (i = 0; i < NUM_RX_CHAINS; i++) {
+                       if (i != max_average_sig_antenna_i) {
+                               s32 rssi_delta = (max_average_sig -
+                                                 average_sig[i]);
+
+                               /* If signal is very weak, compared with
+                                * strongest, mark it as disconnected. */
+                               if (rssi_delta > MAXIMUM_ALLOWED_PATHLOSS)
+                                       data->disconn_array[i] = 1;
+                               else
+                                       active_chains |= (1 << i);
+                       IWL_DEBUG_CALIB("i = %d  rssiDelta = %d  "
+                                    "disconn_array[i] = %d\n",
+                                    i, rssi_delta, data->disconn_array[i]);
+                       }
+               }
+
+               /*If both chains A & B are disconnected -
+                * connect B and leave A as is */
+               if (data->disconn_array[CHAIN_A] &&
+                   data->disconn_array[CHAIN_B]) {
+                       data->disconn_array[CHAIN_B] = 0;
+                       active_chains |= (1 << CHAIN_B);
+                       IWL_DEBUG_CALIB("both A & B chains are disconnected! "
+                                    "W/A - declare B as connected\n");
+               }
+
+               IWL_DEBUG_CALIB("active_chains (bitwise) = 0x%x\n",
+                               active_chains);
+
+               /* Save for use within RXON, TX, SCAN commands, etc. */
+               priv->valid_antenna = active_chains;
+
+               /* Analyze noise for rx balance */
+               average_noise[0] = ((data->chain_noise_a)/CAL_NUM_OF_BEACONS);
+               average_noise[1] = ((data->chain_noise_b)/CAL_NUM_OF_BEACONS);
+               average_noise[2] = ((data->chain_noise_c)/CAL_NUM_OF_BEACONS);
+
+               for (i = 0; i < NUM_RX_CHAINS; i++) {
+                       if (!(data->disconn_array[i]) &&
+                          (average_noise[i] <= min_average_noise)) {
+                               /* This means that chain i is active and has
+                                * lower noise values so far: */
+                               min_average_noise = average_noise[i];
+                               min_average_noise_antenna_i = i;
+                       }
+               }
+
+               data->delta_gain_code[min_average_noise_antenna_i] = 0;
+
+               IWL_DEBUG_CALIB("average_noise: a %d b %d c %d\n",
+                               average_noise[0], average_noise[1],
+                               average_noise[2]);
+
+