|
@@ -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"
|
|
@@ -362,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);
|
|
@@ -388,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)
|
|
|
{
|
|
@@ -437,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);
|
|
|
|
|
@@ -473,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)
|
|
|
{
|
|
|
|
|
@@ -488,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)
|
|
|
{
|
|
@@ -741,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)
|
|
@@ -808,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;
|
|
@@ -970,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 =
|
|
@@ -980,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));
|
|
|
|
|
@@ -996,6 +1240,9 @@ static void wl1271_recovery_work(struct work_struct *work)
|
|
|
|
|
|
/* reboot the chipset */
|
|
|
__wl1271_op_remove_interface(wl, false);
|
|
|
+
|
|
|
+ clear_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags);
|
|
|
+
|
|
|
ieee80211_restart_hw(wl->hw);
|
|
|
|
|
|
/*
|
|
@@ -1074,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)
|
|
@@ -1089,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;
|
|
@@ -1117,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;
|
|
@@ -1171,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:
|
|
@@ -1352,13 +1584,10 @@ static struct notifier_block wl1271_dev_notifier = {
|
|
|
};
|
|
|
|
|
|
#ifdef CONFIG_PM
|
|
|
-static int wl1271_configure_suspend(struct wl1271 *wl)
|
|
|
+static int wl1271_configure_suspend_sta(struct wl1271 *wl)
|
|
|
{
|
|
|
int ret;
|
|
|
|
|
|
- if (wl->bss_type != BSS_TYPE_STA_BSS)
|
|
|
- return 0;
|
|
|
-
|
|
|
mutex_lock(&wl->mutex);
|
|
|
|
|
|
ret = wl1271_ps_elp_wakeup(wl);
|
|
@@ -1403,11 +1632,41 @@ out:
|
|
|
|
|
|
}
|
|
|
|
|
|
+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 (wl->bss_type != BSS_TYPE_STA_BSS)
|
|
|
+ if (!is_sta && !is_ap)
|
|
|
return;
|
|
|
|
|
|
mutex_lock(&wl->mutex);
|
|
@@ -1415,10 +1674,14 @@ static void wl1271_configure_resume(struct wl1271 *wl)
|
|
|
if (ret < 0)
|
|
|
goto out;
|
|
|
|
|
|
- /* 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);
|
|
|
+ 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:
|
|
@@ -1429,69 +1692,69 @@ 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);
|
|
|
- wl->wow_enabled = !!wow;
|
|
|
- if (wl->wow_enabled) {
|
|
|
- int ret;
|
|
|
- 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);
|
|
|
+ WARN_ON(!wow || !wow->any);
|
|
|
|
|
|
- /*
|
|
|
- * disable and re-enable interrupts in order to flush
|
|
|
- * the threaded_irq
|
|
|
- */
|
|
|
- wl1271_disable_interrupts(wl);
|
|
|
+ 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);
|
|
|
|
|
|
- /*
|
|
|
- * 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);
|
|
|
+ /*
|
|
|
+ * 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);
|
|
|
|
|
|
- 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.
|
|
|
*/
|
|
|
- if (wl->wow_enabled) {
|
|
|
- struct wl1271 *wl = hw->priv;
|
|
|
- unsigned long flags;
|
|
|
- bool run_irq_work = false;
|
|
|
-
|
|
|
- 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);
|
|
|
- }
|
|
|
+ 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);
|
|
|
|
|
|
- wl1271_configure_resume(wl);
|
|
|
+ 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;
|
|
|
}
|
|
@@ -1629,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
|
|
@@ -1694,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);
|
|
|
|
|
@@ -2780,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;
|
|
@@ -3023,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;
|
|
@@ -3061,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:
|
|
@@ -3784,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;
|
|
@@ -3964,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;
|
|
@@ -3989,6 +4327,10 @@ struct ieee80211_hw *wl1271_alloc_hw(void)
|
|
|
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++)
|
|
@@ -4006,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);
|
|
@@ -4015,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);
|
|
|
|
|
@@ -4037,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);
|
|
@@ -4066,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));
|
|
@@ -4081,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);
|
|
|
|
|
@@ -4093,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>");
|