wl12xx: Support routing FW logs to the host
[pandora-kernel.git] / drivers / net / wireless / wl12xx / main.c
index 6dab6f0..a3734bd 100644 (file)
@@ -31,6 +31,7 @@
 #include <linux/platform_device.h>
 #include <linux/slab.h>
 #include <linux/wl12xx.h>
+#include <linux/sched.h>
 
 #include "wl12xx.h"
 #include "wl12xx_80211.h"
@@ -257,12 +258,16 @@ static struct conf_drv_settings default_conf = {
                .wake_up_event               = CONF_WAKE_UP_EVENT_DTIM,
                .listen_interval             = 1,
                .bcn_filt_mode               = CONF_BCN_FILT_MODE_ENABLED,
-               .bcn_filt_ie_count           = 1,
+               .bcn_filt_ie_count           = 2,
                .bcn_filt_ie = {
                        [0] = {
                                .ie          = WLAN_EID_CHANNEL_SWITCH,
                                .rule        = CONF_BCN_RULE_PASS_ON_APPEARANCE,
-                       }
+                       },
+                       [1] = {
+                               .ie          = WLAN_EID_HT_INFORMATION,
+                               .rule        = CONF_BCN_RULE_PASS_ON_CHANGE,
+                       },
                },
                .synch_fail_thold            = 10,
                .bss_lose_timeout            = 100,
@@ -302,6 +307,16 @@ static struct conf_drv_settings default_conf = {
                .max_dwell_time_passive       = 100000,
                .num_probe_reqs               = 2,
        },
+       .sched_scan = {
+               /* sched_scan requires dwell times in TU instead of TU/1000 */
+               .min_dwell_time_active = 8,
+               .max_dwell_time_active = 30,
+               .dwell_time_passive    = 100,
+               .dwell_time_dfs        = 150,
+               .num_probe_reqs        = 2,
+               .rssi_threshold        = -90,
+               .snr_threshold         = 0,
+       },
        .rf = {
                .tx_per_channel_power_compensation_2 = {
                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -348,9 +363,25 @@ static struct conf_drv_settings default_conf = {
                .fm_disturbed_band_margin     = 0xff,       /* default */
                .swallow_clk_diff             = 0xff,       /* default */
        },
+       .rx_streaming = {
+               .duration                      = 150,
+               .queues                        = 0x1,
+               .interval                      = 20,
+               .always                        = 0,
+       },
+       .fwlog = {
+               .mode                         = WL12XX_FWLOG_ON_DEMAND,
+               .mem_blocks                   = 2,
+               .severity                     = 0,
+               .timestamp                    = WL12XX_FWLOG_TIMESTAMP_DISABLED,
+               .output                       = WL12XX_FWLOG_OUTPUT_HOST,
+               .threshold                    = 0,
+       },
        .hci_io_ds = HCI_IO_DS_6MA,
 };
 
+static char *fwlog_param;
+
 static void __wl1271_op_remove_interface(struct wl1271 *wl,
                                         bool reset_tx_queues);
 static void wl1271_free_ap_keys(struct wl1271 *wl);
@@ -374,6 +405,22 @@ static struct platform_device wl1271_device = {
 static DEFINE_MUTEX(wl_list_mutex);
 static LIST_HEAD(wl_list);
 
+static int wl1271_check_operstate(struct wl1271 *wl, unsigned char operstate)
+{
+       int ret;
+       if (operstate != IF_OPER_UP)
+               return 0;
+
+       if (test_and_set_bit(WL1271_FLAG_STA_STATE_SENT, &wl->flags))
+               return 0;
+
+       ret = wl1271_cmd_set_sta_state(wl);
+       if (ret < 0)
+               return ret;
+
+       wl1271_info("Association completed.");
+       return 0;
+}
 static int wl1271_dev_notify(struct notifier_block *me, unsigned long what,
                             void *arg)
 {
@@ -423,11 +470,7 @@ static int wl1271_dev_notify(struct notifier_block *me, unsigned long what,
        if (ret < 0)
                goto out;
 
-       if ((dev->operstate == IF_OPER_UP) &&
-           !test_and_set_bit(WL1271_FLAG_STA_STATE_SENT, &wl->flags)) {
-               wl1271_cmd_set_sta_state(wl);
-               wl1271_info("Association completed.");
-       }
+       wl1271_check_operstate(wl, dev->operstate);
 
        wl1271_ps_elp_sleep(wl);
 
@@ -459,6 +502,117 @@ static int wl1271_reg_notify(struct wiphy *wiphy,
        return 0;
 }
 
+static int wl1271_set_rx_streaming(struct wl1271 *wl, bool enable)
+{
+       int ret = 0;
+
+       /* we should hold wl->mutex */
+       ret = wl1271_acx_ps_rx_streaming(wl, enable);
+       if (ret < 0)
+               goto out;
+
+       if (enable)
+               set_bit(WL1271_FLAG_RX_STREAMING_STARTED, &wl->flags);
+       else
+               clear_bit(WL1271_FLAG_RX_STREAMING_STARTED, &wl->flags);
+out:
+       return ret;
+}
+
+/*
+ * this function is being called when the rx_streaming interval
+ * has beed changed or rx_streaming should be disabled
+ */
+int wl1271_recalc_rx_streaming(struct wl1271 *wl)
+{
+       int ret = 0;
+       int period = wl->conf.rx_streaming.interval;
+
+       /* don't reconfigure if rx_streaming is disabled */
+       if (!test_bit(WL1271_FLAG_RX_STREAMING_STARTED, &wl->flags))
+               goto out;
+
+       /* reconfigure/disable according to new streaming_period */
+       if (period &&
+           test_bit(WL1271_FLAG_STA_ASSOCIATED, &wl->flags) &&
+           (wl->conf.rx_streaming.always ||
+            test_bit(WL1271_FLAG_SOFT_GEMINI, &wl->flags)))
+               ret = wl1271_set_rx_streaming(wl, true);
+       else {
+               ret = wl1271_set_rx_streaming(wl, false);
+               /* don't cancel_work_sync since we might deadlock */
+               del_timer_sync(&wl->rx_streaming_timer);
+       }
+out:
+       return ret;
+}
+
+static void wl1271_rx_streaming_enable_work(struct work_struct *work)
+{
+       int ret;
+       struct wl1271 *wl =
+               container_of(work, struct wl1271, rx_streaming_enable_work);
+
+       mutex_lock(&wl->mutex);
+
+       if (test_bit(WL1271_FLAG_RX_STREAMING_STARTED, &wl->flags) ||
+           !test_bit(WL1271_FLAG_STA_ASSOCIATED, &wl->flags) ||
+           (!wl->conf.rx_streaming.always &&
+            !test_bit(WL1271_FLAG_SOFT_GEMINI, &wl->flags)))
+               goto out;
+
+       if (!wl->conf.rx_streaming.interval)
+               goto out;
+
+       ret = wl1271_ps_elp_wakeup(wl);
+       if (ret < 0)
+               goto out;
+
+       ret = wl1271_set_rx_streaming(wl, true);
+       if (ret < 0)
+               goto out_sleep;
+
+       /* stop it after some time of inactivity */
+       mod_timer(&wl->rx_streaming_timer,
+                 jiffies + msecs_to_jiffies(wl->conf.rx_streaming.duration));
+
+out_sleep:
+       wl1271_ps_elp_sleep(wl);
+out:
+       mutex_unlock(&wl->mutex);
+}
+
+static void wl1271_rx_streaming_disable_work(struct work_struct *work)
+{
+       int ret;
+       struct wl1271 *wl =
+               container_of(work, struct wl1271, rx_streaming_disable_work);
+
+       mutex_lock(&wl->mutex);
+
+       if (!test_bit(WL1271_FLAG_RX_STREAMING_STARTED, &wl->flags))
+               goto out;
+
+       ret = wl1271_ps_elp_wakeup(wl);
+       if (ret < 0)
+               goto out;
+
+       ret = wl1271_set_rx_streaming(wl, false);
+       if (ret)
+               goto out_sleep;
+
+out_sleep:
+       wl1271_ps_elp_sleep(wl);
+out:
+       mutex_unlock(&wl->mutex);
+}
+
+static void wl1271_rx_streaming_timer(unsigned long data)
+{
+       struct wl1271 *wl = (struct wl1271 *)data;
+       ieee80211_queue_work(wl->hw, &wl->rx_streaming_disable_work);
+}
+
 static void wl1271_conf_init(struct wl1271 *wl)
 {
 
@@ -474,8 +628,24 @@ static void wl1271_conf_init(struct wl1271 *wl)
 
        /* apply driver default configuration */
        memcpy(&wl->conf, &default_conf, sizeof(default_conf));
-}
 
+       /* Adjust settings according to optional module parameters */
+       if (fwlog_param) {
+               if (!strcmp(fwlog_param, "continuous")) {
+                       wl->conf.fwlog.mode = WL12XX_FWLOG_CONTINUOUS;
+               } else if (!strcmp(fwlog_param, "ondemand")) {
+                       wl->conf.fwlog.mode = WL12XX_FWLOG_ON_DEMAND;
+               } else if (!strcmp(fwlog_param, "dbgpins")) {
+                       wl->conf.fwlog.mode = WL12XX_FWLOG_CONTINUOUS;
+                       wl->conf.fwlog.output = WL12XX_FWLOG_OUTPUT_DBG_PINS;
+               } else if (!strcmp(fwlog_param, "disable")) {
+                       wl->conf.fwlog.mem_blocks = 0;
+                       wl->conf.fwlog.output = WL12XX_FWLOG_OUTPUT_NONE;
+               } else {
+                       wl1271_error("Unknown fwlog parameter %s", fwlog_param);
+               }
+       }
+}
 
 static int wl1271_plt_init(struct wl1271 *wl)
 {
@@ -727,7 +897,7 @@ static void wl1271_flush_deferred_work(struct wl1271 *wl)
 
        /* Return sent skbs to the network stack */
        while ((skb = skb_dequeue(&wl->deferred_tx_queue)))
-               ieee80211_tx_status(wl->hw, skb);
+               ieee80211_tx_status_ni(wl->hw, skb);
 }
 
 static void wl1271_netstack_work(struct work_struct *work)
@@ -794,7 +964,7 @@ irqreturn_t wl1271_irq(int irq, void *cookie)
                if (unlikely(intr & WL1271_ACX_INTR_WATCHDOG)) {
                        wl1271_error("watchdog interrupt received! "
                                     "starting recovery.");
-                       ieee80211_queue_work(wl->hw, &wl->recovery_work);
+                       wl12xx_queue_recovery_work(wl);
 
                        /* restarting the chip. ignore any other interrupt. */
                        goto out;
@@ -956,6 +1126,89 @@ out:
        return ret;
 }
 
+void wl12xx_queue_recovery_work(struct wl1271 *wl)
+{
+       if (!test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags))
+               ieee80211_queue_work(wl->hw, &wl->recovery_work);
+}
+
+size_t wl12xx_copy_fwlog(struct wl1271 *wl, u8 *memblock, size_t maxlen)
+{
+       size_t len = 0;
+
+       /* The FW log is a length-value list, find where the log end */
+       while (len < maxlen) {
+               if (memblock[len] == 0)
+                       break;
+               if (len + memblock[len] + 1 > maxlen)
+                       break;
+               len += memblock[len] + 1;
+       }
+
+       /* Make sure we have enough room */
+       len = min(len, (size_t)(PAGE_SIZE - wl->fwlog_size));
+
+       /* Fill the FW log file, consumed by the sysfs fwlog entry */
+       memcpy(wl->fwlog + wl->fwlog_size, memblock, len);
+       wl->fwlog_size += len;
+
+       return len;
+}
+
+static void wl12xx_read_fwlog_panic(struct wl1271 *wl)
+{
+       u32 addr;
+       u32 first_addr;
+       u8 *block;
+
+       if ((wl->quirks & WL12XX_QUIRK_FWLOG_NOT_IMPLEMENTED) ||
+           (wl->conf.fwlog.mode != WL12XX_FWLOG_ON_DEMAND) ||
+           (wl->conf.fwlog.mem_blocks == 0))
+               return;
+
+       wl1271_info("Reading FW panic log");
+
+       block = kmalloc(WL12XX_HW_BLOCK_SIZE, GFP_KERNEL);
+       if (!block)
+               return;
+
+       /*
+        * Make sure the chip is awake and the logger isn't active.
+        * This might fail if the firmware hanged.
+        */
+       if (!wl1271_ps_elp_wakeup(wl))
+               wl12xx_cmd_stop_fwlog(wl);
+
+       /* Read the first memory block address */
+       wl1271_fw_status(wl, wl->fw_status);
+       first_addr = __le32_to_cpu(wl->fw_status->sta.log_start_addr);
+       if (!first_addr)
+               goto out;
+
+       /* Traverse the memory blocks linked list */
+       addr = first_addr;
+       do {
+               memset(block, 0, WL12XX_HW_BLOCK_SIZE);
+               wl1271_read_hwaddr(wl, addr, block, WL12XX_HW_BLOCK_SIZE,
+                                  false);
+
+               /*
+                * Memory blocks are linked to one another. The first 4 bytes
+                * of each memory block hold the hardware address of the next
+                * one. The last memory block points to the first one.
+                */
+               addr = __le32_to_cpup((__le32 *)block);
+               if (!wl12xx_copy_fwlog(wl, block + sizeof(addr),
+                                      WL12XX_HW_BLOCK_SIZE - sizeof(addr)))
+                       break;
+       } while (addr && (addr != first_addr));
+
+       wake_up_interruptible(&wl->fwlog_waitq);
+
+out:
+       kfree(block);
+}
+
 static void wl1271_recovery_work(struct work_struct *work)
 {
        struct wl1271 *wl =
@@ -966,6 +1219,11 @@ static void wl1271_recovery_work(struct work_struct *work)
        if (wl->state != WL1271_STATE_ON)
                goto out;
 
+       /* Avoid a recursive recovery */
+       set_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags);
+
+       wl12xx_read_fwlog_panic(wl);
+
        wl1271_info("Hardware recovery in progress. FW ver: %s pc: 0x%x",
                    wl->chip.fw_ver_str, wl1271_read32(wl, SCR_PAD4));
 
@@ -975,8 +1233,16 @@ static void wl1271_recovery_work(struct work_struct *work)
        /* Prevent spurious TX during FW restart */
        ieee80211_stop_queues(wl->hw);
 
+       if (wl->sched_scanning) {
+               ieee80211_sched_scan_stopped(wl->hw);
+               wl->sched_scanning = false;
+       }
+
        /* reboot the chipset */
        __wl1271_op_remove_interface(wl, false);
+
+       clear_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags);
+
        ieee80211_restart_hw(wl->hw);
 
        /*
@@ -1055,9 +1321,13 @@ static int wl1271_chip_wakeup(struct wl1271 *wl)
                wl1271_debug(DEBUG_BOOT, "chip id 0x%x (1271 PG20)",
                             wl->chip.id);
 
-               /* end-of-transaction flag should be set in wl127x AP mode */
+               /*
+                * 'end-of-transaction flag' and 'LPD mode flag'
+                * should be set in wl127x AP mode only
+                */
                if (wl->bss_type == BSS_TYPE_AP_BSS)
-                       wl->quirks |= WL12XX_QUIRK_END_OF_TRANSACTION;
+                       wl->quirks |= (WL12XX_QUIRK_END_OF_TRANSACTION |
+                                      WL12XX_QUIRK_LPD_MODE);
 
                ret = wl1271_setup(wl);
                if (ret < 0)
@@ -1070,6 +1340,7 @@ static int wl1271_chip_wakeup(struct wl1271 *wl)
                ret = wl1271_setup(wl);
                if (ret < 0)
                        goto out;
+
                if (wl1271_set_block_size(wl))
                        wl->quirks |= WL12XX_QUIRK_BLOCKSIZE_ALIGNMENT;
                break;
@@ -1098,24 +1369,6 @@ out:
        return ret;
 }
 
-static unsigned int wl1271_get_fw_ver_quirks(struct wl1271 *wl)
-{
-       unsigned int quirks = 0;
-       unsigned int *fw_ver = wl->chip.fw_ver;
-
-       /* Only for wl127x */
-       if ((fw_ver[FW_VER_CHIP] == FW_VER_CHIP_WL127X) &&
-           /* Check STA version */
-           (((fw_ver[FW_VER_IF_TYPE] == FW_VER_IF_TYPE_STA) &&
-             (fw_ver[FW_VER_MINOR] < FW_VER_MINOR_1_SPARE_STA_MIN)) ||
-            /* Check AP version */
-            ((fw_ver[FW_VER_IF_TYPE] == FW_VER_IF_TYPE_AP) &&
-             (fw_ver[FW_VER_MINOR] < FW_VER_MINOR_1_SPARE_AP_MIN))))
-               quirks |= WL12XX_QUIRK_USE_2_SPARE_BLOCKS;
-
-       return quirks;
-}
-
 int wl1271_plt_start(struct wl1271 *wl)
 {
        int retries = WL1271_BOOT_RETRIES;
@@ -1152,8 +1405,6 @@ int wl1271_plt_start(struct wl1271 *wl)
                wl1271_notice("firmware booted in PLT mode (%s)",
                              wl->chip.fw_ver_str);
 
-               /* Check if any quirks are needed with older fw versions */
-               wl->quirks |= wl1271_get_fw_ver_quirks(wl);
                goto out;
 
 irq_disable:
@@ -1332,6 +1583,183 @@ static struct notifier_block wl1271_dev_notifier = {
        .notifier_call = wl1271_dev_notify,
 };
 
+#ifdef CONFIG_PM
+static int wl1271_configure_suspend_sta(struct wl1271 *wl)
+{
+       int ret;
+
+       mutex_lock(&wl->mutex);
+
+       ret = wl1271_ps_elp_wakeup(wl);
+       if (ret < 0)
+               goto out_unlock;
+
+       /* enter psm if needed*/
+       if (!test_bit(WL1271_FLAG_PSM, &wl->flags)) {
+               DECLARE_COMPLETION_ONSTACK(compl);
+
+               wl->ps_compl = &compl;
+               ret = wl1271_ps_set_mode(wl, STATION_POWER_SAVE_MODE,
+                                  wl->basic_rate, true);
+               if (ret < 0)
+                       goto out_sleep;
+
+               /* we must unlock here so we will be able to get events */
+               wl1271_ps_elp_sleep(wl);
+               mutex_unlock(&wl->mutex);
+
+               ret = wait_for_completion_timeout(
+                       &compl, msecs_to_jiffies(WL1271_PS_COMPLETE_TIMEOUT));
+               if (ret <= 0) {
+                       wl1271_warning("couldn't enter ps mode!");
+                       ret = -EBUSY;
+                       goto out;
+               }
+
+               /* take mutex again, and wakeup */
+               mutex_lock(&wl->mutex);
+
+               ret = wl1271_ps_elp_wakeup(wl);
+               if (ret < 0)
+                       goto out_unlock;
+       }
+out_sleep:
+       wl1271_ps_elp_sleep(wl);
+out_unlock:
+       mutex_unlock(&wl->mutex);
+out:
+       return ret;
+
+}
+
+static int wl1271_configure_suspend_ap(struct wl1271 *wl)
+{
+       int ret;
+
+       mutex_lock(&wl->mutex);
+
+       ret = wl1271_ps_elp_wakeup(wl);
+       if (ret < 0)
+               goto out_unlock;
+
+       ret = wl1271_acx_set_ap_beacon_filter(wl, true);
+
+       wl1271_ps_elp_sleep(wl);
+out_unlock:
+       mutex_unlock(&wl->mutex);
+       return ret;
+
+}
+
+static int wl1271_configure_suspend(struct wl1271 *wl)
+{
+       if (wl->bss_type == BSS_TYPE_STA_BSS)
+               return wl1271_configure_suspend_sta(wl);
+       if (wl->bss_type == BSS_TYPE_AP_BSS)
+               return wl1271_configure_suspend_ap(wl);
+       return 0;
+}
+
+static void wl1271_configure_resume(struct wl1271 *wl)
+{
+       int ret;
+       bool is_sta = wl->bss_type == BSS_TYPE_STA_BSS;
+       bool is_ap = wl->bss_type == BSS_TYPE_AP_BSS;
+
+       if (!is_sta && !is_ap)
+               return;
+
+       mutex_lock(&wl->mutex);
+       ret = wl1271_ps_elp_wakeup(wl);
+       if (ret < 0)
+               goto out;
+
+       if (is_sta) {
+               /* exit psm if it wasn't configured */
+               if (!test_bit(WL1271_FLAG_PSM_REQUESTED, &wl->flags))
+                       wl1271_ps_set_mode(wl, STATION_ACTIVE_MODE,
+                                          wl->basic_rate, true);
+       } else if (is_ap) {
+               wl1271_acx_set_ap_beacon_filter(wl, false);
+       }
+
+       wl1271_ps_elp_sleep(wl);
+out:
+       mutex_unlock(&wl->mutex);
+}
+
+static int wl1271_op_suspend(struct ieee80211_hw *hw,
+                           struct cfg80211_wowlan *wow)
+{
+       struct wl1271 *wl = hw->priv;
+       int ret;
+
+       wl1271_debug(DEBUG_MAC80211, "mac80211 suspend wow=%d", !!wow);
+       WARN_ON(!wow || !wow->any);
+
+       wl->wow_enabled = true;
+       ret = wl1271_configure_suspend(wl);
+       if (ret < 0) {
+               wl1271_warning("couldn't prepare device to suspend");
+               return ret;
+       }
+       /* flush any remaining work */
+       wl1271_debug(DEBUG_MAC80211, "flushing remaining works");
+       flush_delayed_work(&wl->scan_complete_work);
+
+       /*
+        * disable and re-enable interrupts in order to flush
+        * the threaded_irq
+        */
+       wl1271_disable_interrupts(wl);
+
+       /*
+        * set suspended flag to avoid triggering a new threaded_irq
+        * work. no need for spinlock as interrupts are disabled.
+        */
+       set_bit(WL1271_FLAG_SUSPENDED, &wl->flags);
+
+       wl1271_enable_interrupts(wl);
+       flush_work(&wl->tx_work);
+       flush_delayed_work(&wl->pspoll_work);
+       flush_delayed_work(&wl->elp_work);
+
+       return 0;
+}
+
+static int wl1271_op_resume(struct ieee80211_hw *hw)
+{
+       struct wl1271 *wl = hw->priv;
+       unsigned long flags;
+       bool run_irq_work = false;
+
+       wl1271_debug(DEBUG_MAC80211, "mac80211 resume wow=%d",
+                    wl->wow_enabled);
+       WARN_ON(!wl->wow_enabled);
+
+       /*
+        * re-enable irq_work enqueuing, and call irq_work directly if
+        * there is a pending work.
+        */
+       spin_lock_irqsave(&wl->wl_lock, flags);
+       clear_bit(WL1271_FLAG_SUSPENDED, &wl->flags);
+       if (test_and_clear_bit(WL1271_FLAG_PENDING_WORK, &wl->flags))
+               run_irq_work = true;
+       spin_unlock_irqrestore(&wl->wl_lock, flags);
+
+       if (run_irq_work) {
+               wl1271_debug(DEBUG_MAC80211,
+                            "run postponed irq_work directly");
+               wl1271_irq(0, wl);
+               wl1271_enable_interrupts(wl);
+       }
+       wl1271_configure_resume(wl);
+       wl->wow_enabled = false;
+
+       return 0;
+}
+#endif
+
 static int wl1271_op_start(struct ieee80211_hw *hw)
 {
        wl1271_debug(DEBUG_MAC80211, "mac80211 start");
@@ -1464,9 +1892,6 @@ power_off:
        strncpy(wiphy->fw_version, wl->chip.fw_ver_str,
                sizeof(wiphy->fw_version));
 
-       /* Check if any quirks are needed with older fw versions */
-       wl->quirks |= wl1271_get_fw_ver_quirks(wl);
-
        /*
         * Now we know if 11a is supported (info from the NVS), so disable
         * 11a channels if not supported
@@ -1529,6 +1954,9 @@ static void __wl1271_op_remove_interface(struct wl1271 *wl,
        cancel_delayed_work_sync(&wl->scan_complete_work);
        cancel_work_sync(&wl->netstack_work);
        cancel_work_sync(&wl->tx_work);
+       del_timer_sync(&wl->rx_streaming_timer);
+       cancel_work_sync(&wl->rx_streaming_enable_work);
+       cancel_work_sync(&wl->rx_streaming_disable_work);
        cancel_delayed_work_sync(&wl->pspoll_work);
        cancel_delayed_work_sync(&wl->elp_work);
 
@@ -1563,6 +1991,7 @@ static void __wl1271_op_remove_interface(struct wl1271 *wl,
        memset(wl->ap_hlid_map, 0, sizeof(wl->ap_hlid_map));
        wl->ap_fw_ps_map = 0;
        wl->ap_ps_map = 0;
+       wl->sched_scanning = false;
 
        /*
         * this is performed after the cancel_work calls and the associated
@@ -1765,6 +2194,13 @@ static int wl1271_sta_handle_idle(struct wl1271 *wl, bool idle)
                wl->session_counter++;
                if (wl->session_counter >= SESSION_COUNTER_MAX)
                        wl->session_counter = 0;
+
+               /* The current firmware only supports sched_scan in idle */
+               if (wl->sched_scanning) {
+                       wl1271_scan_sched_scan_stop(wl);
+                       ieee80211_sched_scan_stopped(wl->hw);
+               }
+
                ret = wl1271_dummy_join(wl);
                if (ret < 0)
                        goto out;
@@ -2317,6 +2753,60 @@ out:
        return ret;
 }
 
+static int wl1271_op_sched_scan_start(struct ieee80211_hw *hw,
+                                     struct ieee80211_vif *vif,
+                                     struct cfg80211_sched_scan_request *req,
+                                     struct ieee80211_sched_scan_ies *ies)
+{
+       struct wl1271 *wl = hw->priv;
+       int ret;
+
+       wl1271_debug(DEBUG_MAC80211, "wl1271_op_sched_scan_start");
+
+       mutex_lock(&wl->mutex);
+
+       ret = wl1271_ps_elp_wakeup(wl);
+       if (ret < 0)
+               goto out;
+
+       ret = wl1271_scan_sched_scan_config(wl, req, ies);
+       if (ret < 0)
+               goto out_sleep;
+
+       ret = wl1271_scan_sched_scan_start(wl);
+       if (ret < 0)
+               goto out_sleep;
+
+       wl->sched_scanning = true;
+
+out_sleep:
+       wl1271_ps_elp_sleep(wl);
+out:
+       mutex_unlock(&wl->mutex);
+       return ret;
+}
+
+static void wl1271_op_sched_scan_stop(struct ieee80211_hw *hw,
+                                     struct ieee80211_vif *vif)
+{
+       struct wl1271 *wl = hw->priv;
+       int ret;
+
+       wl1271_debug(DEBUG_MAC80211, "wl1271_op_sched_scan_stop");
+
+       mutex_lock(&wl->mutex);
+
+       ret = wl1271_ps_elp_wakeup(wl);
+       if (ret < 0)
+               goto out;
+
+       wl1271_scan_sched_scan_stop(wl);
+
+       wl1271_ps_elp_sleep(wl);
+out:
+       mutex_unlock(&wl->mutex);
+}
+
 static int wl1271_op_set_frag_threshold(struct ieee80211_hw *hw, u32 value)
 {
        struct wl1271 *wl = hw->priv;
@@ -2376,20 +2866,24 @@ out:
 static int wl1271_ssid_set(struct wl1271 *wl, struct sk_buff *skb,
                            int offset)
 {
-       u8 *ptr = skb->data + offset;
+       u8 ssid_len;
+       const u8 *ptr = cfg80211_find_ie(WLAN_EID_SSID, skb->data + offset,
+                                        skb->len - offset);
 
-       /* find the location of the ssid in the beacon */
-       while (ptr < skb->data + skb->len) {
-               if (ptr[0] == WLAN_EID_SSID) {
-                       wl->ssid_len = ptr[1];
-                       memcpy(wl->ssid, ptr+2, wl->ssid_len);
-                       return 0;
-               }
-               ptr += (ptr[1] + 2);
+       if (!ptr) {
+               wl1271_error("No SSID in IEs!");
+               return -ENOENT;
+       }
+
+       ssid_len = ptr[1];
+       if (ssid_len > IEEE80211_MAX_SSID_LEN) {
+               wl1271_error("SSID is too long!");
+               return -EINVAL;
        }
 
-       wl1271_error("No SSID in IEs!\n");
-       return -ENOENT;
+       wl->ssid_len = ssid_len;
+       memcpy(wl->ssid, ptr+2, ssid_len);
+       return 0;
 }
 
 static int wl1271_bss_erp_info_changed(struct wl1271 *wl,
@@ -2549,24 +3043,6 @@ static void wl1271_bss_info_changed_ap(struct wl1271 *wl,
                }
        }
 
-       if (changed & BSS_CHANGED_IBSS) {
-               wl1271_debug(DEBUG_ADHOC, "ibss_joined: %d",
-                            bss_conf->ibss_joined);
-
-               if (bss_conf->ibss_joined) {
-                       u32 rates = bss_conf->basic_rates;
-                       wl->basic_rate_set = wl1271_tx_enabled_rates_get(wl,
-                                                                        rates);
-                       wl->basic_rate = wl1271_tx_min_rate_get(wl);
-
-                       /* by default, use 11b rates */
-                       wl->rate_set = CONF_TX_IBSS_DEFAULT_RATES;
-                       ret = wl1271_acx_sta_rate_policies(wl);
-                       if (ret < 0)
-                               goto out;
-               }
-       }
-
        ret = wl1271_bss_erp_info_changed(wl, bss_conf, changed);
        if (ret < 0)
                goto out;
@@ -2792,6 +3268,24 @@ static void wl1271_bss_info_changed_sta(struct wl1271 *wl,
                }
        }
 
+       if (changed & BSS_CHANGED_IBSS) {
+               wl1271_debug(DEBUG_ADHOC, "ibss_joined: %d",
+                            bss_conf->ibss_joined);
+
+               if (bss_conf->ibss_joined) {
+                       u32 rates = bss_conf->basic_rates;
+                       wl->basic_rate_set = wl1271_tx_enabled_rates_get(wl,
+                                                                        rates);
+                       wl->basic_rate = wl1271_tx_min_rate_get(wl);
+
+                       /* by default, use 11b rates */
+                       wl->rate_set = CONF_TX_IBSS_DEFAULT_RATES;
+                       ret = wl1271_acx_sta_rate_policies(wl);
+                       if (ret < 0)
+                               goto out;
+               }
+       }
+
        ret = wl1271_bss_erp_info_changed(wl, bss_conf, changed);
        if (ret < 0)
                goto out;
@@ -2830,6 +3324,7 @@ static void wl1271_bss_info_changed_sta(struct wl1271 *wl,
                        wl1271_warning("cmd join failed %d", ret);
                        goto out;
                }
+               wl1271_check_operstate(wl, ieee80211_get_operstate(vif));
        }
 
 out:
@@ -3123,9 +3618,12 @@ static int wl1271_op_ampdu_action(struct ieee80211_hw *hw,
        if (ret < 0)
                goto out;
 
+       wl1271_debug(DEBUG_MAC80211, "mac80211 ampdu: Rx tid %d action %d",
+                    tid, action);
+
        switch (action) {
        case IEEE80211_AMPDU_RX_START:
-               if (wl->ba_support) {
+               if ((wl->ba_support) && (wl->ba_allowed)) {
                        ret = wl1271_acx_set_ba_receiver_session(wl, tid, *ssn,
                                                                 true);
                        if (!ret)
@@ -3422,12 +3920,18 @@ static const struct ieee80211_ops wl1271_ops = {
        .stop = wl1271_op_stop,
        .add_interface = wl1271_op_add_interface,
        .remove_interface = wl1271_op_remove_interface,
+#ifdef CONFIG_PM
+       .suspend = wl1271_op_suspend,
+       .resume = wl1271_op_resume,
+#endif
        .config = wl1271_op_config,
        .prepare_multicast = wl1271_op_prepare_multicast,
        .configure_filter = wl1271_op_configure_filter,
        .tx = wl1271_op_tx,
        .set_key = wl1271_op_set_key,
        .hw_scan = wl1271_op_hw_scan,
+       .sched_scan_start = wl1271_op_sched_scan_start,
+       .sched_scan_stop = wl1271_op_sched_scan_stop,
        .bss_info_changed = wl1271_op_bss_info_changed,
        .set_frag_threshold = wl1271_op_set_frag_threshold,
        .set_rts_threshold = wl1271_op_set_rts_threshold,
@@ -3544,6 +4048,69 @@ static ssize_t wl1271_sysfs_show_hw_pg_ver(struct device *dev,
 static DEVICE_ATTR(hw_pg_ver, S_IRUGO | S_IWUSR,
                   wl1271_sysfs_show_hw_pg_ver, NULL);
 
+static ssize_t wl1271_sysfs_read_fwlog(struct file *filp, struct kobject *kobj,
+                                      struct bin_attribute *bin_attr,
+                                      char *buffer, loff_t pos, size_t count)
+{
+       struct device *dev = container_of(kobj, struct device, kobj);
+       struct wl1271 *wl = dev_get_drvdata(dev);
+       ssize_t len;
+       int ret;
+
+       ret = mutex_lock_interruptible(&wl->mutex);
+       if (ret < 0)
+               return -ERESTARTSYS;
+
+       /* Let only one thread read the log at a time, blocking others */
+       while (wl->fwlog_size == 0) {
+               DEFINE_WAIT(wait);
+
+               prepare_to_wait_exclusive(&wl->fwlog_waitq,
+                                         &wait,
+                                         TASK_INTERRUPTIBLE);
+
+               if (wl->fwlog_size != 0) {
+                       finish_wait(&wl->fwlog_waitq, &wait);
+                       break;
+               }
+
+               mutex_unlock(&wl->mutex);
+
+               schedule();
+               finish_wait(&wl->fwlog_waitq, &wait);
+
+               if (signal_pending(current))
+                       return -ERESTARTSYS;
+
+               ret = mutex_lock_interruptible(&wl->mutex);
+               if (ret < 0)
+                       return -ERESTARTSYS;
+       }
+
+       /* Check if the fwlog is still valid */
+       if (wl->fwlog_size < 0) {
+               mutex_unlock(&wl->mutex);
+               return 0;
+       }
+
+       /* Seeking is not supported - old logs are not kept. Disregard pos. */
+       len = min(count, (size_t)wl->fwlog_size);
+       wl->fwlog_size -= len;
+       memcpy(buffer, wl->fwlog, len);
+
+       /* Make room for new messages */
+       memmove(wl->fwlog, wl->fwlog + len, wl->fwlog_size);
+
+       mutex_unlock(&wl->mutex);
+
+       return len;
+}
+
+static struct bin_attribute fwlog_attr = {
+       .attr = {.name = "fwlog", .mode = S_IRUSR},
+       .read = wl1271_sysfs_read_fwlog,
+};
+
 int wl1271_register_hw(struct wl1271 *wl)
 {
        int ret;
@@ -3626,6 +4193,7 @@ int wl1271_init_ieee80211(struct wl1271 *wl)
                IEEE80211_HW_CONNECTION_MONITOR |
                IEEE80211_HW_SUPPORTS_CQM_RSSI |
                IEEE80211_HW_REPORTS_TX_ACK_STATUS |
+               IEEE80211_HW_SPECTRUM_MGMT |
                IEEE80211_HW_AP_LINK_PS;
 
        wl->hw->wiphy->cipher_suites = cipher_suites;
@@ -3723,6 +4291,17 @@ struct ieee80211_hw *wl1271_alloc_hw(void)
        INIT_WORK(&wl->tx_work, wl1271_tx_work);
        INIT_WORK(&wl->recovery_work, wl1271_recovery_work);
        INIT_DELAYED_WORK(&wl->scan_complete_work, wl1271_scan_complete_work);
+       INIT_WORK(&wl->rx_streaming_enable_work,
+                 wl1271_rx_streaming_enable_work);
+       INIT_WORK(&wl->rx_streaming_disable_work,
+                 wl1271_rx_streaming_disable_work);
+
+       wl->freezable_wq = create_freezable_workqueue("wl12xx_wq");
+       if (!wl->freezable_wq) {
+               ret = -ENOMEM;
+               goto err_hw;
+       }
+
        wl->channel = WL1271_DEFAULT_CHANNEL;
        wl->beacon_int = WL1271_DEFAULT_BEACON_INT;
        wl->default_key = 0;
@@ -3747,6 +4326,11 @@ struct ieee80211_hw *wl1271_alloc_hw(void)
        wl->ap_fw_ps_map = 0;
        wl->quirks = 0;
        wl->platform_quirks = 0;
+       wl->sched_scanning = false;
+       setup_timer(&wl->rx_streaming_timer, wl1271_rx_streaming_timer,
+                   (unsigned long) wl);
+       wl->fwlog_size = 0;
+       init_waitqueue_head(&wl->fwlog_waitq);
 
        memset(wl->tx_frames_map, 0, sizeof(wl->tx_frames_map));
        for (i = 0; i < ACX_TX_DESCRIPTORS; i++)
@@ -3764,7 +4348,7 @@ struct ieee80211_hw *wl1271_alloc_hw(void)
        wl->aggr_buf = (u8 *)__get_free_pages(GFP_KERNEL, order);
        if (!wl->aggr_buf) {
                ret = -ENOMEM;
-               goto err_hw;
+               goto err_wq;
        }
 
        wl->dummy_packet = wl12xx_alloc_dummy_packet(wl);
@@ -3773,11 +4357,18 @@ struct ieee80211_hw *wl1271_alloc_hw(void)
                goto err_aggr;
        }
 
+       /* Allocate one page for the FW log */
+       wl->fwlog = (u8 *)get_zeroed_page(GFP_KERNEL);
+       if (!wl->fwlog) {
+               ret = -ENOMEM;
+               goto err_dummy_packet;
+       }
+
        /* Register platform device */
        ret = platform_device_register(wl->plat_dev);
        if (ret) {
                wl1271_error("couldn't register platform device");
-               goto err_dummy_packet;
+               goto err_fwlog;
        }
        dev_set_drvdata(&wl->plat_dev->dev, wl);
 
@@ -3795,20 +4386,36 @@ struct ieee80211_hw *wl1271_alloc_hw(void)
                goto err_bt_coex_state;
        }
 
+       /* Create sysfs file for the FW log */
+       ret = device_create_bin_file(&wl->plat_dev->dev, &fwlog_attr);
+       if (ret < 0) {
+               wl1271_error("failed to create sysfs file fwlog");
+               goto err_hw_pg_ver;
+       }
+
        return hw;
 
+err_hw_pg_ver:
+       device_remove_file(&wl->plat_dev->dev, &dev_attr_hw_pg_ver);
+
 err_bt_coex_state:
        device_remove_file(&wl->plat_dev->dev, &dev_attr_bt_coex_state);
 
 err_platform:
        platform_device_unregister(wl->plat_dev);
 
+err_fwlog:
+       free_page((unsigned long)wl->fwlog);
+
 err_dummy_packet:
        dev_kfree_skb(wl->dummy_packet);
 
 err_aggr:
        free_pages((unsigned long)wl->aggr_buf, order);
 
+err_wq:
+       destroy_workqueue(wl->freezable_wq);
+
 err_hw:
        wl1271_debugfs_exit(wl);
        kfree(plat_dev);
@@ -3824,7 +4431,15 @@ EXPORT_SYMBOL_GPL(wl1271_alloc_hw);
 
 int wl1271_free_hw(struct wl1271 *wl)
 {
+       /* Unblock any fwlog readers */
+       mutex_lock(&wl->mutex);
+       wl->fwlog_size = -1;
+       wake_up_interruptible_all(&wl->fwlog_waitq);
+       mutex_unlock(&wl->mutex);
+
+       device_remove_bin_file(&wl->plat_dev->dev, &fwlog_attr);
        platform_device_unregister(wl->plat_dev);
+       free_page((unsigned long)wl->fwlog);
        dev_kfree_skb(wl->dummy_packet);
        free_pages((unsigned long)wl->aggr_buf,
                        get_order(WL1271_AGGR_BUFFER_SIZE));
@@ -3839,6 +4454,7 @@ int wl1271_free_hw(struct wl1271 *wl)
 
        kfree(wl->fw_status);
        kfree(wl->tx_res_if);
+       destroy_workqueue(wl->freezable_wq);
 
        ieee80211_free_hw(wl->hw);
 
@@ -3851,6 +4467,10 @@ EXPORT_SYMBOL_GPL(wl12xx_debug_level);
 module_param_named(debug_level, wl12xx_debug_level, uint, S_IRUSR | S_IWUSR);
 MODULE_PARM_DESC(debug_level, "wl12xx debugging level");
 
+module_param_named(fwlog, fwlog_param, charp, 0);
+MODULE_PARM_DESC(keymap,
+                "FW logger options: continuous, ondemand, dbgpins or disable");
+
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Luciano Coelho <coelho@ti.com>");
 MODULE_AUTHOR("Juuso Oikarinen <juuso.oikarinen@nokia.com>");