Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-2.0-only
config WLAN_VENDOR_ST
bool "STMicroelectronics devices"
default y
help
If you have a wireless card belonging to this class, say Y.
Note that the answer to this question doesn't directly affect the
kernel: saying N will just cause the configurator to skip all the
questions about these cards. If you say Y, you will be asked for
your specific card in the following questions.
if WLAN_VENDOR_ST
source "drivers/net/wireless/st/cw1200/Kconfig"
endif # WLAN_VENDOR_ST
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_CW1200) += cw1200/
@@ -0,0 +1,31 @@
# SPDX-License-Identifier: GPL-2.0-only
config CW1200
tristate "CW1200 WLAN support"
depends on MAC80211 && CFG80211
help
This is a driver for the ST-E CW1100 & CW1200 WLAN chipsets.
This option just enables the driver core, see below for
specific bus support.
if CW1200
config CW1200_WLAN_SDIO
tristate "Support SDIO platforms"
depends on CW1200 && MMC
help
Enable support for the CW1200 connected via an SDIO bus.
By default this driver only supports the Sagrad SG901-1091/1098 EVK
and similar designs that utilize a hardware reset circuit. To
support different CW1200 SDIO designs you will need to override
the default platform data by calling cw1200_sdio_set_platform_data()
in your board setup file.
config CW1200_WLAN_SPI
tristate "Support SPI platforms"
depends on CW1200 && SPI
help
Enables support for the CW1200 connected via a SPI bus. You will
need to add appropriate platform data glue in your board setup
file.
endif
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0
cw1200_core-y := \
fwio.o \
txrx.o \
main.o \
queue.o \
hwio.o \
bh.o \
wsm.o \
sta.o \
scan.o \
debug.o
cw1200_core-$(CONFIG_PM) += pm.o
# CFLAGS_sta.o += -DDEBUG
cw1200_wlan_sdio-y := cw1200_sdio.o
cw1200_wlan_spi-y := cw1200_spi.o
obj-$(CONFIG_CW1200) += cw1200_core.o
obj-$(CONFIG_CW1200_WLAN_SDIO) += cw1200_wlan_sdio.o
obj-$(CONFIG_CW1200_WLAN_SPI) += cw1200_wlan_spi.o
@@ -0,0 +1,608 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Device handling thread implementation for mac80211 ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on:
* ST-Ericsson UMAC CW1200 driver, which is
* Copyright (c) 2010, ST-Ericsson
* Author: Ajitpal Singh <ajitpal.singh@stericsson.com>
*/
#include <linux/module.h>
#include <net/mac80211.h>
#include <linux/kthread.h>
#include <linux/timer.h>
#include "cw1200.h"
#include "bh.h"
#include "hwio.h"
#include "wsm.h"
#include "hwbus.h"
#include "debug.h"
#include "fwio.h"
static int cw1200_bh(void *arg);
#define DOWNLOAD_BLOCK_SIZE_WR (0x1000 - 4)
/* an SPI message cannot be bigger than (2"12-1)*2 bytes
* "*2" to cvt to bytes
*/
#define MAX_SZ_RD_WR_BUFFERS (DOWNLOAD_BLOCK_SIZE_WR*2)
#define PIGGYBACK_CTRL_REG (2)
#define EFFECTIVE_BUF_SIZE (MAX_SZ_RD_WR_BUFFERS - PIGGYBACK_CTRL_REG)
/* Suspend state privates */
enum cw1200_bh_pm_state {
CW1200_BH_RESUMED = 0,
CW1200_BH_SUSPEND,
CW1200_BH_SUSPENDED,
CW1200_BH_RESUME,
};
static void cw1200_bh_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, bh_work);
cw1200_bh(priv);
}
int cw1200_register_bh(struct cw1200_common *priv)
{
int err = 0;
/* Realtime workqueue */
priv->bh_workqueue = alloc_workqueue("cw1200_bh",
WQ_MEM_RECLAIM | WQ_HIGHPRI |
WQ_CPU_INTENSIVE | WQ_PERCPU,
1);
if (!priv->bh_workqueue)
return -ENOMEM;
INIT_WORK(&priv->bh_work, cw1200_bh_work);
pr_debug("[BH] register.\n");
atomic_set(&priv->bh_rx, 0);
atomic_set(&priv->bh_tx, 0);
atomic_set(&priv->bh_term, 0);
atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED);
priv->bh_error = 0;
priv->hw_bufs_used = 0;
priv->buf_id_tx = 0;
priv->buf_id_rx = 0;
init_waitqueue_head(&priv->bh_wq);
init_waitqueue_head(&priv->bh_evt_wq);
err = !queue_work(priv->bh_workqueue, &priv->bh_work);
WARN_ON(err);
return err;
}
void cw1200_unregister_bh(struct cw1200_common *priv)
{
atomic_inc(&priv->bh_term);
wake_up(&priv->bh_wq);
destroy_workqueue(priv->bh_workqueue);
priv->bh_workqueue = NULL;
pr_debug("[BH] unregistered.\n");
}
void cw1200_irq_handler(struct cw1200_common *priv)
{
pr_debug("[BH] irq.\n");
/* Disable Interrupts! */
/* NOTE: hwbus_ops->lock already held */
__cw1200_irq_enable(priv, 0);
if (/* WARN_ON */(priv->bh_error))
return;
if (atomic_inc_return(&priv->bh_rx) == 1)
wake_up(&priv->bh_wq);
}
EXPORT_SYMBOL_GPL(cw1200_irq_handler);
void cw1200_bh_wakeup(struct cw1200_common *priv)
{
pr_debug("[BH] wakeup.\n");
if (priv->bh_error) {
pr_err("[BH] wakeup failed (BH error)\n");
return;
}
if (atomic_inc_return(&priv->bh_tx) == 1)
wake_up(&priv->bh_wq);
}
int cw1200_bh_suspend(struct cw1200_common *priv)
{
pr_debug("[BH] suspend.\n");
if (priv->bh_error) {
wiphy_warn(priv->hw->wiphy, "BH error -- can't suspend\n");
return -EINVAL;
}
atomic_set(&priv->bh_suspend, CW1200_BH_SUSPEND);
wake_up(&priv->bh_wq);
return wait_event_timeout(priv->bh_evt_wq, priv->bh_error ||
(CW1200_BH_SUSPENDED == atomic_read(&priv->bh_suspend)),
1 * HZ) ? 0 : -ETIMEDOUT;
}
int cw1200_bh_resume(struct cw1200_common *priv)
{
pr_debug("[BH] resume.\n");
if (priv->bh_error) {
wiphy_warn(priv->hw->wiphy, "BH error -- can't resume\n");
return -EINVAL;
}
atomic_set(&priv->bh_suspend, CW1200_BH_RESUME);
wake_up(&priv->bh_wq);
return wait_event_timeout(priv->bh_evt_wq, priv->bh_error ||
(CW1200_BH_RESUMED == atomic_read(&priv->bh_suspend)),
1 * HZ) ? 0 : -ETIMEDOUT;
}
static inline void wsm_alloc_tx_buffer(struct cw1200_common *priv)
{
++priv->hw_bufs_used;
}
int wsm_release_tx_buffer(struct cw1200_common *priv, int count)
{
int ret = 0;
int hw_bufs_used = priv->hw_bufs_used;
priv->hw_bufs_used -= count;
if (WARN_ON(priv->hw_bufs_used < 0))
ret = -1;
else if (hw_bufs_used >= priv->wsm_caps.input_buffers)
ret = 1;
if (!priv->hw_bufs_used)
wake_up(&priv->bh_evt_wq);
return ret;
}
static int cw1200_bh_read_ctrl_reg(struct cw1200_common *priv,
u16 *ctrl_reg)
{
int ret;
ret = cw1200_reg_read_16(priv,
ST90TDS_CONTROL_REG_ID, ctrl_reg);
if (ret) {
ret = cw1200_reg_read_16(priv,
ST90TDS_CONTROL_REG_ID, ctrl_reg);
if (ret)
pr_err("[BH] Failed to read control register.\n");
}
return ret;
}
static int cw1200_device_wakeup(struct cw1200_common *priv)
{
u16 ctrl_reg;
int ret;
pr_debug("[BH] Device wakeup.\n");
/* First, set the dpll register */
ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID,
cw1200_dpll_from_clk(priv->hw_refclk));
if (WARN_ON(ret))
return ret;
/* To force the device to be always-on, the host sets WLAN_UP to 1 */
ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID,
ST90TDS_CONT_WUP_BIT);
if (WARN_ON(ret))
return ret;
ret = cw1200_bh_read_ctrl_reg(priv, &ctrl_reg);
if (WARN_ON(ret))
return ret;
/* If the device returns WLAN_RDY as 1, the device is active and will
* remain active.
*/
if (ctrl_reg & ST90TDS_CONT_RDY_BIT) {
pr_debug("[BH] Device awake.\n");
return 1;
}
return 0;
}
/* Must be called from BH thraed. */
void cw1200_enable_powersave(struct cw1200_common *priv,
bool enable)
{
pr_debug("[BH] Powerave is %s.\n",
enable ? "enabled" : "disabled");
priv->powersave_enabled = enable;
}
static int cw1200_bh_rx_helper(struct cw1200_common *priv,
uint16_t *ctrl_reg,
int *tx)
{
size_t read_len = 0;
struct sk_buff *skb_rx = NULL;
struct wsm_hdr *wsm;
size_t wsm_len;
u16 wsm_id;
u8 wsm_seq;
int rx_resync = 1;
size_t alloc_len;
u8 *data;
read_len = (*ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) * 2;
if (!read_len)
return 0; /* No more work */
if (WARN_ON((read_len < sizeof(struct wsm_hdr)) ||
(read_len > EFFECTIVE_BUF_SIZE))) {
pr_debug("Invalid read len: %zu (%04x)",
read_len, *ctrl_reg);
goto err;
}
/* Add SIZE of PIGGYBACK reg (CONTROL Reg)
* to the NEXT Message length + 2 Bytes for SKB
*/
read_len = read_len + 2;
alloc_len = priv->hwbus_ops->align_size(
priv->hwbus_priv, read_len);
/* Check if not exceeding CW1200 capabilities */
if (WARN_ON_ONCE(alloc_len > EFFECTIVE_BUF_SIZE)) {
pr_debug("Read aligned len: %zu\n",
alloc_len);
}
skb_rx = dev_alloc_skb(alloc_len);
if (WARN_ON(!skb_rx))
goto err;
skb_trim(skb_rx, 0);
skb_put(skb_rx, read_len);
data = skb_rx->data;
if (WARN_ON(!data))
goto err;
if (WARN_ON(cw1200_data_read(priv, data, alloc_len))) {
pr_err("rx blew up, len %zu\n", alloc_len);
goto err;
}
/* Piggyback */
*ctrl_reg = __le16_to_cpu(
((__le16 *)data)[alloc_len / 2 - 1]);
wsm = (struct wsm_hdr *)data;
wsm_len = __le16_to_cpu(wsm->len);
if (WARN_ON(wsm_len > read_len))
goto err;
if (priv->wsm_enable_wsm_dumps)
print_hex_dump_bytes("<-- ",
DUMP_PREFIX_NONE,
data, wsm_len);
wsm_id = __le16_to_cpu(wsm->id) & 0xFFF;
wsm_seq = (__le16_to_cpu(wsm->id) >> 13) & 7;
skb_trim(skb_rx, wsm_len);
if (wsm_id == 0x0800) {
wsm_handle_exception(priv,
&data[sizeof(*wsm)],
wsm_len - sizeof(*wsm));
goto err;
} else if (!rx_resync) {
if (WARN_ON(wsm_seq != priv->wsm_rx_seq))
goto err;
}
priv->wsm_rx_seq = (wsm_seq + 1) & 7;
rx_resync = 0;
if (wsm_id & 0x0400) {
int rc = wsm_release_tx_buffer(priv, 1);
if (WARN_ON(rc < 0)) {
dev_kfree_skb(skb_rx);
return rc;
} else if (rc > 0) {
*tx = 1;
}
}
/* cw1200_wsm_rx takes care on SKB livetime */
if (WARN_ON(wsm_handle_rx(priv, wsm_id, wsm, &skb_rx)))
goto err;
dev_kfree_skb(skb_rx);
return 0;
err:
dev_kfree_skb(skb_rx);
return -1;
}
static int cw1200_bh_tx_helper(struct cw1200_common *priv,
int *pending_tx,
int *tx_burst)
{
size_t tx_len;
u8 *data;
int ret;
struct wsm_hdr *wsm;
if (priv->device_can_sleep) {
ret = cw1200_device_wakeup(priv);
if (WARN_ON(ret < 0)) { /* Error in wakeup */
*pending_tx = 1;
return 0;
} else if (ret) { /* Woke up */
priv->device_can_sleep = false;
} else { /* Did not awake */
*pending_tx = 1;
return 0;
}
}
wsm_alloc_tx_buffer(priv);
ret = wsm_get_tx(priv, &data, &tx_len, tx_burst);
if (ret <= 0) {
wsm_release_tx_buffer(priv, 1);
if (WARN_ON(ret < 0))
return ret; /* Error */
return 0; /* No work */
}
wsm = (struct wsm_hdr *)data;
BUG_ON(tx_len < sizeof(*wsm));
BUG_ON(__le16_to_cpu(wsm->len) != tx_len);
atomic_inc(&priv->bh_tx);
tx_len = priv->hwbus_ops->align_size(
priv->hwbus_priv, tx_len);
/* Check if not exceeding CW1200 capabilities */
if (WARN_ON_ONCE(tx_len > EFFECTIVE_BUF_SIZE))
pr_debug("Write aligned len: %zu\n", tx_len);
wsm->id &= __cpu_to_le16(0xffff ^ WSM_TX_SEQ(WSM_TX_SEQ_MAX));
wsm->id |= __cpu_to_le16(WSM_TX_SEQ(priv->wsm_tx_seq));
if (WARN_ON(cw1200_data_write(priv, data, tx_len))) {
pr_err("tx blew up, len %zu\n", tx_len);
wsm_release_tx_buffer(priv, 1);
return -1; /* Error */
}
if (priv->wsm_enable_wsm_dumps)
print_hex_dump_bytes("--> ",
DUMP_PREFIX_NONE,
data,
__le16_to_cpu(wsm->len));
wsm_txed(priv, data);
priv->wsm_tx_seq = (priv->wsm_tx_seq + 1) & WSM_TX_SEQ_MAX;
if (*tx_burst > 1) {
cw1200_debug_tx_burst(priv);
return 1; /* Work remains */
}
return 0;
}
static int cw1200_bh(void *arg)
{
struct cw1200_common *priv = arg;
int rx, tx, term, suspend;
u16 ctrl_reg = 0;
int tx_allowed;
int pending_tx = 0;
int tx_burst;
long status;
u32 dummy;
int ret;
for (;;) {
if (!priv->hw_bufs_used &&
priv->powersave_enabled &&
!priv->device_can_sleep &&
!atomic_read(&priv->recent_scan)) {
status = 1 * HZ;
pr_debug("[BH] Device wakedown. No data.\n");
cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID, 0);
priv->device_can_sleep = true;
} else if (priv->hw_bufs_used) {
/* Interrupt loss detection */
status = 1 * HZ;
} else {
status = MAX_SCHEDULE_TIMEOUT;
}
/* Dummy Read for SDIO retry mechanism*/
if ((priv->hw_type != -1) &&
(atomic_read(&priv->bh_rx) == 0) &&
(atomic_read(&priv->bh_tx) == 0))
cw1200_reg_read(priv, ST90TDS_CONFIG_REG_ID,
&dummy, sizeof(dummy));
pr_debug("[BH] waiting ...\n");
status = wait_event_interruptible_timeout(priv->bh_wq, ({
rx = atomic_xchg(&priv->bh_rx, 0);
tx = atomic_xchg(&priv->bh_tx, 0);
term = atomic_xchg(&priv->bh_term, 0);
suspend = pending_tx ?
0 : atomic_read(&priv->bh_suspend);
(rx || tx || term || suspend || priv->bh_error);
}), status);
pr_debug("[BH] - rx: %d, tx: %d, term: %d, bh_err: %d, suspend: %d, status: %ld\n",
rx, tx, term, suspend, priv->bh_error, status);
/* Did an error occur? */
if ((status < 0 && status != -ERESTARTSYS) ||
term || priv->bh_error) {
break;
}
if (!status) { /* wait_event timed out */
unsigned long timestamp = jiffies;
long timeout;
int pending = 0;
int i;
/* Check to see if we have any outstanding frames */
if (priv->hw_bufs_used && (!rx || !tx)) {
wiphy_warn(priv->hw->wiphy,
"Missed interrupt? (%d frames outstanding)\n",
priv->hw_bufs_used);
rx = 1;
/* Get a timestamp of "oldest" frame */
for (i = 0; i < 4; ++i)
pending += cw1200_queue_get_xmit_timestamp(
&priv->tx_queue[i],
&timestamp,
priv->pending_frame_id);
/* Check if frame transmission is timed out.
* Add an extra second with respect to possible
* interrupt loss.
*/
timeout = timestamp +
WSM_CMD_LAST_CHANCE_TIMEOUT +
1 * HZ -
jiffies;
/* And terminate BH thread if the frame is "stuck" */
if (pending && timeout < 0) {
wiphy_warn(priv->hw->wiphy,
"Timeout waiting for TX confirm (%d/%d pending, %ld vs %lu).\n",
priv->hw_bufs_used, pending,
timestamp, jiffies);
break;
}
} else if (!priv->device_can_sleep &&
!atomic_read(&priv->recent_scan)) {
pr_debug("[BH] Device wakedown. Timeout.\n");
cw1200_reg_write_16(priv,
ST90TDS_CONTROL_REG_ID, 0);
priv->device_can_sleep = true;
}
goto done;
} else if (suspend) {
pr_debug("[BH] Device suspend.\n");
if (priv->powersave_enabled) {
pr_debug("[BH] Device wakedown. Suspend.\n");
cw1200_reg_write_16(priv,
ST90TDS_CONTROL_REG_ID, 0);
priv->device_can_sleep = true;
}
atomic_set(&priv->bh_suspend, CW1200_BH_SUSPENDED);
wake_up(&priv->bh_evt_wq);
status = wait_event_interruptible(priv->bh_wq,
CW1200_BH_RESUME == atomic_read(&priv->bh_suspend));
if (status < 0) {
wiphy_err(priv->hw->wiphy,
"Failed to wait for resume: %ld.\n",
status);
break;
}
pr_debug("[BH] Device resume.\n");
atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED);
wake_up(&priv->bh_evt_wq);
atomic_inc(&priv->bh_rx);
goto done;
}
rx:
tx += pending_tx;
pending_tx = 0;
if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg))
break;
/* Don't bother trying to rx unless we have data to read */
if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) {
ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx);
if (ret < 0)
break;
/* Double up here if there's more data.. */
if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) {
ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx);
if (ret < 0)
break;
}
}
tx:
if (tx) {
tx = 0;
BUG_ON(priv->hw_bufs_used > priv->wsm_caps.input_buffers);
tx_burst = priv->wsm_caps.input_buffers - priv->hw_bufs_used;
tx_allowed = tx_burst > 0;
if (!tx_allowed) {
/* Buffers full. Ensure we process tx
* after we handle rx..
*/
pending_tx = tx;
goto done_rx;
}
ret = cw1200_bh_tx_helper(priv, &pending_tx, &tx_burst);
if (ret < 0)
break;
if (ret > 0) /* More to transmit */
tx = ret;
/* Re-read ctrl reg */
if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg))
break;
}
done_rx:
if (priv->bh_error)
break;
if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK)
goto rx;
if (tx)
goto tx;
done:
/* Re-enable device interrupts */
priv->hwbus_ops->lock(priv->hwbus_priv);
__cw1200_irq_enable(priv, 1);
priv->hwbus_ops->unlock(priv->hwbus_priv);
}
/* Explicitly disable device interrupts */
priv->hwbus_ops->lock(priv->hwbus_priv);
__cw1200_irq_enable(priv, 0);
priv->hwbus_ops->unlock(priv->hwbus_priv);
if (!term) {
pr_err("[BH] Fatal error, exiting.\n");
priv->bh_error = 1;
/* TODO: schedule_work(recovery) */
}
return 0;
}
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Device handling thread interface for mac80211 ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#ifndef CW1200_BH_H
#define CW1200_BH_H
/* extern */ struct cw1200_common;
int cw1200_register_bh(struct cw1200_common *priv);
void cw1200_unregister_bh(struct cw1200_common *priv);
void cw1200_irq_handler(struct cw1200_common *priv);
void cw1200_bh_wakeup(struct cw1200_common *priv);
int cw1200_bh_suspend(struct cw1200_common *priv);
int cw1200_bh_resume(struct cw1200_common *priv);
/* Must be called from BH thread. */
void cw1200_enable_powersave(struct cw1200_common *priv,
bool enable);
int wsm_release_tx_buffer(struct cw1200_common *priv, int count);
#endif /* CW1200_BH_H */
@@ -0,0 +1,320 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Common private data for ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on the mac80211 Prism54 code, which is
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
*
* Based on the islsm (softmac prism54) driver, which is:
* Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
*/
#ifndef CW1200_H
#define CW1200_H
#include <linux/wait.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <net/mac80211.h>
#include "queue.h"
#include "wsm.h"
#include "scan.h"
#include "txrx.h"
#include "pm.h"
/* Forward declarations */
struct hwbus_ops;
struct task_struct;
struct cw1200_debug_priv;
struct firmware;
#define CW1200_MAX_CTRL_FRAME_LEN (0x1000)
#define CW1200_MAX_STA_IN_AP_MODE (5)
#define CW1200_LINK_ID_AFTER_DTIM (CW1200_MAX_STA_IN_AP_MODE + 1)
#define CW1200_LINK_ID_UAPSD (CW1200_MAX_STA_IN_AP_MODE + 2)
#define CW1200_LINK_ID_MAX (CW1200_MAX_STA_IN_AP_MODE + 3)
#define CW1200_MAX_REQUEUE_ATTEMPTS (5)
#define CW1200_MAX_TID (8)
#define CW1200_BLOCK_ACK_CNT (30)
#define CW1200_BLOCK_ACK_THLD (800)
#define CW1200_BLOCK_ACK_HIST (3)
#define CW1200_BLOCK_ACK_INTERVAL (1 * HZ / CW1200_BLOCK_ACK_HIST)
#define CW1200_JOIN_TIMEOUT (1 * HZ)
#define CW1200_AUTH_TIMEOUT (5 * HZ)
struct cw1200_ht_info {
struct ieee80211_sta_ht_cap ht_cap;
enum nl80211_channel_type channel_type;
u16 operation_mode;
};
/* Please keep order */
enum cw1200_join_status {
CW1200_JOIN_STATUS_PASSIVE = 0,
CW1200_JOIN_STATUS_MONITOR,
CW1200_JOIN_STATUS_JOINING,
CW1200_JOIN_STATUS_PRE_STA,
CW1200_JOIN_STATUS_STA,
CW1200_JOIN_STATUS_IBSS,
CW1200_JOIN_STATUS_AP,
};
enum cw1200_link_status {
CW1200_LINK_OFF,
CW1200_LINK_RESERVE,
CW1200_LINK_SOFT,
CW1200_LINK_HARD,
CW1200_LINK_RESET,
CW1200_LINK_RESET_REMAP,
};
extern int cw1200_power_mode;
extern const char * const cw1200_fw_types[];
struct cw1200_link_entry {
unsigned long timestamp;
enum cw1200_link_status status;
enum cw1200_link_status prev_status;
u8 mac[ETH_ALEN];
u8 buffered[CW1200_MAX_TID];
struct sk_buff_head rx_queue;
};
struct cw1200_common {
/* interfaces to the rest of the stack */
struct ieee80211_hw *hw;
struct ieee80211_vif *vif;
struct device *pdev;
/* Statistics */
struct ieee80211_low_level_stats stats;
/* Our macaddr */
u8 mac_addr[ETH_ALEN];
/* Hardware interface */
const struct hwbus_ops *hwbus_ops;
struct hwbus_priv *hwbus_priv;
/* Hardware information */
enum {
HIF_9000_SILICON_VERSATILE = 0,
HIF_8601_VERSATILE,
HIF_8601_SILICON,
} hw_type;
enum {
CW1200_HW_REV_CUT10 = 10,
CW1200_HW_REV_CUT11 = 11,
CW1200_HW_REV_CUT20 = 20,
CW1200_HW_REV_CUT22 = 22,
CW1X60_HW_REV = 40,
} hw_revision;
int hw_refclk;
bool hw_have_5ghz;
const struct firmware *sdd;
char *sdd_path;
struct cw1200_debug_priv *debug;
struct workqueue_struct *workqueue;
struct mutex conf_mutex;
struct cw1200_queue tx_queue[4];
struct cw1200_queue_stats tx_queue_stats;
int tx_burst_idx;
/* firmware/hardware info */
unsigned int tx_hdr_len;
/* Radio data */
int output_power;
/* BBP/MAC state */
struct ieee80211_rate *rates;
struct ieee80211_rate *mcs_rates;
struct ieee80211_channel *channel;
struct wsm_edca_params edca;
struct wsm_tx_queue_params tx_queue_params;
struct wsm_mib_association_mode association_mode;
struct wsm_set_bss_params bss_params;
struct cw1200_ht_info ht_info;
struct wsm_set_pm powersave_mode;
struct wsm_set_pm firmware_ps_mode;
int cqm_rssi_thold;
unsigned cqm_rssi_hyst;
bool cqm_use_rssi;
int cqm_beacon_loss_count;
int channel_switch_in_progress;
wait_queue_head_t channel_switch_done;
u8 long_frame_max_tx_count;
u8 short_frame_max_tx_count;
int mode;
bool enable_beacon;
int beacon_int;
bool listening;
struct wsm_rx_filter rx_filter;
struct wsm_mib_multicast_filter multicast_filter;
bool has_multicast_subscription;
bool disable_beacon_filter;
struct work_struct update_filtering_work;
struct work_struct set_beacon_wakeup_period_work;
u8 ba_rx_tid_mask;
u8 ba_tx_tid_mask;
struct cw1200_pm_state pm_state;
struct wsm_p2p_ps_modeinfo p2p_ps_modeinfo;
struct wsm_uapsd_info uapsd_info;
bool setbssparams_done;
bool bt_present;
u8 conf_listen_interval;
u32 listen_interval;
u32 erp_info;
u32 rts_threshold;
/* BH */
atomic_t bh_rx;
atomic_t bh_tx;
atomic_t bh_term;
atomic_t bh_suspend;
struct workqueue_struct *bh_workqueue;
struct work_struct bh_work;
int bh_error;
wait_queue_head_t bh_wq;
wait_queue_head_t bh_evt_wq;
u8 buf_id_tx;
u8 buf_id_rx;
u8 wsm_rx_seq;
u8 wsm_tx_seq;
int hw_bufs_used;
bool powersave_enabled;
bool device_can_sleep;
/* Scan status */
struct cw1200_scan scan;
/* Keep cw1200 awake (WUP = 1) 1 second after each scan to avoid
* FW issue with sleeping/waking up.
*/
atomic_t recent_scan;
struct delayed_work clear_recent_scan_work;
/* WSM */
struct wsm_startup_ind wsm_caps;
struct mutex wsm_cmd_mux;
struct wsm_buf wsm_cmd_buf;
struct wsm_cmd wsm_cmd;
wait_queue_head_t wsm_cmd_wq;
wait_queue_head_t wsm_startup_done;
int firmware_ready;
atomic_t tx_lock;
/* WSM debug */
int wsm_enable_wsm_dumps;
/* WSM Join */
enum cw1200_join_status join_status;
u32 pending_frame_id;
bool join_pending;
struct delayed_work join_timeout;
struct work_struct unjoin_work;
struct work_struct join_complete_work;
int join_complete_status;
int join_dtim_period;
bool delayed_unjoin;
/* TX/RX and security */
s8 wep_default_key_id;
struct work_struct wep_key_work;
u32 key_map;
struct wsm_add_key keys[WSM_KEY_MAX_INDEX + 1];
/* AP powersave */
u32 link_id_map;
struct cw1200_link_entry link_id_db[CW1200_MAX_STA_IN_AP_MODE];
struct work_struct link_id_work;
struct delayed_work link_id_gc_work;
u32 sta_asleep_mask;
u32 pspoll_mask;
bool aid0_bit_set;
spinlock_t ps_state_lock; /* Protect power save state */
bool buffered_multicasts;
bool tx_multicast;
struct work_struct set_tim_work;
struct work_struct set_cts_work;
struct work_struct multicast_start_work;
struct work_struct multicast_stop_work;
struct timer_list mcast_timeout;
/* WSM events and CQM implementation */
spinlock_t event_queue_lock; /* Protect event queue */
struct list_head event_queue;
struct work_struct event_handler;
struct delayed_work bss_loss_work;
spinlock_t bss_loss_lock; /* Protect BSS loss state */
int bss_loss_state;
u32 bss_loss_confirm_id;
int delayed_link_loss;
struct work_struct bss_params_work;
/* TX rate policy cache */
struct tx_policy_cache tx_policy_cache;
struct work_struct tx_policy_upload_work;
/* legacy PS mode switch in suspend */
int ps_mode_switch_in_progress;
wait_queue_head_t ps_mode_switch_done;
/* Workaround for WFD testcase 6.1.10*/
struct work_struct linkid_reset_work;
u8 action_frame_sa[ETH_ALEN];
u8 action_linkid;
};
struct cw1200_sta_priv {
int link_id;
};
/* interfaces for the drivers */
int cw1200_core_probe(const struct hwbus_ops *hwbus_ops,
struct hwbus_priv *hwbus,
struct device *pdev,
struct cw1200_common **pself,
int ref_clk, const u8 *macaddr,
const char *sdd_path, bool have_5ghz);
void cw1200_core_release(struct cw1200_common *self);
#define FWLOAD_BLOCK_SIZE (1024)
static inline int cw1200_is_ht(const struct cw1200_ht_info *ht_info)
{
return ht_info->channel_type != NL80211_CHAN_NO_HT;
}
static inline int cw1200_ht_greenfield(const struct cw1200_ht_info *ht_info)
{
return cw1200_is_ht(ht_info) &&
(ht_info->ht_cap.cap & IEEE80211_HT_CAP_GRN_FLD) &&
!(ht_info->operation_mode &
IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT);
}
static inline int cw1200_ht_ampdu_density(const struct cw1200_ht_info *ht_info)
{
if (!cw1200_is_ht(ht_info))
return 0;
return ht_info->ht_cap.ampdu_density;
}
#endif /* CW1200_H */
@@ -0,0 +1,425 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Mac80211 SDIO driver for ST-Ericsson CW1200 device
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/mmc/host.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/card.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/sdio_ids.h>
#include <net/mac80211.h>
#include "cw1200.h"
#include "hwbus.h"
#include <linux/platform_data/net-cw1200.h>
#include "hwio.h"
MODULE_AUTHOR("Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>");
MODULE_DESCRIPTION("mac80211 ST-Ericsson CW1200 SDIO driver");
MODULE_LICENSE("GPL");
#define SDIO_BLOCK_SIZE (512)
/* Default platform data for Sagrad modules */
static struct cw1200_platform_data_sdio sagrad_109x_evk_platform_data = {
.ref_clk = 38400,
.have_5ghz = false,
.sdd_file = "sdd_sagrad_1091_1098.bin",
};
/* Allow platform data to be overridden */
static struct cw1200_platform_data_sdio *global_plat_data = &sagrad_109x_evk_platform_data;
void __init cw1200_sdio_set_platform_data(struct cw1200_platform_data_sdio *pdata)
{
global_plat_data = pdata;
}
struct hwbus_priv {
struct sdio_func *func;
struct cw1200_common *core;
const struct cw1200_platform_data_sdio *pdata;
};
static const struct sdio_device_id cw1200_sdio_ids[] = {
{ SDIO_DEVICE(SDIO_VENDOR_ID_STE, SDIO_DEVICE_ID_STE_CW1200) },
{ /* end: all zeroes */ },
};
MODULE_DEVICE_TABLE(sdio, cw1200_sdio_ids);
/* hwbus_ops implemetation */
static int cw1200_sdio_memcpy_fromio(struct hwbus_priv *self,
unsigned int addr,
void *dst, int count)
{
return sdio_memcpy_fromio(self->func, dst, addr, count);
}
static int cw1200_sdio_memcpy_toio(struct hwbus_priv *self,
unsigned int addr,
const void *src, int count)
{
return sdio_memcpy_toio(self->func, addr, (void *)src, count);
}
static void cw1200_sdio_lock(struct hwbus_priv *self)
{
sdio_claim_host(self->func);
}
static void cw1200_sdio_unlock(struct hwbus_priv *self)
{
sdio_release_host(self->func);
}
static void cw1200_sdio_irq_handler(struct sdio_func *func)
{
struct hwbus_priv *self = sdio_get_drvdata(func);
/* note: sdio_host already claimed here. */
if (self->core)
cw1200_irq_handler(self->core);
}
static irqreturn_t cw1200_gpio_hardirq(int irq, void *dev_id)
{
return IRQ_WAKE_THREAD;
}
static irqreturn_t cw1200_gpio_irq(int irq, void *dev_id)
{
struct hwbus_priv *self = dev_id;
if (self->core) {
cw1200_sdio_lock(self);
cw1200_irq_handler(self->core);
cw1200_sdio_unlock(self);
return IRQ_HANDLED;
} else {
return IRQ_NONE;
}
}
static int cw1200_request_irq(struct hwbus_priv *self)
{
int ret;
u8 cccr;
cccr = sdio_f0_readb(self->func, SDIO_CCCR_IENx, &ret);
if (WARN_ON(ret))
goto err;
/* Master interrupt enable ... */
cccr |= BIT(0);
/* ... for our function */
cccr |= BIT(self->func->num);
sdio_f0_writeb(self->func, cccr, SDIO_CCCR_IENx, &ret);
if (WARN_ON(ret))
goto err;
ret = enable_irq_wake(self->pdata->irq);
if (WARN_ON(ret))
goto err;
/* Request the IRQ */
ret = request_threaded_irq(self->pdata->irq, cw1200_gpio_hardirq,
cw1200_gpio_irq,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"cw1200_wlan_irq", self);
if (WARN_ON(ret))
goto err;
return 0;
err:
return ret;
}
static int cw1200_sdio_irq_subscribe(struct hwbus_priv *self)
{
int ret = 0;
pr_debug("SW IRQ subscribe\n");
sdio_claim_host(self->func);
if (self->pdata->irq)
ret = cw1200_request_irq(self);
else
ret = sdio_claim_irq(self->func, cw1200_sdio_irq_handler);
sdio_release_host(self->func);
return ret;
}
static int cw1200_sdio_irq_unsubscribe(struct hwbus_priv *self)
{
int ret = 0;
pr_debug("SW IRQ unsubscribe\n");
if (self->pdata->irq) {
disable_irq_wake(self->pdata->irq);
free_irq(self->pdata->irq, self);
} else {
sdio_claim_host(self->func);
ret = sdio_release_irq(self->func);
sdio_release_host(self->func);
}
return ret;
}
/* Like the rest of the driver, this only supports one device per system */
static struct gpio_desc *cw1200_reset;
static struct gpio_desc *cw1200_powerup;
static int cw1200_sdio_off(const struct cw1200_platform_data_sdio *pdata)
{
if (cw1200_reset) {
gpiod_set_value(cw1200_reset, 0);
msleep(30); /* Min is 2 * CLK32K cycles */
}
if (pdata->power_ctrl)
pdata->power_ctrl(pdata, false);
if (pdata->clk_ctrl)
pdata->clk_ctrl(pdata, false);
return 0;
}
static int cw1200_sdio_on(const struct cw1200_platform_data_sdio *pdata)
{
/* Ensure I/Os are pulled low (reset is active low) */
cw1200_reset = devm_gpiod_get_optional(NULL, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(cw1200_reset)) {
pr_err("could not get CW1200 SDIO reset GPIO\n");
return PTR_ERR(cw1200_reset);
}
gpiod_set_consumer_name(cw1200_reset, "cw1200_wlan_reset");
cw1200_powerup = devm_gpiod_get_optional(NULL, "powerup", GPIOD_OUT_LOW);
if (IS_ERR(cw1200_powerup)) {
pr_err("could not get CW1200 SDIO powerup GPIO\n");
return PTR_ERR(cw1200_powerup);
}
gpiod_set_consumer_name(cw1200_powerup, "cw1200_wlan_powerup");
if (cw1200_reset || cw1200_powerup)
msleep(10); /* Settle time? */
/* Enable 3v3 and 1v8 to hardware */
if (pdata->power_ctrl) {
if (pdata->power_ctrl(pdata, true)) {
pr_err("power_ctrl() failed!\n");
return -1;
}
}
/* Enable CLK32K */
if (pdata->clk_ctrl) {
if (pdata->clk_ctrl(pdata, true)) {
pr_err("clk_ctrl() failed!\n");
return -1;
}
msleep(10); /* Delay until clock is stable for 2 cycles */
}
/* Enable POWERUP signal */
if (cw1200_powerup) {
gpiod_set_value(cw1200_powerup, 1);
msleep(250); /* or more..? */
}
/* Deassert RSTn signal, note active low */
if (cw1200_reset) {
gpiod_set_value(cw1200_reset, 0);
msleep(50); /* Or more..? */
}
return 0;
}
static size_t cw1200_sdio_align_size(struct hwbus_priv *self, size_t size)
{
if (self->pdata->no_nptb)
size = round_up(size, SDIO_BLOCK_SIZE);
else
size = sdio_align_size(self->func, size);
return size;
}
static int cw1200_sdio_pm(struct hwbus_priv *self, bool suspend)
{
int ret = 0;
if (self->pdata->irq)
ret = irq_set_irq_wake(self->pdata->irq, suspend);
return ret;
}
static const struct hwbus_ops cw1200_sdio_hwbus_ops = {
.hwbus_memcpy_fromio = cw1200_sdio_memcpy_fromio,
.hwbus_memcpy_toio = cw1200_sdio_memcpy_toio,
.lock = cw1200_sdio_lock,
.unlock = cw1200_sdio_unlock,
.align_size = cw1200_sdio_align_size,
.power_mgmt = cw1200_sdio_pm,
};
/* Probe Function to be called by SDIO stack when device is discovered */
static int cw1200_sdio_probe(struct sdio_func *func,
const struct sdio_device_id *id)
{
struct hwbus_priv *self;
int status;
pr_info("cw1200_wlan_sdio: Probe called\n");
/* We are only able to handle the wlan function */
if (func->num != 0x01)
return -ENODEV;
self = kzalloc_obj(*self);
if (!self) {
pr_err("Can't allocate SDIO hwbus_priv.\n");
return -ENOMEM;
}
func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
self->pdata = global_plat_data; /* FIXME */
self->func = func;
sdio_set_drvdata(func, self);
sdio_claim_host(func);
sdio_enable_func(func);
sdio_release_host(func);
status = cw1200_sdio_irq_subscribe(self);
status = cw1200_core_probe(&cw1200_sdio_hwbus_ops,
self, &func->dev, &self->core,
self->pdata->ref_clk,
self->pdata->macaddr,
self->pdata->sdd_file,
self->pdata->have_5ghz);
if (status) {
cw1200_sdio_irq_unsubscribe(self);
sdio_claim_host(func);
sdio_disable_func(func);
sdio_release_host(func);
sdio_set_drvdata(func, NULL);
kfree(self);
}
return status;
}
/* Disconnect Function to be called by SDIO stack when
* device is disconnected
*/
static void cw1200_sdio_disconnect(struct sdio_func *func)
{
struct hwbus_priv *self = sdio_get_drvdata(func);
if (self) {
cw1200_sdio_irq_unsubscribe(self);
if (self->core) {
cw1200_core_release(self->core);
self->core = NULL;
}
sdio_claim_host(func);
sdio_disable_func(func);
sdio_release_host(func);
sdio_set_drvdata(func, NULL);
kfree(self);
}
}
#ifdef CONFIG_PM
static int cw1200_sdio_suspend(struct device *dev)
{
int ret;
struct sdio_func *func = dev_to_sdio_func(dev);
struct hwbus_priv *self = sdio_get_drvdata(func);
if (!cw1200_can_suspend(self->core))
return -EAGAIN;
/* Notify SDIO that CW1200 will remain powered during suspend */
ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
if (ret)
pr_err("Error setting SDIO pm flags: %i\n", ret);
return ret;
}
static int cw1200_sdio_resume(struct device *dev)
{
return 0;
}
static const struct dev_pm_ops cw1200_pm_ops = {
.suspend = cw1200_sdio_suspend,
.resume = cw1200_sdio_resume,
};
#endif
static struct sdio_driver sdio_driver = {
.name = "cw1200_wlan_sdio",
.id_table = cw1200_sdio_ids,
.probe = cw1200_sdio_probe,
.remove = cw1200_sdio_disconnect,
#ifdef CONFIG_PM
.drv = {
.pm = &cw1200_pm_ops,
}
#endif
};
/* Init Module function -> Called by insmod */
static int __init cw1200_sdio_init(void)
{
const struct cw1200_platform_data_sdio *pdata;
int ret;
/* FIXME -- this won't support multiple devices */
pdata = global_plat_data;
if (cw1200_sdio_on(pdata)) {
ret = -1;
goto err;
}
ret = sdio_register_driver(&sdio_driver);
if (ret)
goto err;
return 0;
err:
cw1200_sdio_off(pdata);
return ret;
}
/* Called at Driver Unloading */
static void __exit cw1200_sdio_exit(void)
{
const struct cw1200_platform_data_sdio *pdata;
/* FIXME -- this won't support multiple devices */
pdata = global_plat_data;
sdio_unregister_driver(&sdio_driver);
cw1200_sdio_off(pdata);
}
module_init(cw1200_sdio_init);
module_exit(cw1200_sdio_exit);
@@ -0,0 +1,471 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Mac80211 SPI driver for ST-Ericsson CW1200 device
*
* Copyright (c) 2011, Sagrad Inc.
* Author: Solomon Peachy <speachy@sagrad.com>
*
* Based on cw1200_sdio.c
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <net/mac80211.h>
#include <linux/spi/spi.h>
#include <linux/device.h>
#include "cw1200.h"
#include "hwbus.h"
#include <linux/platform_data/net-cw1200.h>
#include "hwio.h"
MODULE_AUTHOR("Solomon Peachy <speachy@sagrad.com>");
MODULE_DESCRIPTION("mac80211 ST-Ericsson CW1200 SPI driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:cw1200_wlan_spi");
/* #define SPI_DEBUG */
struct hwbus_priv {
struct spi_device *func;
struct cw1200_common *core;
const struct cw1200_platform_data_spi *pdata;
spinlock_t lock; /* Serialize all bus operations */
wait_queue_head_t wq;
struct gpio_desc *reset;
struct gpio_desc *powerup;
int claimed;
};
#define SDIO_TO_SPI_ADDR(addr) ((addr & 0x1f)>>2)
#define SET_WRITE 0x7FFF /* usage: and operation */
#define SET_READ 0x8000 /* usage: or operation */
/* Notes on byte ordering:
LE: B0 B1 B2 B3
BE: B3 B2 B1 B0
Hardware expects 32-bit data to be written as 16-bit BE words:
B1 B0 B3 B2
*/
static int cw1200_spi_memcpy_fromio(struct hwbus_priv *self,
unsigned int addr,
void *dst, int count)
{
int ret, i;
u16 regaddr;
struct spi_message m;
struct spi_transfer t_addr = {
.tx_buf = &regaddr,
.len = sizeof(regaddr),
};
struct spi_transfer t_msg = {
.rx_buf = dst,
.len = count,
};
regaddr = (SDIO_TO_SPI_ADDR(addr))<<12;
regaddr |= SET_READ;
regaddr |= (count>>1);
#ifdef SPI_DEBUG
pr_info("READ : %04d from 0x%02x (%04x)\n", count, addr, regaddr);
#endif
/* Header is LE16 */
regaddr = (__force u16)cpu_to_le16(regaddr);
/* We have to byteswap if the SPI bus is limited to 8b operation
or we are running on a Big Endian system
*/
#if defined(__LITTLE_ENDIAN)
if (self->func->bits_per_word == 8)
#endif
regaddr = swab16(regaddr);
spi_message_init(&m);
spi_message_add_tail(&t_addr, &m);
spi_message_add_tail(&t_msg, &m);
ret = spi_sync(self->func, &m);
#ifdef SPI_DEBUG
pr_info("READ : ");
for (i = 0; i < t_addr.len; i++)
printk("%02x ", ((u8 *)t_addr.tx_buf)[i]);
printk(" : ");
for (i = 0; i < t_msg.len; i++)
printk("%02x ", ((u8 *)t_msg.rx_buf)[i]);
printk("\n");
#endif
/* We have to byteswap if the SPI bus is limited to 8b operation
or we are running on a Big Endian system
*/
#if defined(__LITTLE_ENDIAN)
if (self->func->bits_per_word == 8)
#endif
{
uint16_t *buf = (uint16_t *)dst;
for (i = 0; i < ((count + 1) >> 1); i++)
buf[i] = swab16(buf[i]);
}
return ret;
}
static int cw1200_spi_memcpy_toio(struct hwbus_priv *self,
unsigned int addr,
const void *src, int count)
{
int rval, i;
u16 regaddr;
struct spi_transfer t_addr = {
.tx_buf = &regaddr,
.len = sizeof(regaddr),
};
struct spi_transfer t_msg = {
.tx_buf = src,
.len = count,
};
struct spi_message m;
regaddr = (SDIO_TO_SPI_ADDR(addr))<<12;
regaddr &= SET_WRITE;
regaddr |= (count>>1);
#ifdef SPI_DEBUG
pr_info("WRITE: %04d to 0x%02x (%04x)\n", count, addr, regaddr);
#endif
/* Header is LE16 */
regaddr = (__force u16)cpu_to_le16(regaddr);
/* We have to byteswap if the SPI bus is limited to 8b operation
or we are running on a Big Endian system
*/
#if defined(__LITTLE_ENDIAN)
if (self->func->bits_per_word == 8)
#endif
{
uint16_t *buf = (uint16_t *)src;
regaddr = swab16(regaddr);
for (i = 0; i < ((count + 1) >> 1); i++)
buf[i] = swab16(buf[i]);
}
#ifdef SPI_DEBUG
pr_info("WRITE: ");
for (i = 0; i < t_addr.len; i++)
printk("%02x ", ((u8 *)t_addr.tx_buf)[i]);
printk(" : ");
for (i = 0; i < t_msg.len; i++)
printk("%02x ", ((u8 *)t_msg.tx_buf)[i]);
printk("\n");
#endif
spi_message_init(&m);
spi_message_add_tail(&t_addr, &m);
spi_message_add_tail(&t_msg, &m);
rval = spi_sync(self->func, &m);
#ifdef SPI_DEBUG
pr_info("WROTE: %d\n", m.actual_length);
#endif
#if defined(__LITTLE_ENDIAN)
/* We have to byteswap if the SPI bus is limited to 8b operation */
if (self->func->bits_per_word == 8)
#endif
{
uint16_t *buf = (uint16_t *)src;
for (i = 0; i < ((count + 1) >> 1); i++)
buf[i] = swab16(buf[i]);
}
return rval;
}
static void cw1200_spi_lock(struct hwbus_priv *self)
{
unsigned long flags;
DECLARE_WAITQUEUE(wait, current);
might_sleep();
add_wait_queue(&self->wq, &wait);
spin_lock_irqsave(&self->lock, flags);
while (1) {
set_current_state(TASK_UNINTERRUPTIBLE);
if (!self->claimed)
break;
spin_unlock_irqrestore(&self->lock, flags);
schedule();
spin_lock_irqsave(&self->lock, flags);
}
set_current_state(TASK_RUNNING);
self->claimed = 1;
spin_unlock_irqrestore(&self->lock, flags);
remove_wait_queue(&self->wq, &wait);
return;
}
static void cw1200_spi_unlock(struct hwbus_priv *self)
{
unsigned long flags;
spin_lock_irqsave(&self->lock, flags);
self->claimed = 0;
spin_unlock_irqrestore(&self->lock, flags);
wake_up(&self->wq);
return;
}
static irqreturn_t cw1200_spi_irq_handler(int irq, void *dev_id)
{
struct hwbus_priv *self = dev_id;
if (self->core) {
cw1200_spi_lock(self);
cw1200_irq_handler(self->core);
cw1200_spi_unlock(self);
return IRQ_HANDLED;
} else {
return IRQ_NONE;
}
}
static int cw1200_spi_irq_subscribe(struct hwbus_priv *self)
{
int ret;
pr_debug("SW IRQ subscribe\n");
ret = request_threaded_irq(self->func->irq, NULL,
cw1200_spi_irq_handler,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"cw1200_wlan_irq", self);
if (WARN_ON(ret < 0))
goto exit;
ret = enable_irq_wake(self->func->irq);
if (WARN_ON(ret))
goto free_irq;
return 0;
free_irq:
free_irq(self->func->irq, self);
exit:
return ret;
}
static void cw1200_spi_irq_unsubscribe(struct hwbus_priv *self)
{
pr_debug("SW IRQ unsubscribe\n");
disable_irq_wake(self->func->irq);
free_irq(self->func->irq, self);
}
static int cw1200_spi_off(struct hwbus_priv *self, const struct cw1200_platform_data_spi *pdata)
{
if (self->reset) {
/* Assert RESET, note active low */
gpiod_set_value(self->reset, 1);
msleep(30); /* Min is 2 * CLK32K cycles */
}
if (pdata->power_ctrl)
pdata->power_ctrl(pdata, false);
if (pdata->clk_ctrl)
pdata->clk_ctrl(pdata, false);
return 0;
}
static int cw1200_spi_on(struct hwbus_priv *self, const struct cw1200_platform_data_spi *pdata)
{
/* Ensure I/Os are pulled low */
gpiod_direction_output(self->reset, 1); /* Active low */
gpiod_direction_output(self->powerup, 0);
if (self->reset || self->powerup)
msleep(10); /* Settle time? */
/* Enable 3v3 and 1v8 to hardware */
if (pdata->power_ctrl) {
if (pdata->power_ctrl(pdata, true)) {
pr_err("power_ctrl() failed!\n");
return -1;
}
}
/* Enable CLK32K */
if (pdata->clk_ctrl) {
if (pdata->clk_ctrl(pdata, true)) {
pr_err("clk_ctrl() failed!\n");
return -1;
}
msleep(10); /* Delay until clock is stable for 2 cycles */
}
/* Enable POWERUP signal */
if (self->powerup) {
gpiod_set_value(self->powerup, 1);
msleep(250); /* or more..? */
}
/* Assert RSTn signal, note active low */
if (self->reset) {
gpiod_set_value(self->reset, 0);
msleep(50); /* Or more..? */
}
return 0;
}
static size_t cw1200_spi_align_size(struct hwbus_priv *self, size_t size)
{
return size & 1 ? size + 1 : size;
}
static int cw1200_spi_pm(struct hwbus_priv *self, bool suspend)
{
return irq_set_irq_wake(self->func->irq, suspend);
}
static const struct hwbus_ops cw1200_spi_hwbus_ops = {
.hwbus_memcpy_fromio = cw1200_spi_memcpy_fromio,
.hwbus_memcpy_toio = cw1200_spi_memcpy_toio,
.lock = cw1200_spi_lock,
.unlock = cw1200_spi_unlock,
.align_size = cw1200_spi_align_size,
.power_mgmt = cw1200_spi_pm,
};
/* Probe Function to be called by SPI stack when device is discovered */
static int cw1200_spi_probe(struct spi_device *func)
{
const struct cw1200_platform_data_spi *plat_data =
dev_get_platdata(&func->dev);
struct hwbus_priv *self;
int status;
/* Sanity check speed */
if (func->max_speed_hz > 52000000)
func->max_speed_hz = 52000000;
if (func->max_speed_hz < 1000000)
func->max_speed_hz = 1000000;
/* Fix up transfer size */
if (plat_data->spi_bits_per_word)
func->bits_per_word = plat_data->spi_bits_per_word;
if (!func->bits_per_word)
func->bits_per_word = 16;
/* And finally.. */
func->mode = SPI_MODE_0;
pr_info("cw1200_wlan_spi: Probe called (CS %d M %d BPW %d CLK %d)\n",
spi_get_chipselect(func, 0), func->mode, func->bits_per_word,
func->max_speed_hz);
self = devm_kzalloc(&func->dev, sizeof(*self), GFP_KERNEL);
if (!self) {
pr_err("Can't allocate SPI hwbus_priv.");
return -ENOMEM;
}
/* Request reset asserted */
self->reset = devm_gpiod_get_optional(&func->dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(self->reset))
return dev_err_probe(&func->dev, PTR_ERR(self->reset),
"could not get reset GPIO\n");
gpiod_set_consumer_name(self->reset, "cw1200_wlan_reset");
self->powerup = devm_gpiod_get_optional(&func->dev, "powerup", GPIOD_OUT_LOW);
if (IS_ERR(self->powerup))
return dev_err_probe(&func->dev, PTR_ERR(self->powerup),
"could not get powerup GPIO\n");
gpiod_set_consumer_name(self->reset, "cw1200_wlan_powerup");
if (cw1200_spi_on(self, plat_data)) {
pr_err("spi_on() failed!\n");
return -ENODEV;
}
if (spi_setup(func)) {
pr_err("spi_setup() failed!\n");
return -ENODEV;
}
self->pdata = plat_data;
self->func = func;
spin_lock_init(&self->lock);
spi_set_drvdata(func, self);
init_waitqueue_head(&self->wq);
status = cw1200_spi_irq_subscribe(self);
status = cw1200_core_probe(&cw1200_spi_hwbus_ops,
self, &func->dev, &self->core,
self->pdata->ref_clk,
self->pdata->macaddr,
self->pdata->sdd_file,
self->pdata->have_5ghz);
if (status) {
cw1200_spi_irq_unsubscribe(self);
cw1200_spi_off(self, plat_data);
}
return status;
}
/* Disconnect Function to be called by SPI stack when device is disconnected */
static void cw1200_spi_disconnect(struct spi_device *func)
{
struct hwbus_priv *self = spi_get_drvdata(func);
if (self) {
cw1200_spi_irq_unsubscribe(self);
if (self->core) {
cw1200_core_release(self->core);
self->core = NULL;
}
cw1200_spi_off(self, dev_get_platdata(&func->dev));
}
}
static int __maybe_unused cw1200_spi_suspend(struct device *dev)
{
struct hwbus_priv *self = spi_get_drvdata(to_spi_device(dev));
if (self && !cw1200_can_suspend(self->core))
return -EAGAIN;
/* XXX notify host that we have to keep CW1200 powered on? */
return 0;
}
static SIMPLE_DEV_PM_OPS(cw1200_pm_ops, cw1200_spi_suspend, NULL);
static struct spi_driver spi_driver = {
.probe = cw1200_spi_probe,
.remove = cw1200_spi_disconnect,
.driver = {
.name = "cw1200_wlan_spi",
.pm = IS_ENABLED(CONFIG_PM) ? &cw1200_pm_ops : NULL,
},
};
module_spi_driver(spi_driver);
@@ -0,0 +1,388 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers
* DebugFS code
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include "cw1200.h"
#include "debug.h"
#include "fwio.h"
/* join_status */
static const char * const cw1200_debug_join_status[] = {
"passive",
"monitor",
"station (joining)",
"station (not authenticated yet)",
"station",
"adhoc",
"access point",
};
/* WSM_JOIN_PREAMBLE_... */
static const char * const cw1200_debug_preamble[] = {
"long",
"short",
"long on 1 and 2 Mbps",
};
static const char * const cw1200_debug_link_id[] = {
"OFF",
"REQ",
"SOFT",
"HARD",
"RESET",
"RESET_REMAP",
};
static const char *cw1200_debug_mode(int mode)
{
switch (mode) {
case NL80211_IFTYPE_UNSPECIFIED:
return "unspecified";
case NL80211_IFTYPE_MONITOR:
return "monitor";
case NL80211_IFTYPE_STATION:
return "station";
case NL80211_IFTYPE_ADHOC:
return "adhoc";
case NL80211_IFTYPE_MESH_POINT:
return "mesh point";
case NL80211_IFTYPE_AP:
return "access point";
case NL80211_IFTYPE_P2P_CLIENT:
return "p2p client";
case NL80211_IFTYPE_P2P_GO:
return "p2p go";
default:
return "unsupported";
}
}
static void cw1200_queue_status_show(struct seq_file *seq,
struct cw1200_queue *q)
{
int i;
seq_printf(seq, "Queue %d:\n", q->queue_id);
seq_printf(seq, " capacity: %zu\n", q->capacity);
seq_printf(seq, " queued: %zu\n", q->num_queued);
seq_printf(seq, " pending: %zu\n", q->num_pending);
seq_printf(seq, " sent: %zu\n", q->num_sent);
seq_printf(seq, " locked: %s\n", q->tx_locked_cnt ? "yes" : "no");
seq_printf(seq, " overfull: %s\n", q->overfull ? "yes" : "no");
seq_puts(seq, " link map: 0-> ");
for (i = 0; i < q->stats->map_capacity; ++i)
seq_printf(seq, "%.2d ", q->link_map_cache[i]);
seq_printf(seq, "<-%zu\n", q->stats->map_capacity);
}
static void cw1200_debug_print_map(struct seq_file *seq,
struct cw1200_common *priv,
const char *label,
u32 map)
{
int i;
seq_printf(seq, "%s0-> ", label);
for (i = 0; i < priv->tx_queue_stats.map_capacity; ++i)
seq_printf(seq, "%s ", (map & BIT(i)) ? "**" : "..");
seq_printf(seq, "<-%zu\n", priv->tx_queue_stats.map_capacity - 1);
}
static int cw1200_status_show(struct seq_file *seq, void *v)
{
int i;
struct list_head *item;
struct cw1200_common *priv = seq->private;
struct cw1200_debug_priv *d = priv->debug;
seq_puts(seq, "CW1200 Wireless LAN driver status\n");
seq_printf(seq, "Hardware: %d.%d\n",
priv->wsm_caps.hw_id,
priv->wsm_caps.hw_subid);
seq_printf(seq, "Firmware: %s %d.%d\n",
cw1200_fw_types[priv->wsm_caps.fw_type],
priv->wsm_caps.fw_ver,
priv->wsm_caps.fw_build);
seq_printf(seq, "FW API: %d\n",
priv->wsm_caps.fw_api);
seq_printf(seq, "FW caps: 0x%.4X\n",
priv->wsm_caps.fw_cap);
seq_printf(seq, "FW label: '%s'\n",
priv->wsm_caps.fw_label);
seq_printf(seq, "Mode: %s%s\n",
cw1200_debug_mode(priv->mode),
priv->listening ? " (listening)" : "");
seq_printf(seq, "Join state: %s\n",
cw1200_debug_join_status[priv->join_status]);
if (priv->channel)
seq_printf(seq, "Channel: %d%s\n",
priv->channel->hw_value,
priv->channel_switch_in_progress ?
" (switching)" : "");
if (priv->rx_filter.promiscuous)
seq_puts(seq, "Filter: promisc\n");
else if (priv->rx_filter.fcs)
seq_puts(seq, "Filter: fcs\n");
if (priv->rx_filter.bssid)
seq_puts(seq, "Filter: bssid\n");
if (!priv->disable_beacon_filter)
seq_puts(seq, "Filter: beacons\n");
if (priv->enable_beacon ||
priv->mode == NL80211_IFTYPE_AP ||
priv->mode == NL80211_IFTYPE_ADHOC ||
priv->mode == NL80211_IFTYPE_MESH_POINT ||
priv->mode == NL80211_IFTYPE_P2P_GO)
seq_printf(seq, "Beaconing: %s\n",
priv->enable_beacon ?
"enabled" : "disabled");
for (i = 0; i < 4; ++i)
seq_printf(seq, "EDCA(%d): %d, %d, %d, %d, %d\n", i,
priv->edca.params[i].cwmin,
priv->edca.params[i].cwmax,
priv->edca.params[i].aifns,
priv->edca.params[i].txop_limit,
priv->edca.params[i].max_rx_lifetime);
if (priv->join_status == CW1200_JOIN_STATUS_STA) {
static const char *pm_mode = "unknown";
switch (priv->powersave_mode.mode) {
case WSM_PSM_ACTIVE:
pm_mode = "off";
break;
case WSM_PSM_PS:
pm_mode = "on";
break;
case WSM_PSM_FAST_PS:
pm_mode = "dynamic";
break;
}
seq_printf(seq, "Preamble: %s\n",
cw1200_debug_preamble[priv->association_mode.preamble]);
seq_printf(seq, "AMPDU spcn: %d\n",
priv->association_mode.mpdu_start_spacing);
seq_printf(seq, "Basic rate: 0x%.8X\n",
le32_to_cpu(priv->association_mode.basic_rate_set));
seq_printf(seq, "Bss lost: %d beacons\n",
priv->bss_params.beacon_lost_count);
seq_printf(seq, "AID: %d\n",
priv->bss_params.aid);
seq_printf(seq, "Rates: 0x%.8X\n",
priv->bss_params.operational_rate_set);
seq_printf(seq, "Powersave: %s\n", pm_mode);
}
seq_printf(seq, "HT: %s\n",
cw1200_is_ht(&priv->ht_info) ? "on" : "off");
if (cw1200_is_ht(&priv->ht_info)) {
seq_printf(seq, "Greenfield: %s\n",
cw1200_ht_greenfield(&priv->ht_info) ? "yes" : "no");
seq_printf(seq, "AMPDU dens: %d\n",
cw1200_ht_ampdu_density(&priv->ht_info));
}
seq_printf(seq, "RSSI thold: %d\n",
priv->cqm_rssi_thold);
seq_printf(seq, "RSSI hyst: %d\n",
priv->cqm_rssi_hyst);
seq_printf(seq, "Long retr: %d\n",
priv->long_frame_max_tx_count);
seq_printf(seq, "Short retr: %d\n",
priv->short_frame_max_tx_count);
spin_lock_bh(&priv->tx_policy_cache.lock);
i = 0;
list_for_each(item, &priv->tx_policy_cache.used)
++i;
spin_unlock_bh(&priv->tx_policy_cache.lock);
seq_printf(seq, "RC in use: %d\n", i);
seq_puts(seq, "\n");
for (i = 0; i < 4; ++i) {
cw1200_queue_status_show(seq, &priv->tx_queue[i]);
seq_puts(seq, "\n");
}
cw1200_debug_print_map(seq, priv, "Link map: ",
priv->link_id_map);
cw1200_debug_print_map(seq, priv, "Asleep map: ",
priv->sta_asleep_mask);
cw1200_debug_print_map(seq, priv, "PSPOLL map: ",
priv->pspoll_mask);
seq_puts(seq, "\n");
for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) {
if (priv->link_id_db[i].status) {
seq_printf(seq, "Link %d: %s, %pM\n",
i + 1,
cw1200_debug_link_id[priv->link_id_db[i].status],
priv->link_id_db[i].mac);
}
}
seq_puts(seq, "\n");
seq_printf(seq, "BH status: %s\n",
atomic_read(&priv->bh_term) ? "terminated" : "alive");
seq_printf(seq, "Pending RX: %d\n",
atomic_read(&priv->bh_rx));
seq_printf(seq, "Pending TX: %d\n",
atomic_read(&priv->bh_tx));
if (priv->bh_error)
seq_printf(seq, "BH errcode: %d\n",
priv->bh_error);
seq_printf(seq, "TX bufs: %d x %d bytes\n",
priv->wsm_caps.input_buffers,
priv->wsm_caps.input_buffer_size);
seq_printf(seq, "Used bufs: %d\n",
priv->hw_bufs_used);
seq_printf(seq, "Powermgmt: %s\n",
priv->powersave_enabled ? "on" : "off");
seq_printf(seq, "Device: %s\n",
priv->device_can_sleep ? "asleep" : "awake");
spin_lock(&priv->wsm_cmd.lock);
seq_printf(seq, "WSM status: %s\n",
priv->wsm_cmd.done ? "idle" : "active");
seq_printf(seq, "WSM cmd: 0x%.4X (%td bytes)\n",
priv->wsm_cmd.cmd, priv->wsm_cmd.len);
seq_printf(seq, "WSM retval: %d\n",
priv->wsm_cmd.ret);
spin_unlock(&priv->wsm_cmd.lock);
seq_printf(seq, "Datapath: %s\n",
atomic_read(&priv->tx_lock) ? "locked" : "unlocked");
if (atomic_read(&priv->tx_lock))
seq_printf(seq, "TXlock cnt: %d\n",
atomic_read(&priv->tx_lock));
seq_printf(seq, "TXed: %d\n",
d->tx);
seq_printf(seq, "AGG TXed: %d\n",
d->tx_agg);
seq_printf(seq, "MULTI TXed: %d (%d)\n",
d->tx_multi, d->tx_multi_frames);
seq_printf(seq, "RXed: %d\n",
d->rx);
seq_printf(seq, "AGG RXed: %d\n",
d->rx_agg);
seq_printf(seq, "TX miss: %d\n",
d->tx_cache_miss);
seq_printf(seq, "TX align: %d\n",
d->tx_align);
seq_printf(seq, "TX burst: %d\n",
d->tx_burst);
seq_printf(seq, "TX TTL: %d\n",
d->tx_ttl);
seq_printf(seq, "Scan: %s\n",
atomic_read(&priv->scan.in_progress) ? "active" : "idle");
return 0;
}
DEFINE_SHOW_ATTRIBUTE(cw1200_status);
static int cw1200_counters_show(struct seq_file *seq, void *v)
{
int ret;
struct cw1200_common *priv = seq->private;
struct wsm_mib_counters_table counters;
ret = wsm_get_counters_table(priv, &counters);
if (ret)
return ret;
#define PUT_COUNTER(tab, name) \
seq_printf(seq, "%s:" tab "%d\n", #name, \
__le32_to_cpu(counters.name))
PUT_COUNTER("\t\t", plcp_errors);
PUT_COUNTER("\t\t", fcs_errors);
PUT_COUNTER("\t\t", tx_packets);
PUT_COUNTER("\t\t", rx_packets);
PUT_COUNTER("\t\t", rx_packet_errors);
PUT_COUNTER("\t", rx_decryption_failures);
PUT_COUNTER("\t\t", rx_mic_failures);
PUT_COUNTER("\t", rx_no_key_failures);
PUT_COUNTER("\t", tx_multicast_frames);
PUT_COUNTER("\t", tx_frames_success);
PUT_COUNTER("\t", tx_frame_failures);
PUT_COUNTER("\t", tx_frames_retried);
PUT_COUNTER("\t", tx_frames_multi_retried);
PUT_COUNTER("\t", rx_frame_duplicates);
PUT_COUNTER("\t\t", rts_success);
PUT_COUNTER("\t\t", rts_failures);
PUT_COUNTER("\t\t", ack_failures);
PUT_COUNTER("\t", rx_multicast_frames);
PUT_COUNTER("\t", rx_frames_success);
PUT_COUNTER("\t", rx_cmac_icv_errors);
PUT_COUNTER("\t\t", rx_cmac_replays);
PUT_COUNTER("\t", rx_mgmt_ccmp_replays);
#undef PUT_COUNTER
return 0;
}
DEFINE_SHOW_ATTRIBUTE(cw1200_counters);
static ssize_t cw1200_wsm_dumps(struct file *file,
const char __user *user_buf, size_t count, loff_t *ppos)
{
struct cw1200_common *priv = file->private_data;
char buf[1];
if (!count)
return -EINVAL;
if (copy_from_user(buf, user_buf, 1))
return -EFAULT;
if (buf[0] == '1')
priv->wsm_enable_wsm_dumps = 1;
else
priv->wsm_enable_wsm_dumps = 0;
return count;
}
static const struct file_operations fops_wsm_dumps = {
.open = simple_open,
.write = cw1200_wsm_dumps,
.llseek = default_llseek,
};
int cw1200_debug_init(struct cw1200_common *priv)
{
int ret = -ENOMEM;
struct cw1200_debug_priv *d = kzalloc_obj(struct cw1200_debug_priv);
priv->debug = d;
if (!d)
return ret;
d->debugfs_phy = debugfs_create_dir("cw1200",
priv->hw->wiphy->debugfsdir);
debugfs_create_file("status", 0400, d->debugfs_phy, priv,
&cw1200_status_fops);
debugfs_create_file("counters", 0400, d->debugfs_phy, priv,
&cw1200_counters_fops);
debugfs_create_file("wsm_dumps", 0200, d->debugfs_phy, priv,
&fops_wsm_dumps);
return 0;
}
void cw1200_debug_release(struct cw1200_common *priv)
{
struct cw1200_debug_priv *d = priv->debug;
if (d) {
debugfs_remove_recursive(d->debugfs_phy);
priv->debug = NULL;
kfree(d);
}
}
@@ -0,0 +1,90 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* DebugFS code for ST-Ericsson CW1200 mac80211 driver
*
* Copyright (c) 2011, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#ifndef CW1200_DEBUG_H_INCLUDED
#define CW1200_DEBUG_H_INCLUDED
struct cw1200_debug_priv {
struct dentry *debugfs_phy;
int tx;
int tx_agg;
int rx;
int rx_agg;
int tx_multi;
int tx_multi_frames;
int tx_cache_miss;
int tx_align;
int tx_ttl;
int tx_burst;
int ba_cnt;
int ba_acc;
int ba_cnt_rx;
int ba_acc_rx;
};
int cw1200_debug_init(struct cw1200_common *priv);
void cw1200_debug_release(struct cw1200_common *priv);
static inline void cw1200_debug_txed(struct cw1200_common *priv)
{
++priv->debug->tx;
}
static inline void cw1200_debug_txed_agg(struct cw1200_common *priv)
{
++priv->debug->tx_agg;
}
static inline void cw1200_debug_txed_multi(struct cw1200_common *priv,
int count)
{
++priv->debug->tx_multi;
priv->debug->tx_multi_frames += count;
}
static inline void cw1200_debug_rxed(struct cw1200_common *priv)
{
++priv->debug->rx;
}
static inline void cw1200_debug_rxed_agg(struct cw1200_common *priv)
{
++priv->debug->rx_agg;
}
static inline void cw1200_debug_tx_cache_miss(struct cw1200_common *priv)
{
++priv->debug->tx_cache_miss;
}
static inline void cw1200_debug_tx_align(struct cw1200_common *priv)
{
++priv->debug->tx_align;
}
static inline void cw1200_debug_tx_ttl(struct cw1200_common *priv)
{
++priv->debug->tx_ttl;
}
static inline void cw1200_debug_tx_burst(struct cw1200_common *priv)
{
++priv->debug->tx_burst;
}
static inline void cw1200_debug_ba(struct cw1200_common *priv,
int ba_cnt, int ba_acc,
int ba_cnt_rx, int ba_acc_rx)
{
priv->debug->ba_cnt = ba_cnt;
priv->debug->ba_acc = ba_acc;
priv->debug->ba_cnt_rx = ba_cnt_rx;
priv->debug->ba_acc_rx = ba_acc_rx;
}
#endif /* CW1200_DEBUG_H_INCLUDED */
@@ -0,0 +1,523 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Firmware I/O code for mac80211 ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on:
* ST-Ericsson UMAC CW1200 driver which is
* Copyright (c) 2010, ST-Ericsson
* Author: Ajitpal Singh <ajitpal.singh@stericsson.com>
*/
#include <linux/vmalloc.h>
#include <linux/sched.h>
#include <linux/firmware.h>
#include "cw1200.h"
#include "fwio.h"
#include "hwio.h"
#include "hwbus.h"
#include "bh.h"
static int cw1200_get_hw_type(u32 config_reg_val, int *major_revision)
{
int hw_type = -1;
u32 silicon_type = (config_reg_val >> 24) & 0x7;
u32 silicon_vers = (config_reg_val >> 31) & 0x1;
switch (silicon_type) {
case 0x00:
*major_revision = 1;
hw_type = HIF_9000_SILICON_VERSATILE;
break;
case 0x01:
case 0x02: /* CW1x00 */
case 0x04: /* CW1x60 */
*major_revision = silicon_type;
if (silicon_vers)
hw_type = HIF_8601_VERSATILE;
else
hw_type = HIF_8601_SILICON;
break;
default:
break;
}
return hw_type;
}
static int cw1200_load_firmware_cw1200(struct cw1200_common *priv)
{
int ret, block, num_blocks;
unsigned i;
u32 val32;
u32 put = 0, get = 0;
u8 *buf = NULL;
const char *fw_path;
const struct firmware *firmware = NULL;
/* Macroses are local. */
#define APB_WRITE(reg, val) \
do { \
ret = cw1200_apb_write_32(priv, CW1200_APB(reg), (val)); \
if (ret < 0) \
goto exit; \
} while (0)
#define APB_WRITE2(reg, val) \
do { \
ret = cw1200_apb_write_32(priv, CW1200_APB(reg), (val)); \
if (ret < 0) \
goto free_buffer; \
} while (0)
#define APB_READ(reg, val) \
do { \
ret = cw1200_apb_read_32(priv, CW1200_APB(reg), &(val)); \
if (ret < 0) \
goto free_buffer; \
} while (0)
#define REG_WRITE(reg, val) \
do { \
ret = cw1200_reg_write_32(priv, (reg), (val)); \
if (ret < 0) \
goto exit; \
} while (0)
#define REG_READ(reg, val) \
do { \
ret = cw1200_reg_read_32(priv, (reg), &(val)); \
if (ret < 0) \
goto exit; \
} while (0)
switch (priv->hw_revision) {
case CW1200_HW_REV_CUT10:
fw_path = FIRMWARE_CUT10;
if (!priv->sdd_path)
priv->sdd_path = SDD_FILE_10;
break;
case CW1200_HW_REV_CUT11:
fw_path = FIRMWARE_CUT11;
if (!priv->sdd_path)
priv->sdd_path = SDD_FILE_11;
break;
case CW1200_HW_REV_CUT20:
fw_path = FIRMWARE_CUT20;
if (!priv->sdd_path)
priv->sdd_path = SDD_FILE_20;
break;
case CW1200_HW_REV_CUT22:
fw_path = FIRMWARE_CUT22;
if (!priv->sdd_path)
priv->sdd_path = SDD_FILE_22;
break;
case CW1X60_HW_REV:
fw_path = FIRMWARE_CW1X60;
if (!priv->sdd_path)
priv->sdd_path = SDD_FILE_CW1X60;
break;
default:
pr_err("Invalid silicon revision %d.\n", priv->hw_revision);
return -EINVAL;
}
/* Initialize common registers */
APB_WRITE(DOWNLOAD_IMAGE_SIZE_REG, DOWNLOAD_ARE_YOU_HERE);
APB_WRITE(DOWNLOAD_PUT_REG, 0);
APB_WRITE(DOWNLOAD_GET_REG, 0);
APB_WRITE(DOWNLOAD_STATUS_REG, DOWNLOAD_PENDING);
APB_WRITE(DOWNLOAD_FLAGS_REG, 0);
/* Write the NOP Instruction */
REG_WRITE(ST90TDS_SRAM_BASE_ADDR_REG_ID, 0xFFF20000);
REG_WRITE(ST90TDS_AHB_DPORT_REG_ID, 0xEAFFFFFE);
/* Release CPU from RESET */
REG_READ(ST90TDS_CONFIG_REG_ID, val32);
val32 &= ~ST90TDS_CONFIG_CPU_RESET_BIT;
REG_WRITE(ST90TDS_CONFIG_REG_ID, val32);
/* Enable Clock */
val32 &= ~ST90TDS_CONFIG_CPU_CLK_DIS_BIT;
REG_WRITE(ST90TDS_CONFIG_REG_ID, val32);
/* Load a firmware file */
ret = request_firmware(&firmware, fw_path, priv->pdev);
if (ret) {
pr_err("Can't load firmware file %s.\n", fw_path);
goto exit;
}
buf = kmalloc(DOWNLOAD_BLOCK_SIZE, GFP_KERNEL | GFP_DMA);
if (!buf) {
pr_err("Can't allocate firmware load buffer.\n");
ret = -ENOMEM;
goto firmware_release;
}
/* Check if the bootloader is ready */
for (i = 0; i < 100; i += 1 + i / 2) {
APB_READ(DOWNLOAD_IMAGE_SIZE_REG, val32);
if (val32 == DOWNLOAD_I_AM_HERE)
break;
mdelay(i);
} /* End of for loop */
if (val32 != DOWNLOAD_I_AM_HERE) {
pr_err("Bootloader is not ready.\n");
ret = -ETIMEDOUT;
goto free_buffer;
}
/* Calculcate number of download blocks */
num_blocks = (firmware->size - 1) / DOWNLOAD_BLOCK_SIZE + 1;
/* Updating the length in Download Ctrl Area */
val32 = firmware->size; /* Explicit cast from size_t to u32 */
APB_WRITE2(DOWNLOAD_IMAGE_SIZE_REG, val32);
/* Firmware downloading loop */
for (block = 0; block < num_blocks; block++) {
size_t tx_size;
size_t block_size;
/* check the download status */
APB_READ(DOWNLOAD_STATUS_REG, val32);
if (val32 != DOWNLOAD_PENDING) {
pr_err("Bootloader reported error %d.\n", val32);
ret = -EIO;
goto free_buffer;
}
/* loop until put - get <= 24K */
for (i = 0; i < 100; i++) {
APB_READ(DOWNLOAD_GET_REG, get);
if ((put - get) <=
(DOWNLOAD_FIFO_SIZE - DOWNLOAD_BLOCK_SIZE))
break;
mdelay(i);
}
if ((put - get) > (DOWNLOAD_FIFO_SIZE - DOWNLOAD_BLOCK_SIZE)) {
pr_err("Timeout waiting for FIFO.\n");
ret = -ETIMEDOUT;
goto free_buffer;
}
/* calculate the block size */
tx_size = block_size = min_t(size_t, firmware->size - put,
DOWNLOAD_BLOCK_SIZE);
memcpy(buf, &firmware->data[put], block_size);
if (block_size < DOWNLOAD_BLOCK_SIZE) {
memset(&buf[block_size], 0,
DOWNLOAD_BLOCK_SIZE - block_size);
tx_size = DOWNLOAD_BLOCK_SIZE;
}
/* send the block to sram */
ret = cw1200_apb_write(priv,
CW1200_APB(DOWNLOAD_FIFO_OFFSET +
(put & (DOWNLOAD_FIFO_SIZE - 1))),
buf, tx_size);
if (ret < 0) {
pr_err("Can't write firmware block @ %d!\n",
put & (DOWNLOAD_FIFO_SIZE - 1));
goto free_buffer;
}
/* update the put register */
put += block_size;
APB_WRITE2(DOWNLOAD_PUT_REG, put);
} /* End of firmware download loop */
/* Wait for the download completion */
for (i = 0; i < 300; i += 1 + i / 2) {
APB_READ(DOWNLOAD_STATUS_REG, val32);
if (val32 != DOWNLOAD_PENDING)
break;
mdelay(i);
}
if (val32 != DOWNLOAD_SUCCESS) {
pr_err("Wait for download completion failed: 0x%.8X\n", val32);
ret = -ETIMEDOUT;
goto free_buffer;
} else {
pr_info("Firmware download completed.\n");
ret = 0;
}
free_buffer:
kfree(buf);
firmware_release:
release_firmware(firmware);
exit:
return ret;
#undef APB_WRITE
#undef APB_WRITE2
#undef APB_READ
#undef REG_WRITE
#undef REG_READ
}
static int config_reg_read(struct cw1200_common *priv, u32 *val)
{
switch (priv->hw_type) {
case HIF_9000_SILICON_VERSATILE: {
u16 val16;
int ret = cw1200_reg_read_16(priv,
ST90TDS_CONFIG_REG_ID,
&val16);
if (ret < 0)
return ret;
*val = val16;
return 0;
}
case HIF_8601_VERSATILE:
case HIF_8601_SILICON:
default:
cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, val);
break;
}
return 0;
}
static int config_reg_write(struct cw1200_common *priv, u32 val)
{
switch (priv->hw_type) {
case HIF_9000_SILICON_VERSATILE:
return cw1200_reg_write_16(priv,
ST90TDS_CONFIG_REG_ID,
(u16)val);
case HIF_8601_VERSATILE:
case HIF_8601_SILICON:
default:
return cw1200_reg_write_32(priv, ST90TDS_CONFIG_REG_ID, val);
}
return 0;
}
int cw1200_load_firmware(struct cw1200_common *priv)
{
int ret;
int i;
u32 val32;
u16 val16;
int major_revision = -1;
/* Read CONFIG Register */
ret = cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
if (ret < 0) {
pr_err("Can't read config register.\n");
goto out;
}
if (val32 == 0 || val32 == 0xffffffff) {
pr_err("Bad config register value (0x%08x)\n", val32);
ret = -EIO;
goto out;
}
ret = cw1200_get_hw_type(val32, &major_revision);
if (ret < 0) {
pr_err("Can't deduce hardware type.\n");
goto out;
}
priv->hw_type = ret;
/* Set DPLL Reg value, and read back to confirm writes work */
ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID,
cw1200_dpll_from_clk(priv->hw_refclk));
if (ret < 0) {
pr_err("Can't write DPLL register.\n");
goto out;
}
msleep(20);
ret = cw1200_reg_read_32(priv,
ST90TDS_TSET_GEN_R_W_REG_ID, &val32);
if (ret < 0) {
pr_err("Can't read DPLL register.\n");
goto out;
}
if (val32 != cw1200_dpll_from_clk(priv->hw_refclk)) {
pr_err("Unable to initialise DPLL register. Wrote 0x%.8X, Read 0x%.8X.\n",
cw1200_dpll_from_clk(priv->hw_refclk), val32);
ret = -EIO;
goto out;
}
/* Set wakeup bit in device */
ret = cw1200_reg_read_16(priv, ST90TDS_CONTROL_REG_ID, &val16);
if (ret < 0) {
pr_err("set_wakeup: can't read control register.\n");
goto out;
}
ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID,
val16 | ST90TDS_CONT_WUP_BIT);
if (ret < 0) {
pr_err("set_wakeup: can't write control register.\n");
goto out;
}
/* Wait for wakeup */
for (i = 0; i < 300; i += (1 + i / 2)) {
ret = cw1200_reg_read_16(priv,
ST90TDS_CONTROL_REG_ID, &val16);
if (ret < 0) {
pr_err("wait_for_wakeup: can't read control register.\n");
goto out;
}
if (val16 & ST90TDS_CONT_RDY_BIT)
break;
msleep(i);
}
if ((val16 & ST90TDS_CONT_RDY_BIT) == 0) {
pr_err("wait_for_wakeup: device is not responding.\n");
ret = -ETIMEDOUT;
goto out;
}
switch (major_revision) {
case 1:
/* CW1200 Hardware detection logic : Check for CUT1.1 */
ret = cw1200_ahb_read_32(priv, CW1200_CUT_ID_ADDR, &val32);
if (ret) {
pr_err("HW detection: can't read CUT ID.\n");
goto out;
}
switch (val32) {
case CW1200_CUT_11_ID_STR:
pr_info("CW1x00 Cut 1.1 silicon detected.\n");
priv->hw_revision = CW1200_HW_REV_CUT11;
break;
default:
pr_info("CW1x00 Cut 1.0 silicon detected.\n");
priv->hw_revision = CW1200_HW_REV_CUT10;
break;
}
/* According to ST-E, CUT<2.0 has busted BA TID0-3.
Just disable it entirely...
*/
priv->ba_rx_tid_mask = 0;
priv->ba_tx_tid_mask = 0;
break;
case 2: {
u32 ar1, ar2, ar3;
ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR, &ar1);
if (ret) {
pr_err("(1) HW detection: can't read CUT ID\n");
goto out;
}
ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR + 4, &ar2);
if (ret) {
pr_err("(2) HW detection: can't read CUT ID.\n");
goto out;
}
ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR + 8, &ar3);
if (ret) {
pr_err("(3) HW detection: can't read CUT ID.\n");
goto out;
}
if (ar1 == CW1200_CUT_22_ID_STR1 &&
ar2 == CW1200_CUT_22_ID_STR2 &&
ar3 == CW1200_CUT_22_ID_STR3) {
pr_info("CW1x00 Cut 2.2 silicon detected.\n");
priv->hw_revision = CW1200_HW_REV_CUT22;
} else {
pr_info("CW1x00 Cut 2.0 silicon detected.\n");
priv->hw_revision = CW1200_HW_REV_CUT20;
}
break;
}
case 4:
pr_info("CW1x60 silicon detected.\n");
priv->hw_revision = CW1X60_HW_REV;
break;
default:
pr_err("Unsupported silicon major revision %d.\n",
major_revision);
ret = -ENOTSUPP;
goto out;
}
/* Checking for access mode */
ret = config_reg_read(priv, &val32);
if (ret < 0) {
pr_err("Can't read config register.\n");
goto out;
}
if (!(val32 & ST90TDS_CONFIG_ACCESS_MODE_BIT)) {
pr_err("Device is already in QUEUE mode!\n");
ret = -EINVAL;
goto out;
}
switch (priv->hw_type) {
case HIF_8601_SILICON:
if (priv->hw_revision == CW1X60_HW_REV) {
pr_err("Can't handle CW1160/1260 firmware load yet.\n");
ret = -ENOTSUPP;
goto out;
}
ret = cw1200_load_firmware_cw1200(priv);
break;
default:
pr_err("Can't perform firmware load for hw type %d.\n",
priv->hw_type);
ret = -ENOTSUPP;
goto out;
}
if (ret < 0) {
pr_err("Firmware load error.\n");
goto out;
}
/* Enable interrupt signalling */
priv->hwbus_ops->lock(priv->hwbus_priv);
ret = __cw1200_irq_enable(priv, 1);
priv->hwbus_ops->unlock(priv->hwbus_priv);
if (ret < 0)
goto unsubscribe;
/* Configure device for MESSSAGE MODE */
ret = config_reg_read(priv, &val32);
if (ret < 0) {
pr_err("Can't read config register.\n");
goto unsubscribe;
}
ret = config_reg_write(priv, val32 & ~ST90TDS_CONFIG_ACCESS_MODE_BIT);
if (ret < 0) {
pr_err("Can't write config register.\n");
goto unsubscribe;
}
/* Unless we read the CONFIG Register we are
* not able to get an interrupt
*/
mdelay(10);
config_reg_read(priv, &val32);
out:
return ret;
unsubscribe:
/* Disable interrupt signalling */
priv->hwbus_ops->lock(priv->hwbus_priv);
ret = __cw1200_irq_enable(priv, 0);
priv->hwbus_ops->unlock(priv->hwbus_priv);
return ret;
}
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Firmware API for mac80211 ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on:
* ST-Ericsson UMAC CW1200 driver which is
* Copyright (c) 2010, ST-Ericsson
* Author: Ajitpal Singh <ajitpal.singh@stericsson.com>
*/
#ifndef FWIO_H_INCLUDED
#define FWIO_H_INCLUDED
#define BOOTLOADER_CW1X60 "boot_cw1x60.bin"
#define FIRMWARE_CW1X60 "wsm_cw1x60.bin"
#define FIRMWARE_CUT22 "wsm_22.bin"
#define FIRMWARE_CUT20 "wsm_20.bin"
#define FIRMWARE_CUT11 "wsm_11.bin"
#define FIRMWARE_CUT10 "wsm_10.bin"
#define SDD_FILE_CW1X60 "sdd_cw1x60.bin"
#define SDD_FILE_22 "sdd_22.bin"
#define SDD_FILE_20 "sdd_20.bin"
#define SDD_FILE_11 "sdd_11.bin"
#define SDD_FILE_10 "sdd_10.bin"
int cw1200_load_firmware(struct cw1200_common *priv);
/* SDD definitions */
#define SDD_PTA_CFG_ELT_ID 0xEB
#define SDD_REFERENCE_FREQUENCY_ELT_ID 0xc5
u32 cw1200_dpll_from_clk(u16 clk);
#endif
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Common hwbus abstraction layer interface for cw1200 wireless driver
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#ifndef CW1200_HWBUS_H
#define CW1200_HWBUS_H
struct hwbus_priv;
void cw1200_irq_handler(struct cw1200_common *priv);
/* This MUST be wrapped with hwbus_ops->lock/unlock! */
int __cw1200_irq_enable(struct cw1200_common *priv, int enable);
struct hwbus_ops {
int (*hwbus_memcpy_fromio)(struct hwbus_priv *self, unsigned int addr,
void *dst, int count);
int (*hwbus_memcpy_toio)(struct hwbus_priv *self, unsigned int addr,
const void *src, int count);
void (*lock)(struct hwbus_priv *self);
void (*unlock)(struct hwbus_priv *self);
size_t (*align_size)(struct hwbus_priv *self, size_t size);
int (*power_mgmt)(struct hwbus_priv *self, bool suspend);
};
#endif /* CW1200_HWBUS_H */
@@ -0,0 +1,309 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Low-level device IO routines for ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on:
* ST-Ericsson UMAC CW1200 driver, which is
* Copyright (c) 2010, ST-Ericsson
* Author: Ajitpal Singh <ajitpal.singh@lockless.no>
*/
#include <linux/types.h>
#include "cw1200.h"
#include "hwio.h"
#include "hwbus.h"
/* Sdio addr is 4*spi_addr */
#define SPI_REG_ADDR_TO_SDIO(spi_reg_addr) ((spi_reg_addr) << 2)
#define SDIO_ADDR17BIT(buf_id, mpf, rfu, reg_id_ofs) \
((((buf_id) & 0x1F) << 7) \
| (((mpf) & 1) << 6) \
| (((rfu) & 1) << 5) \
| (((reg_id_ofs) & 0x1F) << 0))
#define MAX_RETRY 3
static int __cw1200_reg_read(struct cw1200_common *priv, u16 addr,
void *buf, size_t buf_len, int buf_id)
{
u16 addr_sdio;
u32 sdio_reg_addr_17bit;
/* Check if buffer is aligned to 4 byte boundary */
if (WARN_ON(((unsigned long)buf & 3) && (buf_len > 4))) {
pr_err("buffer is not aligned.\n");
return -EINVAL;
}
/* Convert to SDIO Register Address */
addr_sdio = SPI_REG_ADDR_TO_SDIO(addr);
sdio_reg_addr_17bit = SDIO_ADDR17BIT(buf_id, 0, 0, addr_sdio);
return priv->hwbus_ops->hwbus_memcpy_fromio(priv->hwbus_priv,
sdio_reg_addr_17bit,
buf, buf_len);
}
static int __cw1200_reg_write(struct cw1200_common *priv, u16 addr,
const void *buf, size_t buf_len, int buf_id)
{
u16 addr_sdio;
u32 sdio_reg_addr_17bit;
/* Convert to SDIO Register Address */
addr_sdio = SPI_REG_ADDR_TO_SDIO(addr);
sdio_reg_addr_17bit = SDIO_ADDR17BIT(buf_id, 0, 0, addr_sdio);
return priv->hwbus_ops->hwbus_memcpy_toio(priv->hwbus_priv,
sdio_reg_addr_17bit,
buf, buf_len);
}
static inline int __cw1200_reg_read_32(struct cw1200_common *priv,
u16 addr, u32 *val)
{
__le32 tmp;
int i = __cw1200_reg_read(priv, addr, &tmp, sizeof(tmp), 0);
*val = le32_to_cpu(tmp);
return i;
}
static inline int __cw1200_reg_write_32(struct cw1200_common *priv,
u16 addr, u32 val)
{
__le32 tmp = cpu_to_le32(val);
return __cw1200_reg_write(priv, addr, &tmp, sizeof(tmp), 0);
}
static inline int __cw1200_reg_read_16(struct cw1200_common *priv,
u16 addr, u16 *val)
{
__le16 tmp;
int i = __cw1200_reg_read(priv, addr, &tmp, sizeof(tmp), 0);
*val = le16_to_cpu(tmp);
return i;
}
static inline int __cw1200_reg_write_16(struct cw1200_common *priv,
u16 addr, u16 val)
{
__le16 tmp = cpu_to_le16(val);
return __cw1200_reg_write(priv, addr, &tmp, sizeof(tmp), 0);
}
int cw1200_reg_read(struct cw1200_common *priv, u16 addr, void *buf,
size_t buf_len)
{
int ret;
priv->hwbus_ops->lock(priv->hwbus_priv);
ret = __cw1200_reg_read(priv, addr, buf, buf_len, 0);
priv->hwbus_ops->unlock(priv->hwbus_priv);
return ret;
}
int cw1200_reg_write(struct cw1200_common *priv, u16 addr, const void *buf,
size_t buf_len)
{
int ret;
priv->hwbus_ops->lock(priv->hwbus_priv);
ret = __cw1200_reg_write(priv, addr, buf, buf_len, 0);
priv->hwbus_ops->unlock(priv->hwbus_priv);
return ret;
}
int cw1200_data_read(struct cw1200_common *priv, void *buf, size_t buf_len)
{
int ret, retry = 1;
int buf_id_rx = priv->buf_id_rx;
priv->hwbus_ops->lock(priv->hwbus_priv);
while (retry <= MAX_RETRY) {
ret = __cw1200_reg_read(priv,
ST90TDS_IN_OUT_QUEUE_REG_ID, buf,
buf_len, buf_id_rx + 1);
if (!ret) {
buf_id_rx = (buf_id_rx + 1) & 3;
priv->buf_id_rx = buf_id_rx;
break;
} else {
retry++;
mdelay(1);
pr_err("error :[%d]\n", ret);
}
}
priv->hwbus_ops->unlock(priv->hwbus_priv);
return ret;
}
int cw1200_data_write(struct cw1200_common *priv, const void *buf,
size_t buf_len)
{
int ret, retry = 1;
int buf_id_tx = priv->buf_id_tx;
priv->hwbus_ops->lock(priv->hwbus_priv);
while (retry <= MAX_RETRY) {
ret = __cw1200_reg_write(priv,
ST90TDS_IN_OUT_QUEUE_REG_ID, buf,
buf_len, buf_id_tx);
if (!ret) {
buf_id_tx = (buf_id_tx + 1) & 31;
priv->buf_id_tx = buf_id_tx;
break;
} else {
retry++;
mdelay(1);
pr_err("error :[%d]\n", ret);
}
}
priv->hwbus_ops->unlock(priv->hwbus_priv);
return ret;
}
int cw1200_indirect_read(struct cw1200_common *priv, u32 addr, void *buf,
size_t buf_len, u32 prefetch, u16 port_addr)
{
u32 val32 = 0;
int i, ret;
if ((buf_len / 2) >= 0x1000) {
pr_err("Can't read more than 0xfff words.\n");
return -EINVAL;
}
priv->hwbus_ops->lock(priv->hwbus_priv);
/* Write address */
ret = __cw1200_reg_write_32(priv, ST90TDS_SRAM_BASE_ADDR_REG_ID, addr);
if (ret < 0) {
pr_err("Can't write address register.\n");
goto out;
}
/* Read CONFIG Register Value - We will read 32 bits */
ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
if (ret < 0) {
pr_err("Can't read config register.\n");
goto out;
}
/* Set PREFETCH bit */
ret = __cw1200_reg_write_32(priv, ST90TDS_CONFIG_REG_ID,
val32 | prefetch);
if (ret < 0) {
pr_err("Can't write prefetch bit.\n");
goto out;
}
/* Check for PRE-FETCH bit to be cleared */
for (i = 0; i < 20; i++) {
ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
if (ret < 0) {
pr_err("Can't check prefetch bit.\n");
goto out;
}
if (!(val32 & prefetch))
break;
mdelay(i);
}
if (val32 & prefetch) {
pr_err("Prefetch bit is not cleared.\n");
goto out;
}
/* Read data port */
ret = __cw1200_reg_read(priv, port_addr, buf, buf_len, 0);
if (ret < 0) {
pr_err("Can't read data port.\n");
goto out;
}
out:
priv->hwbus_ops->unlock(priv->hwbus_priv);
return ret;
}
int cw1200_apb_write(struct cw1200_common *priv, u32 addr, const void *buf,
size_t buf_len)
{
int ret;
if ((buf_len / 2) >= 0x1000) {
pr_err("Can't write more than 0xfff words.\n");
return -EINVAL;
}
priv->hwbus_ops->lock(priv->hwbus_priv);
/* Write address */
ret = __cw1200_reg_write_32(priv, ST90TDS_SRAM_BASE_ADDR_REG_ID, addr);
if (ret < 0) {
pr_err("Can't write address register.\n");
goto out;
}
/* Write data port */
ret = __cw1200_reg_write(priv, ST90TDS_SRAM_DPORT_REG_ID,
buf, buf_len, 0);
if (ret < 0) {
pr_err("Can't write data port.\n");
goto out;
}
out:
priv->hwbus_ops->unlock(priv->hwbus_priv);
return ret;
}
int __cw1200_irq_enable(struct cw1200_common *priv, int enable)
{
u32 val32;
u16 val16;
int ret;
if (HIF_8601_SILICON == priv->hw_type) {
ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
if (ret < 0) {
pr_err("Can't read config register.\n");
return ret;
}
if (enable)
val32 |= ST90TDS_CONF_IRQ_RDY_ENABLE;
else
val32 &= ~ST90TDS_CONF_IRQ_RDY_ENABLE;
ret = __cw1200_reg_write_32(priv, ST90TDS_CONFIG_REG_ID, val32);
if (ret < 0) {
pr_err("Can't write config register.\n");
return ret;
}
} else {
ret = __cw1200_reg_read_16(priv, ST90TDS_CONFIG_REG_ID, &val16);
if (ret < 0) {
pr_err("Can't read control register.\n");
return ret;
}
if (enable)
val16 |= ST90TDS_CONT_IRQ_RDY_ENABLE;
else
val16 &= ~ST90TDS_CONT_IRQ_RDY_ENABLE;
ret = __cw1200_reg_write_16(priv, ST90TDS_CONFIG_REG_ID, val16);
if (ret < 0) {
pr_err("Can't write control register.\n");
return ret;
}
}
return 0;
}
@@ -0,0 +1,244 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Low-level API for mac80211 ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on:
* ST-Ericsson UMAC CW1200 driver which is
* Copyright (c) 2010, ST-Ericsson
* Author: Ajitpal Singh <ajitpal.singh@stericsson.com>
*/
#ifndef CW1200_HWIO_H_INCLUDED
#define CW1200_HWIO_H_INCLUDED
/* extern */ struct cw1200_common;
#define CW1200_CUT_11_ID_STR (0x302E3830)
#define CW1200_CUT_22_ID_STR1 (0x302e3132)
#define CW1200_CUT_22_ID_STR2 (0x32302e30)
#define CW1200_CUT_22_ID_STR3 (0x3335)
#define CW1200_CUT_ID_ADDR (0xFFF17F90)
#define CW1200_CUT2_ID_ADDR (0xFFF1FF90)
/* Download control area */
/* boot loader start address in SRAM */
#define DOWNLOAD_BOOT_LOADER_OFFSET (0x00000000)
/* 32K, 0x4000 to 0xDFFF */
#define DOWNLOAD_FIFO_OFFSET (0x00004000)
/* 32K */
#define DOWNLOAD_FIFO_SIZE (0x00008000)
/* 128 bytes, 0xFF80 to 0xFFFF */
#define DOWNLOAD_CTRL_OFFSET (0x0000FF80)
#define DOWNLOAD_CTRL_DATA_DWORDS (32-6)
struct download_cntl_t {
/* size of whole firmware file (including Cheksum), host init */
u32 image_size;
/* downloading flags */
u32 flags;
/* No. of bytes put into the download, init & updated by host */
u32 put;
/* last traced program counter, last ARM reg_pc */
u32 trace_pc;
/* No. of bytes read from the download, host init, device updates */
u32 get;
/* r0, boot losader status, host init to pending, device updates */
u32 status;
/* Extra debug info, r1 to r14 if status=r0=DOWNLOAD_EXCEPTION */
u32 debug_data[DOWNLOAD_CTRL_DATA_DWORDS];
};
#define DOWNLOAD_IMAGE_SIZE_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, image_size))
#define DOWNLOAD_FLAGS_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, flags))
#define DOWNLOAD_PUT_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, put))
#define DOWNLOAD_TRACE_PC_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, trace_pc))
#define DOWNLOAD_GET_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, get))
#define DOWNLOAD_STATUS_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, status))
#define DOWNLOAD_DEBUG_DATA_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, debug_data))
#define DOWNLOAD_DEBUG_DATA_LEN (108)
#define DOWNLOAD_BLOCK_SIZE (1024)
/* For boot loader detection */
#define DOWNLOAD_ARE_YOU_HERE (0x87654321)
#define DOWNLOAD_I_AM_HERE (0x12345678)
/* Download error code */
#define DOWNLOAD_PENDING (0xFFFFFFFF)
#define DOWNLOAD_SUCCESS (0)
#define DOWNLOAD_EXCEPTION (1)
#define DOWNLOAD_ERR_MEM_1 (2)
#define DOWNLOAD_ERR_MEM_2 (3)
#define DOWNLOAD_ERR_SOFTWARE (4)
#define DOWNLOAD_ERR_FILE_SIZE (5)
#define DOWNLOAD_ERR_CHECKSUM (6)
#define DOWNLOAD_ERR_OVERFLOW (7)
#define DOWNLOAD_ERR_IMAGE (8)
#define DOWNLOAD_ERR_HOST (9)
#define DOWNLOAD_ERR_ABORT (10)
#define SYS_BASE_ADDR_SILICON (0)
#define PAC_BASE_ADDRESS_SILICON (SYS_BASE_ADDR_SILICON + 0x09000000)
#define PAC_SHARED_MEMORY_SILICON (PAC_BASE_ADDRESS_SILICON)
#define CW1200_APB(addr) (PAC_SHARED_MEMORY_SILICON + (addr))
/* Device register definitions */
/* WBF - SPI Register Addresses */
#define ST90TDS_ADDR_ID_BASE (0x0000)
/* 16/32 bits */
#define ST90TDS_CONFIG_REG_ID (0x0000)
/* 16/32 bits */
#define ST90TDS_CONTROL_REG_ID (0x0001)
/* 16 bits, Q mode W/R */
#define ST90TDS_IN_OUT_QUEUE_REG_ID (0x0002)
/* 32 bits, AHB bus R/W */
#define ST90TDS_AHB_DPORT_REG_ID (0x0003)
/* 16/32 bits */
#define ST90TDS_SRAM_BASE_ADDR_REG_ID (0x0004)
/* 32 bits, APB bus R/W */
#define ST90TDS_SRAM_DPORT_REG_ID (0x0005)
/* 32 bits, t_settle/general */
#define ST90TDS_TSET_GEN_R_W_REG_ID (0x0006)
/* 16 bits, Q mode read, no length */
#define ST90TDS_FRAME_OUT_REG_ID (0x0007)
#define ST90TDS_ADDR_ID_MAX (ST90TDS_FRAME_OUT_REG_ID)
/* WBF - Control register bit set */
/* next o/p length, bit 11 to 0 */
#define ST90TDS_CONT_NEXT_LEN_MASK (0x0FFF)
#define ST90TDS_CONT_WUP_BIT (BIT(12))
#define ST90TDS_CONT_RDY_BIT (BIT(13))
#define ST90TDS_CONT_IRQ_ENABLE (BIT(14))
#define ST90TDS_CONT_RDY_ENABLE (BIT(15))
#define ST90TDS_CONT_IRQ_RDY_ENABLE (BIT(14)|BIT(15))
/* SPI Config register bit set */
#define ST90TDS_CONFIG_FRAME_BIT (BIT(2))
#define ST90TDS_CONFIG_WORD_MODE_BITS (BIT(3)|BIT(4))
#define ST90TDS_CONFIG_WORD_MODE_1 (BIT(3))
#define ST90TDS_CONFIG_WORD_MODE_2 (BIT(4))
#define ST90TDS_CONFIG_ERROR_0_BIT (BIT(5))
#define ST90TDS_CONFIG_ERROR_1_BIT (BIT(6))
#define ST90TDS_CONFIG_ERROR_2_BIT (BIT(7))
/* TBD: Sure??? */
#define ST90TDS_CONFIG_CSN_FRAME_BIT (BIT(7))
#define ST90TDS_CONFIG_ERROR_3_BIT (BIT(8))
#define ST90TDS_CONFIG_ERROR_4_BIT (BIT(9))
/* QueueM */
#define ST90TDS_CONFIG_ACCESS_MODE_BIT (BIT(10))
/* AHB bus */
#define ST90TDS_CONFIG_AHB_PRFETCH_BIT (BIT(11))
#define ST90TDS_CONFIG_CPU_CLK_DIS_BIT (BIT(12))
/* APB bus */
#define ST90TDS_CONFIG_PRFETCH_BIT (BIT(13))
/* cpu reset */
#define ST90TDS_CONFIG_CPU_RESET_BIT (BIT(14))
#define ST90TDS_CONFIG_CLEAR_INT_BIT (BIT(15))
/* For CW1200 the IRQ Enable and Ready Bits are in CONFIG register */
#define ST90TDS_CONF_IRQ_ENABLE (BIT(16))
#define ST90TDS_CONF_RDY_ENABLE (BIT(17))
#define ST90TDS_CONF_IRQ_RDY_ENABLE (BIT(16)|BIT(17))
int cw1200_data_read(struct cw1200_common *priv,
void *buf, size_t buf_len);
int cw1200_data_write(struct cw1200_common *priv,
const void *buf, size_t buf_len);
int cw1200_reg_read(struct cw1200_common *priv, u16 addr,
void *buf, size_t buf_len);
int cw1200_reg_write(struct cw1200_common *priv, u16 addr,
const void *buf, size_t buf_len);
static inline int cw1200_reg_read_16(struct cw1200_common *priv,
u16 addr, u16 *val)
{
__le32 tmp;
int i;
i = cw1200_reg_read(priv, addr, &tmp, sizeof(tmp));
*val = le32_to_cpu(tmp) & 0xfffff;
return i;
}
static inline int cw1200_reg_write_16(struct cw1200_common *priv,
u16 addr, u16 val)
{
__le32 tmp = cpu_to_le32((u32)val);
return cw1200_reg_write(priv, addr, &tmp, sizeof(tmp));
}
static inline int cw1200_reg_read_32(struct cw1200_common *priv,
u16 addr, u32 *val)
{
__le32 tmp;
int i = cw1200_reg_read(priv, addr, &tmp, sizeof(tmp));
*val = le32_to_cpu(tmp);
return i;
}
static inline int cw1200_reg_write_32(struct cw1200_common *priv,
u16 addr, u32 val)
{
__le32 tmp = cpu_to_le32(val);
return cw1200_reg_write(priv, addr, &tmp, sizeof(val));
}
int cw1200_indirect_read(struct cw1200_common *priv, u32 addr, void *buf,
size_t buf_len, u32 prefetch, u16 port_addr);
int cw1200_apb_write(struct cw1200_common *priv, u32 addr, const void *buf,
size_t buf_len);
static inline int cw1200_apb_read(struct cw1200_common *priv, u32 addr,
void *buf, size_t buf_len)
{
return cw1200_indirect_read(priv, addr, buf, buf_len,
ST90TDS_CONFIG_PRFETCH_BIT,
ST90TDS_SRAM_DPORT_REG_ID);
}
static inline int cw1200_ahb_read(struct cw1200_common *priv, u32 addr,
void *buf, size_t buf_len)
{
return cw1200_indirect_read(priv, addr, buf, buf_len,
ST90TDS_CONFIG_AHB_PRFETCH_BIT,
ST90TDS_AHB_DPORT_REG_ID);
}
static inline int cw1200_apb_read_32(struct cw1200_common *priv,
u32 addr, u32 *val)
{
__le32 tmp;
int i = cw1200_apb_read(priv, addr, &tmp, sizeof(tmp));
*val = le32_to_cpu(tmp);
return i;
}
static inline int cw1200_apb_write_32(struct cw1200_common *priv,
u32 addr, u32 val)
{
__le32 tmp = cpu_to_le32(val);
return cw1200_apb_write(priv, addr, &tmp, sizeof(val));
}
static inline int cw1200_ahb_read_32(struct cw1200_common *priv,
u32 addr, u32 *val)
{
__le32 tmp;
int i = cw1200_ahb_read(priv, addr, &tmp, sizeof(tmp));
*val = le32_to_cpu(tmp);
return i;
}
#endif /* CW1200_HWIO_H_INCLUDED */
@@ -0,0 +1,609 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on:
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
* Copyright (c) 2007-2009, Christian Lamparter <chunkeey@web.de>
* Copyright 2008, Johannes Berg <johannes@sipsolutions.net>
*
* Based on:
* - the islsm (softmac prism54) driver, which is:
* Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
* - stlc45xx driver
* Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
*/
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/etherdevice.h>
#include <linux/vmalloc.h>
#include <linux/random.h>
#include <linux/sched.h>
#include <net/mac80211.h>
#include "cw1200.h"
#include "txrx.h"
#include "hwbus.h"
#include "fwio.h"
#include "hwio.h"
#include "bh.h"
#include "sta.h"
#include "scan.h"
#include "debug.h"
#include "pm.h"
MODULE_AUTHOR("Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>");
MODULE_DESCRIPTION("Softmac ST-Ericsson CW1200 common code");
MODULE_LICENSE("GPL");
MODULE_ALIAS("cw1200_core");
/* Accept MAC address of the form macaddr=0x00,0x80,0xE1,0x30,0x40,0x50 */
static u8 cw1200_mac_template[ETH_ALEN] = {0x02, 0x80, 0xe1, 0x00, 0x00, 0x00};
module_param_array_named(macaddr, cw1200_mac_template, byte, NULL, 0444);
MODULE_PARM_DESC(macaddr, "Override platform_data MAC address");
static char *cw1200_sdd_path;
module_param(cw1200_sdd_path, charp, 0644);
MODULE_PARM_DESC(cw1200_sdd_path, "Override platform_data SDD file");
static int cw1200_refclk;
module_param(cw1200_refclk, int, 0644);
MODULE_PARM_DESC(cw1200_refclk, "Override platform_data reference clock");
int cw1200_power_mode = wsm_power_mode_quiescent;
module_param(cw1200_power_mode, int, 0644);
MODULE_PARM_DESC(cw1200_power_mode, "WSM power mode. 0 == active, 1 == doze, 2 == quiescent (default)");
#define RATETAB_ENT(_rate, _rateid, _flags) \
{ \
.bitrate = (_rate), \
.hw_value = (_rateid), \
.flags = (_flags), \
}
static struct ieee80211_rate cw1200_rates[] = {
RATETAB_ENT(10, 0, 0),
RATETAB_ENT(20, 1, 0),
RATETAB_ENT(55, 2, 0),
RATETAB_ENT(110, 3, 0),
RATETAB_ENT(60, 6, 0),
RATETAB_ENT(90, 7, 0),
RATETAB_ENT(120, 8, 0),
RATETAB_ENT(180, 9, 0),
RATETAB_ENT(240, 10, 0),
RATETAB_ENT(360, 11, 0),
RATETAB_ENT(480, 12, 0),
RATETAB_ENT(540, 13, 0),
};
static struct ieee80211_rate cw1200_mcs_rates[] = {
RATETAB_ENT(65, 14, IEEE80211_TX_RC_MCS),
RATETAB_ENT(130, 15, IEEE80211_TX_RC_MCS),
RATETAB_ENT(195, 16, IEEE80211_TX_RC_MCS),
RATETAB_ENT(260, 17, IEEE80211_TX_RC_MCS),
RATETAB_ENT(390, 18, IEEE80211_TX_RC_MCS),
RATETAB_ENT(520, 19, IEEE80211_TX_RC_MCS),
RATETAB_ENT(585, 20, IEEE80211_TX_RC_MCS),
RATETAB_ENT(650, 21, IEEE80211_TX_RC_MCS),
};
#define cw1200_a_rates (cw1200_rates + 4)
#define cw1200_a_rates_size (ARRAY_SIZE(cw1200_rates) - 4)
#define cw1200_g_rates (cw1200_rates + 0)
#define cw1200_g_rates_size (ARRAY_SIZE(cw1200_rates))
#define cw1200_n_rates (cw1200_mcs_rates)
#define cw1200_n_rates_size (ARRAY_SIZE(cw1200_mcs_rates))
#define CHAN2G(_channel, _freq, _flags) { \
.band = NL80211_BAND_2GHZ, \
.center_freq = (_freq), \
.hw_value = (_channel), \
.flags = (_flags), \
.max_antenna_gain = 0, \
.max_power = 30, \
}
#define CHAN5G(_channel, _flags) { \
.band = NL80211_BAND_5GHZ, \
.center_freq = 5000 + (5 * (_channel)), \
.hw_value = (_channel), \
.flags = (_flags), \
.max_antenna_gain = 0, \
.max_power = 30, \
}
static struct ieee80211_channel cw1200_2ghz_chantable[] = {
CHAN2G(1, 2412, 0),
CHAN2G(2, 2417, 0),
CHAN2G(3, 2422, 0),
CHAN2G(4, 2427, 0),
CHAN2G(5, 2432, 0),
CHAN2G(6, 2437, 0),
CHAN2G(7, 2442, 0),
CHAN2G(8, 2447, 0),
CHAN2G(9, 2452, 0),
CHAN2G(10, 2457, 0),
CHAN2G(11, 2462, 0),
CHAN2G(12, 2467, 0),
CHAN2G(13, 2472, 0),
CHAN2G(14, 2484, 0),
};
static struct ieee80211_channel cw1200_5ghz_chantable[] = {
CHAN5G(34, 0), CHAN5G(36, 0),
CHAN5G(38, 0), CHAN5G(40, 0),
CHAN5G(42, 0), CHAN5G(44, 0),
CHAN5G(46, 0), CHAN5G(48, 0),
CHAN5G(52, 0), CHAN5G(56, 0),
CHAN5G(60, 0), CHAN5G(64, 0),
CHAN5G(100, 0), CHAN5G(104, 0),
CHAN5G(108, 0), CHAN5G(112, 0),
CHAN5G(116, 0), CHAN5G(120, 0),
CHAN5G(124, 0), CHAN5G(128, 0),
CHAN5G(132, 0), CHAN5G(136, 0),
CHAN5G(140, 0), CHAN5G(149, 0),
CHAN5G(153, 0), CHAN5G(157, 0),
CHAN5G(161, 0), CHAN5G(165, 0),
CHAN5G(184, 0), CHAN5G(188, 0),
CHAN5G(192, 0), CHAN5G(196, 0),
CHAN5G(200, 0), CHAN5G(204, 0),
CHAN5G(208, 0), CHAN5G(212, 0),
CHAN5G(216, 0),
};
static struct ieee80211_supported_band cw1200_band_2ghz = {
.channels = cw1200_2ghz_chantable,
.n_channels = ARRAY_SIZE(cw1200_2ghz_chantable),
.bitrates = cw1200_g_rates,
.n_bitrates = cw1200_g_rates_size,
.ht_cap = {
.cap = IEEE80211_HT_CAP_GRN_FLD |
(1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) |
IEEE80211_HT_CAP_MAX_AMSDU,
.ht_supported = 1,
.ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K,
.ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE,
.mcs = {
.rx_mask[0] = 0xFF,
.rx_highest = __cpu_to_le16(0x41),
.tx_params = IEEE80211_HT_MCS_TX_DEFINED,
},
},
};
static struct ieee80211_supported_band cw1200_band_5ghz = {
.channels = cw1200_5ghz_chantable,
.n_channels = ARRAY_SIZE(cw1200_5ghz_chantable),
.bitrates = cw1200_a_rates,
.n_bitrates = cw1200_a_rates_size,
.ht_cap = {
.cap = IEEE80211_HT_CAP_GRN_FLD |
(1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) |
IEEE80211_HT_CAP_MAX_AMSDU,
.ht_supported = 1,
.ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K,
.ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE,
.mcs = {
.rx_mask[0] = 0xFF,
.rx_highest = __cpu_to_le16(0x41),
.tx_params = IEEE80211_HT_MCS_TX_DEFINED,
},
},
};
static const unsigned long cw1200_ttl[] = {
1 * HZ, /* VO */
2 * HZ, /* VI */
5 * HZ, /* BE */
10 * HZ /* BK */
};
static const struct ieee80211_ops cw1200_ops = {
.add_chanctx = ieee80211_emulate_add_chanctx,
.remove_chanctx = ieee80211_emulate_remove_chanctx,
.change_chanctx = ieee80211_emulate_change_chanctx,
.switch_vif_chanctx = ieee80211_emulate_switch_vif_chanctx,
.start = cw1200_start,
.stop = cw1200_stop,
.add_interface = cw1200_add_interface,
.remove_interface = cw1200_remove_interface,
.change_interface = cw1200_change_interface,
.tx = cw1200_tx,
.wake_tx_queue = ieee80211_handle_wake_tx_queue,
.hw_scan = cw1200_hw_scan,
.set_tim = cw1200_set_tim,
.sta_notify = cw1200_sta_notify,
.sta_add = cw1200_sta_add,
.sta_remove = cw1200_sta_remove,
.set_key = cw1200_set_key,
.set_rts_threshold = cw1200_set_rts_threshold,
.config = cw1200_config,
.bss_info_changed = cw1200_bss_info_changed,
.prepare_multicast = cw1200_prepare_multicast,
.configure_filter = cw1200_configure_filter,
.conf_tx = cw1200_conf_tx,
.get_stats = cw1200_get_stats,
.ampdu_action = cw1200_ampdu_action,
.flush = cw1200_flush,
#ifdef CONFIG_PM
.suspend = cw1200_wow_suspend,
.resume = cw1200_wow_resume,
#endif
/* Intentionally not offloaded: */
/*.channel_switch = cw1200_channel_switch, */
/*.remain_on_channel = cw1200_remain_on_channel, */
/*.cancel_remain_on_channel = cw1200_cancel_remain_on_channel, */
};
static int cw1200_ba_rx_tids = -1;
static int cw1200_ba_tx_tids = -1;
module_param(cw1200_ba_rx_tids, int, 0644);
module_param(cw1200_ba_tx_tids, int, 0644);
MODULE_PARM_DESC(cw1200_ba_rx_tids, "Block ACK RX TIDs");
MODULE_PARM_DESC(cw1200_ba_tx_tids, "Block ACK TX TIDs");
#ifdef CONFIG_PM
static const struct wiphy_wowlan_support cw1200_wowlan_support = {
/* Support only for limited wowlan functionalities */
.flags = WIPHY_WOWLAN_ANY | WIPHY_WOWLAN_DISCONNECT,
};
#endif
static struct ieee80211_hw *cw1200_init_common(const u8 *macaddr,
const bool have_5ghz)
{
int i, band;
struct ieee80211_hw *hw;
struct cw1200_common *priv;
hw = ieee80211_alloc_hw(sizeof(struct cw1200_common), &cw1200_ops);
if (!hw)
return NULL;
priv = hw->priv;
priv->hw = hw;
priv->hw_type = -1;
priv->mode = NL80211_IFTYPE_UNSPECIFIED;
priv->rates = cw1200_rates; /* TODO: fetch from FW */
priv->mcs_rates = cw1200_n_rates;
if (cw1200_ba_rx_tids != -1)
priv->ba_rx_tid_mask = cw1200_ba_rx_tids;
else
priv->ba_rx_tid_mask = 0xFF; /* Enable RX BLKACK for all TIDs */
if (cw1200_ba_tx_tids != -1)
priv->ba_tx_tid_mask = cw1200_ba_tx_tids;
else
priv->ba_tx_tid_mask = 0xff; /* Enable TX BLKACK for all TIDs */
ieee80211_hw_set(hw, NEED_DTIM_BEFORE_ASSOC);
ieee80211_hw_set(hw, TX_AMPDU_SETUP_IN_HW);
ieee80211_hw_set(hw, AMPDU_AGGREGATION);
ieee80211_hw_set(hw, CONNECTION_MONITOR);
ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS);
ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS);
ieee80211_hw_set(hw, SIGNAL_DBM);
ieee80211_hw_set(hw, SUPPORTS_PS);
hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
BIT(NL80211_IFTYPE_ADHOC) |
BIT(NL80211_IFTYPE_AP) |
BIT(NL80211_IFTYPE_MESH_POINT) |
BIT(NL80211_IFTYPE_P2P_CLIENT) |
BIT(NL80211_IFTYPE_P2P_GO);
#ifdef CONFIG_PM
hw->wiphy->wowlan = &cw1200_wowlan_support;
#endif
hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
hw->queues = 4;
priv->rts_threshold = -1;
hw->max_rates = 8;
hw->max_rate_tries = 15;
hw->extra_tx_headroom = WSM_TX_EXTRA_HEADROOM +
8; /* TKIP IV */
hw->sta_data_size = sizeof(struct cw1200_sta_priv);
hw->wiphy->bands[NL80211_BAND_2GHZ] = &cw1200_band_2ghz;
if (have_5ghz)
hw->wiphy->bands[NL80211_BAND_5GHZ] = &cw1200_band_5ghz;
/* Channel params have to be cleared before registering wiphy again */
for (band = 0; band < NUM_NL80211_BANDS; band++) {
struct ieee80211_supported_band *sband = hw->wiphy->bands[band];
if (!sband)
continue;
for (i = 0; i < sband->n_channels; i++) {
sband->channels[i].flags = 0;
sband->channels[i].max_antenna_gain = 0;
sband->channels[i].max_power = 30;
}
}
hw->wiphy->max_scan_ssids = 2;
hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN;
if (macaddr)
SET_IEEE80211_PERM_ADDR(hw, (u8 *)macaddr);
else
SET_IEEE80211_PERM_ADDR(hw, cw1200_mac_template);
/* Fix up mac address if necessary */
if (hw->wiphy->perm_addr[3] == 0 &&
hw->wiphy->perm_addr[4] == 0 &&
hw->wiphy->perm_addr[5] == 0) {
get_random_bytes(&hw->wiphy->perm_addr[3], 3);
}
mutex_init(&priv->wsm_cmd_mux);
mutex_init(&priv->conf_mutex);
priv->workqueue = create_singlethread_workqueue("cw1200_wq");
if (!priv->workqueue) {
ieee80211_free_hw(hw);
return NULL;
}
sema_init(&priv->scan.lock, 1);
INIT_WORK(&priv->scan.work, cw1200_scan_work);
INIT_DELAYED_WORK(&priv->scan.probe_work, cw1200_probe_work);
INIT_DELAYED_WORK(&priv->scan.timeout, cw1200_scan_timeout);
INIT_DELAYED_WORK(&priv->clear_recent_scan_work,
cw1200_clear_recent_scan_work);
INIT_DELAYED_WORK(&priv->join_timeout, cw1200_join_timeout);
INIT_WORK(&priv->unjoin_work, cw1200_unjoin_work);
INIT_WORK(&priv->join_complete_work, cw1200_join_complete_work);
INIT_WORK(&priv->wep_key_work, cw1200_wep_key_work);
INIT_WORK(&priv->tx_policy_upload_work, tx_policy_upload_work);
spin_lock_init(&priv->event_queue_lock);
INIT_LIST_HEAD(&priv->event_queue);
INIT_WORK(&priv->event_handler, cw1200_event_handler);
INIT_DELAYED_WORK(&priv->bss_loss_work, cw1200_bss_loss_work);
INIT_WORK(&priv->bss_params_work, cw1200_bss_params_work);
spin_lock_init(&priv->bss_loss_lock);
spin_lock_init(&priv->ps_state_lock);
INIT_WORK(&priv->set_cts_work, cw1200_set_cts_work);
INIT_WORK(&priv->set_tim_work, cw1200_set_tim_work);
INIT_WORK(&priv->multicast_start_work, cw1200_multicast_start_work);
INIT_WORK(&priv->multicast_stop_work, cw1200_multicast_stop_work);
INIT_WORK(&priv->link_id_work, cw1200_link_id_work);
INIT_DELAYED_WORK(&priv->link_id_gc_work, cw1200_link_id_gc_work);
INIT_WORK(&priv->linkid_reset_work, cw1200_link_id_reset);
INIT_WORK(&priv->update_filtering_work, cw1200_update_filtering_work);
INIT_WORK(&priv->set_beacon_wakeup_period_work,
cw1200_set_beacon_wakeup_period_work);
timer_setup(&priv->mcast_timeout, cw1200_mcast_timeout, 0);
if (cw1200_queue_stats_init(&priv->tx_queue_stats,
CW1200_LINK_ID_MAX,
cw1200_skb_dtor,
priv)) {
destroy_workqueue(priv->workqueue);
ieee80211_free_hw(hw);
return NULL;
}
for (i = 0; i < 4; ++i) {
if (cw1200_queue_init(&priv->tx_queue[i],
&priv->tx_queue_stats, i, 16,
cw1200_ttl[i])) {
for (; i > 0; i--)
cw1200_queue_deinit(&priv->tx_queue[i - 1]);
cw1200_queue_stats_deinit(&priv->tx_queue_stats);
destroy_workqueue(priv->workqueue);
ieee80211_free_hw(hw);
return NULL;
}
}
init_waitqueue_head(&priv->channel_switch_done);
init_waitqueue_head(&priv->wsm_cmd_wq);
init_waitqueue_head(&priv->wsm_startup_done);
init_waitqueue_head(&priv->ps_mode_switch_done);
wsm_buf_init(&priv->wsm_cmd_buf);
spin_lock_init(&priv->wsm_cmd.lock);
priv->wsm_cmd.done = 1;
tx_policy_init(priv);
return hw;
}
static int cw1200_register_common(struct ieee80211_hw *dev)
{
struct cw1200_common *priv = dev->priv;
int err;
#ifdef CONFIG_PM
err = cw1200_pm_init(&priv->pm_state, priv);
if (err) {
pr_err("Cannot init PM. (%d).\n",
err);
return err;
}
#endif
err = ieee80211_register_hw(dev);
if (err) {
pr_err("Cannot register device (%d).\n",
err);
#ifdef CONFIG_PM
cw1200_pm_deinit(&priv->pm_state);
#endif
return err;
}
cw1200_debug_init(priv);
pr_info("Registered as '%s'\n", wiphy_name(dev->wiphy));
return 0;
}
static void cw1200_free_common(struct ieee80211_hw *dev)
{
ieee80211_free_hw(dev);
}
static void cw1200_unregister_common(struct ieee80211_hw *dev)
{
struct cw1200_common *priv = dev->priv;
int i;
ieee80211_unregister_hw(dev);
timer_delete_sync(&priv->mcast_timeout);
cw1200_unregister_bh(priv);
cw1200_debug_release(priv);
mutex_destroy(&priv->conf_mutex);
wsm_buf_deinit(&priv->wsm_cmd_buf);
destroy_workqueue(priv->workqueue);
priv->workqueue = NULL;
if (priv->sdd) {
release_firmware(priv->sdd);
priv->sdd = NULL;
}
for (i = 0; i < 4; ++i)
cw1200_queue_deinit(&priv->tx_queue[i]);
cw1200_queue_stats_deinit(&priv->tx_queue_stats);
#ifdef CONFIG_PM
cw1200_pm_deinit(&priv->pm_state);
#endif
}
/* Clock is in KHz */
u32 cw1200_dpll_from_clk(u16 clk_khz)
{
switch (clk_khz) {
case 0x32C8: /* 13000 KHz */
return 0x1D89D241;
case 0x3E80: /* 16000 KHz */
return 0x000001E1;
case 0x41A0: /* 16800 KHz */
return 0x124931C1;
case 0x4B00: /* 19200 KHz */
return 0x00000191;
case 0x5DC0: /* 24000 KHz */
return 0x00000141;
case 0x6590: /* 26000 KHz */
return 0x0EC4F121;
case 0x8340: /* 33600 KHz */
return 0x092490E1;
case 0x9600: /* 38400 KHz */
return 0x100010C1;
case 0x9C40: /* 40000 KHz */
return 0x000000C1;
case 0xBB80: /* 48000 KHz */
return 0x000000A1;
case 0xCB20: /* 52000 KHz */
return 0x07627091;
default:
pr_err("Unknown Refclk freq (0x%04x), using 26000KHz\n",
clk_khz);
return 0x0EC4F121;
}
}
int cw1200_core_probe(const struct hwbus_ops *hwbus_ops,
struct hwbus_priv *hwbus,
struct device *pdev,
struct cw1200_common **core,
int ref_clk, const u8 *macaddr,
const char *sdd_path, bool have_5ghz)
{
int err = -EINVAL;
struct ieee80211_hw *dev;
struct cw1200_common *priv;
struct wsm_operational_mode mode = {
.power_mode = cw1200_power_mode,
.disable_more_flag_usage = true,
};
dev = cw1200_init_common(macaddr, have_5ghz);
if (!dev)
goto err;
priv = dev->priv;
priv->hw_refclk = ref_clk;
if (cw1200_refclk)
priv->hw_refclk = cw1200_refclk;
priv->sdd_path = (char *)sdd_path;
if (cw1200_sdd_path)
priv->sdd_path = cw1200_sdd_path;
priv->hwbus_ops = hwbus_ops;
priv->hwbus_priv = hwbus;
priv->pdev = pdev;
SET_IEEE80211_DEV(priv->hw, pdev);
/* Pass struct cw1200_common back up */
*core = priv;
err = cw1200_register_bh(priv);
if (err)
goto err1;
err = cw1200_load_firmware(priv);
if (err)
goto err2;
if (wait_event_interruptible_timeout(priv->wsm_startup_done,
priv->firmware_ready,
3*HZ) <= 0) {
/* TODO: Need to find how to reset device
in QUEUE mode properly.
*/
pr_err("Timeout waiting on device startup\n");
err = -ETIMEDOUT;
goto err2;
}
/* Set low-power mode. */
wsm_set_operational_mode(priv, &mode);
/* Enable multi-TX confirmation */
wsm_use_multi_tx_conf(priv, true);
err = cw1200_register_common(dev);
if (err)
goto err2;
return err;
err2:
cw1200_unregister_bh(priv);
err1:
cw1200_free_common(dev);
err:
*core = NULL;
return err;
}
EXPORT_SYMBOL_GPL(cw1200_core_probe);
void cw1200_core_release(struct cw1200_common *self)
{
/* Disable device interrupts */
self->hwbus_ops->lock(self->hwbus_priv);
__cw1200_irq_enable(self, 0);
self->hwbus_ops->unlock(self->hwbus_priv);
/* And then clean up */
cw1200_unregister_common(self->hw);
cw1200_free_common(self->hw);
return;
}
EXPORT_SYMBOL_GPL(cw1200_core_release);
@@ -0,0 +1,363 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Mac80211 power management API for ST-Ericsson CW1200 drivers
*
* Copyright (c) 2011, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#include <linux/module.h>
#include <linux/if_ether.h>
#include "cw1200.h"
#include "pm.h"
#include "sta.h"
#include "bh.h"
#include "hwbus.h"
#define CW1200_BEACON_SKIPPING_MULTIPLIER 3
struct cw1200_udp_port_filter {
struct wsm_udp_port_filter_hdr hdr;
/* Up to 4 filters are allowed. */
struct wsm_udp_port_filter filters[WSM_MAX_FILTER_ELEMENTS];
} __packed;
struct cw1200_ether_type_filter {
struct wsm_ether_type_filter_hdr hdr;
/* Up to 4 filters are allowed. */
struct wsm_ether_type_filter filters[WSM_MAX_FILTER_ELEMENTS];
} __packed;
static struct cw1200_udp_port_filter cw1200_udp_port_filter_on = {
.hdr.num = 2,
.filters = {
[0] = {
.action = WSM_FILTER_ACTION_FILTER_OUT,
.type = WSM_FILTER_PORT_TYPE_DST,
.port = __cpu_to_le16(67), /* DHCP Bootps */
},
[1] = {
.action = WSM_FILTER_ACTION_FILTER_OUT,
.type = WSM_FILTER_PORT_TYPE_DST,
.port = __cpu_to_le16(68), /* DHCP Bootpc */
},
}
};
static struct wsm_udp_port_filter_hdr cw1200_udp_port_filter_off = {
.num = 0,
};
#ifndef ETH_P_WAPI
#define ETH_P_WAPI 0x88B4
#endif
static struct cw1200_ether_type_filter cw1200_ether_type_filter_on = {
.hdr.num = 4,
.filters = {
[0] = {
.action = WSM_FILTER_ACTION_FILTER_IN,
.type = __cpu_to_le16(ETH_P_IP),
},
[1] = {
.action = WSM_FILTER_ACTION_FILTER_IN,
.type = __cpu_to_le16(ETH_P_PAE),
},
[2] = {
.action = WSM_FILTER_ACTION_FILTER_IN,
.type = __cpu_to_le16(ETH_P_WAPI),
},
[3] = {
.action = WSM_FILTER_ACTION_FILTER_IN,
.type = __cpu_to_le16(ETH_P_ARP),
},
},
};
static struct wsm_ether_type_filter_hdr cw1200_ether_type_filter_off = {
.num = 0,
};
/* private */
struct cw1200_suspend_state {
unsigned long bss_loss_tmo;
unsigned long join_tmo;
unsigned long direct_probe;
unsigned long link_id_gc;
bool beacon_skipping;
u8 prev_ps_mode;
};
static void cw1200_pm_stay_awake_tmo(struct timer_list *unused)
{
/* XXX what's the point of this ? */
}
int cw1200_pm_init(struct cw1200_pm_state *pm,
struct cw1200_common *priv)
{
spin_lock_init(&pm->lock);
timer_setup(&pm->stay_awake, cw1200_pm_stay_awake_tmo, 0);
return 0;
}
void cw1200_pm_deinit(struct cw1200_pm_state *pm)
{
timer_delete_sync(&pm->stay_awake);
}
void cw1200_pm_stay_awake(struct cw1200_pm_state *pm,
unsigned long tmo)
{
long cur_tmo;
spin_lock_bh(&pm->lock);
cur_tmo = pm->stay_awake.expires - jiffies;
if (!timer_pending(&pm->stay_awake) || cur_tmo < (long)tmo)
mod_timer(&pm->stay_awake, jiffies + tmo);
spin_unlock_bh(&pm->lock);
}
static long cw1200_suspend_work(struct delayed_work *work)
{
int ret = cancel_delayed_work(work);
long tmo;
if (ret > 0) {
/* Timer is pending */
tmo = work->timer.expires - jiffies;
if (tmo < 0)
tmo = 0;
} else {
tmo = -1;
}
return tmo;
}
static int cw1200_resume_work(struct cw1200_common *priv,
struct delayed_work *work,
unsigned long tmo)
{
if ((long)tmo < 0)
return 1;
return queue_delayed_work(priv->workqueue, work, tmo);
}
int cw1200_can_suspend(struct cw1200_common *priv)
{
if (atomic_read(&priv->bh_rx)) {
wiphy_dbg(priv->hw->wiphy, "Suspend interrupted.\n");
return 0;
}
return 1;
}
EXPORT_SYMBOL_GPL(cw1200_can_suspend);
int cw1200_wow_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
{
struct cw1200_common *priv = hw->priv;
struct cw1200_pm_state *pm_state = &priv->pm_state;
struct cw1200_suspend_state *state;
int ret;
spin_lock_bh(&pm_state->lock);
ret = timer_pending(&pm_state->stay_awake);
spin_unlock_bh(&pm_state->lock);
if (ret)
return -EAGAIN;
/* Do not suspend when datapath is not idle */
if (priv->tx_queue_stats.num_queued)
return -EBUSY;
/* Make sure there is no configuration requests in progress. */
if (!mutex_trylock(&priv->conf_mutex))
return -EBUSY;
/* Ensure pending operations are done.
* Note also that wow_suspend must return in ~2.5sec, before
* watchdog is triggered.
*/
if (priv->channel_switch_in_progress)
goto revert1;
/* Do not suspend when join is pending */
if (priv->join_pending)
goto revert1;
/* Do not suspend when scanning */
if (down_trylock(&priv->scan.lock))
goto revert1;
/* Lock TX. */
wsm_lock_tx_async(priv);
/* Wait to avoid possible race with bh code.
* But do not wait too long...
*/
if (wait_event_timeout(priv->bh_evt_wq,
!priv->hw_bufs_used, HZ / 10) <= 0)
goto revert2;
/* Set UDP filter */
wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_on.hdr);
/* Set ethernet frame type filter */
wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_on.hdr);
/* Allocate state */
state = kzalloc_obj(struct cw1200_suspend_state);
if (!state)
goto revert3;
/* Change to legacy PS while going to suspend */
if (!priv->vif->p2p &&
priv->join_status == CW1200_JOIN_STATUS_STA &&
priv->powersave_mode.mode != WSM_PSM_PS) {
state->prev_ps_mode = priv->powersave_mode.mode;
priv->powersave_mode.mode = WSM_PSM_PS;
cw1200_set_pm(priv, &priv->powersave_mode);
if (wait_event_interruptible_timeout(priv->ps_mode_switch_done,
!priv->ps_mode_switch_in_progress, 1*HZ) <= 0) {
goto revert4;
}
}
/* Store delayed work states. */
state->bss_loss_tmo =
cw1200_suspend_work(&priv->bss_loss_work);
state->join_tmo =
cw1200_suspend_work(&priv->join_timeout);
state->direct_probe =
cw1200_suspend_work(&priv->scan.probe_work);
state->link_id_gc =
cw1200_suspend_work(&priv->link_id_gc_work);
cancel_delayed_work_sync(&priv->clear_recent_scan_work);
atomic_set(&priv->recent_scan, 0);
/* Enable beacon skipping */
if (priv->join_status == CW1200_JOIN_STATUS_STA &&
priv->join_dtim_period &&
!priv->has_multicast_subscription) {
state->beacon_skipping = true;
wsm_set_beacon_wakeup_period(priv,
priv->join_dtim_period,
CW1200_BEACON_SKIPPING_MULTIPLIER * priv->join_dtim_period);
}
/* Stop serving thread */
if (cw1200_bh_suspend(priv))
goto revert5;
ret = timer_pending(&priv->mcast_timeout);
if (ret)
goto revert6;
/* Store suspend state */
pm_state->suspend_state = state;
/* Enable IRQ wake */
ret = priv->hwbus_ops->power_mgmt(priv->hwbus_priv, true);
if (ret) {
wiphy_err(priv->hw->wiphy,
"PM request failed: %d. WoW is disabled.\n", ret);
cw1200_wow_resume(hw);
return -EBUSY;
}
/* Force resume if event is coming from the device. */
if (atomic_read(&priv->bh_rx)) {
cw1200_wow_resume(hw);
return -EAGAIN;
}
return 0;
revert6:
WARN_ON(cw1200_bh_resume(priv));
revert5:
cw1200_resume_work(priv, &priv->bss_loss_work,
state->bss_loss_tmo);
cw1200_resume_work(priv, &priv->join_timeout,
state->join_tmo);
cw1200_resume_work(priv, &priv->scan.probe_work,
state->direct_probe);
cw1200_resume_work(priv, &priv->link_id_gc_work,
state->link_id_gc);
revert4:
kfree(state);
revert3:
wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off);
wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off);
revert2:
wsm_unlock_tx(priv);
up(&priv->scan.lock);
revert1:
mutex_unlock(&priv->conf_mutex);
return -EBUSY;
}
int cw1200_wow_resume(struct ieee80211_hw *hw)
{
struct cw1200_common *priv = hw->priv;
struct cw1200_pm_state *pm_state = &priv->pm_state;
struct cw1200_suspend_state *state;
state = pm_state->suspend_state;
pm_state->suspend_state = NULL;
/* Disable IRQ wake */
priv->hwbus_ops->power_mgmt(priv->hwbus_priv, false);
/* Scan.lock must be released before BH is resumed other way
* in case when BSS_LOST command arrived the processing of the
* command will be delayed.
*/
up(&priv->scan.lock);
/* Resume BH thread */
WARN_ON(cw1200_bh_resume(priv));
/* Restores previous PS mode */
if (!priv->vif->p2p && priv->join_status == CW1200_JOIN_STATUS_STA) {
priv->powersave_mode.mode = state->prev_ps_mode;
cw1200_set_pm(priv, &priv->powersave_mode);
}
if (state->beacon_skipping) {
wsm_set_beacon_wakeup_period(priv, priv->beacon_int *
priv->join_dtim_period >
MAX_BEACON_SKIP_TIME_MS ? 1 :
priv->join_dtim_period, 0);
state->beacon_skipping = false;
}
/* Resume delayed work */
cw1200_resume_work(priv, &priv->bss_loss_work,
state->bss_loss_tmo);
cw1200_resume_work(priv, &priv->join_timeout,
state->join_tmo);
cw1200_resume_work(priv, &priv->scan.probe_work,
state->direct_probe);
cw1200_resume_work(priv, &priv->link_id_gc_work,
state->link_id_gc);
/* Remove UDP port filter */
wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off);
/* Remove ethernet frame type filter */
wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off);
/* Unlock datapath */
wsm_unlock_tx(priv);
/* Unlock configuration mutex */
mutex_unlock(&priv->conf_mutex);
/* Free memory */
kfree(state);
return 0;
}
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Mac80211 power management interface for ST-Ericsson CW1200 mac80211 drivers
*
* Copyright (c) 2011, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#ifndef PM_H_INCLUDED
#define PM_H_INCLUDED
/* ******************************************************************** */
/* mac80211 API */
/* extern */ struct cw1200_common;
/* private */ struct cw1200_suspend_state;
struct cw1200_pm_state {
struct cw1200_suspend_state *suspend_state;
struct timer_list stay_awake;
struct platform_device *pm_dev;
spinlock_t lock; /* Protect access */
};
#ifdef CONFIG_PM
int cw1200_pm_init(struct cw1200_pm_state *pm,
struct cw1200_common *priv);
void cw1200_pm_deinit(struct cw1200_pm_state *pm);
int cw1200_wow_suspend(struct ieee80211_hw *hw,
struct cfg80211_wowlan *wowlan);
int cw1200_can_suspend(struct cw1200_common *priv);
int cw1200_wow_resume(struct ieee80211_hw *hw);
void cw1200_pm_stay_awake(struct cw1200_pm_state *pm,
unsigned long tmo);
#else
static inline void cw1200_pm_stay_awake(struct cw1200_pm_state *pm,
unsigned long tmo)
{
}
static inline int cw1200_can_suspend(struct cw1200_common *priv)
{
return 0;
}
#endif
#endif
@@ -0,0 +1,549 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* O(1) TX queue with built-in allocator for ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#include <net/mac80211.h>
#include <linux/sched.h>
#include <linux/jiffies.h>
#include "queue.h"
#include "cw1200.h"
#include "debug.h"
/* private */ struct cw1200_queue_item
{
struct list_head head;
struct sk_buff *skb;
u32 packet_id;
unsigned long queue_timestamp;
unsigned long xmit_timestamp;
struct cw1200_txpriv txpriv;
u8 generation;
};
static inline void __cw1200_queue_lock(struct cw1200_queue *queue)
{
struct cw1200_queue_stats *stats = queue->stats;
if (queue->tx_locked_cnt++ == 0) {
pr_debug("[TX] Queue %d is locked.\n",
queue->queue_id);
ieee80211_stop_queue(stats->priv->hw, queue->queue_id);
}
}
static inline void __cw1200_queue_unlock(struct cw1200_queue *queue)
{
struct cw1200_queue_stats *stats = queue->stats;
BUG_ON(!queue->tx_locked_cnt);
if (--queue->tx_locked_cnt == 0) {
pr_debug("[TX] Queue %d is unlocked.\n",
queue->queue_id);
ieee80211_wake_queue(stats->priv->hw, queue->queue_id);
}
}
static inline void cw1200_queue_parse_id(u32 packet_id, u8 *queue_generation,
u8 *queue_id, u8 *item_generation,
u8 *item_id)
{
*item_id = (packet_id >> 0) & 0xFF;
*item_generation = (packet_id >> 8) & 0xFF;
*queue_id = (packet_id >> 16) & 0xFF;
*queue_generation = (packet_id >> 24) & 0xFF;
}
static inline u32 cw1200_queue_mk_packet_id(u8 queue_generation, u8 queue_id,
u8 item_generation, u8 item_id)
{
return ((u32)item_id << 0) |
((u32)item_generation << 8) |
((u32)queue_id << 16) |
((u32)queue_generation << 24);
}
static void cw1200_queue_post_gc(struct cw1200_queue_stats *stats,
struct list_head *gc_list)
{
struct cw1200_queue_item *item, *tmp;
list_for_each_entry_safe(item, tmp, gc_list, head) {
list_del(&item->head);
stats->skb_dtor(stats->priv, item->skb, &item->txpriv);
kfree(item);
}
}
static void cw1200_queue_register_post_gc(struct list_head *gc_list,
struct cw1200_queue_item *item)
{
struct cw1200_queue_item *gc_item;
gc_item = kmemdup(item, sizeof(struct cw1200_queue_item),
GFP_ATOMIC);
BUG_ON(!gc_item);
list_add_tail(&gc_item->head, gc_list);
}
static void __cw1200_queue_gc(struct cw1200_queue *queue,
struct list_head *head,
bool unlock)
{
struct cw1200_queue_stats *stats = queue->stats;
struct cw1200_queue_item *item = NULL, *iter, *tmp;
bool wakeup_stats = false;
list_for_each_entry_safe(iter, tmp, &queue->queue, head) {
if (time_is_after_jiffies(iter->queue_timestamp + queue->ttl)) {
item = iter;
break;
}
--queue->num_queued;
--queue->link_map_cache[iter->txpriv.link_id];
spin_lock_bh(&stats->lock);
--stats->num_queued;
if (!--stats->link_map_cache[iter->txpriv.link_id])
wakeup_stats = true;
spin_unlock_bh(&stats->lock);
cw1200_debug_tx_ttl(stats->priv);
cw1200_queue_register_post_gc(head, iter);
iter->skb = NULL;
list_move_tail(&iter->head, &queue->free_pool);
}
if (wakeup_stats)
wake_up(&stats->wait_link_id_empty);
if (queue->overfull) {
if (queue->num_queued <= (queue->capacity >> 1)) {
queue->overfull = false;
if (unlock)
__cw1200_queue_unlock(queue);
} else if (item) {
unsigned long tmo = item->queue_timestamp + queue->ttl;
mod_timer(&queue->gc, tmo);
cw1200_pm_stay_awake(&stats->priv->pm_state,
tmo - jiffies);
}
}
}
static void cw1200_queue_gc(struct timer_list *t)
{
LIST_HEAD(list);
struct cw1200_queue *queue =
timer_container_of(queue, t, gc);
spin_lock_bh(&queue->lock);
__cw1200_queue_gc(queue, &list, true);
spin_unlock_bh(&queue->lock);
cw1200_queue_post_gc(queue->stats, &list);
}
int cw1200_queue_stats_init(struct cw1200_queue_stats *stats,
size_t map_capacity,
cw1200_queue_skb_dtor_t skb_dtor,
struct cw1200_common *priv)
{
memset(stats, 0, sizeof(*stats));
stats->map_capacity = map_capacity;
stats->skb_dtor = skb_dtor;
stats->priv = priv;
spin_lock_init(&stats->lock);
init_waitqueue_head(&stats->wait_link_id_empty);
stats->link_map_cache = kzalloc_objs(int, map_capacity);
if (!stats->link_map_cache)
return -ENOMEM;
return 0;
}
int cw1200_queue_init(struct cw1200_queue *queue,
struct cw1200_queue_stats *stats,
u8 queue_id,
size_t capacity,
unsigned long ttl)
{
size_t i;
memset(queue, 0, sizeof(*queue));
queue->stats = stats;
queue->capacity = capacity;
queue->queue_id = queue_id;
queue->ttl = ttl;
INIT_LIST_HEAD(&queue->queue);
INIT_LIST_HEAD(&queue->pending);
INIT_LIST_HEAD(&queue->free_pool);
spin_lock_init(&queue->lock);
timer_setup(&queue->gc, cw1200_queue_gc, 0);
queue->pool = kzalloc_objs(struct cw1200_queue_item, capacity);
if (!queue->pool)
return -ENOMEM;
queue->link_map_cache = kzalloc_objs(int, stats->map_capacity);
if (!queue->link_map_cache) {
kfree(queue->pool);
queue->pool = NULL;
return -ENOMEM;
}
for (i = 0; i < capacity; ++i)
list_add_tail(&queue->pool[i].head, &queue->free_pool);
return 0;
}
int cw1200_queue_clear(struct cw1200_queue *queue)
{
int i;
LIST_HEAD(gc_list);
struct cw1200_queue_stats *stats = queue->stats;
struct cw1200_queue_item *item, *tmp;
spin_lock_bh(&queue->lock);
queue->generation++;
list_splice_tail_init(&queue->queue, &queue->pending);
list_for_each_entry_safe(item, tmp, &queue->pending, head) {
WARN_ON(!item->skb);
cw1200_queue_register_post_gc(&gc_list, item);
item->skb = NULL;
list_move_tail(&item->head, &queue->free_pool);
}
queue->num_queued = 0;
queue->num_pending = 0;
spin_lock_bh(&stats->lock);
for (i = 0; i < stats->map_capacity; ++i) {
stats->num_queued -= queue->link_map_cache[i];
stats->link_map_cache[i] -= queue->link_map_cache[i];
queue->link_map_cache[i] = 0;
}
spin_unlock_bh(&stats->lock);
if (queue->overfull) {
queue->overfull = false;
__cw1200_queue_unlock(queue);
}
spin_unlock_bh(&queue->lock);
wake_up(&stats->wait_link_id_empty);
cw1200_queue_post_gc(stats, &gc_list);
return 0;
}
void cw1200_queue_stats_deinit(struct cw1200_queue_stats *stats)
{
kfree(stats->link_map_cache);
stats->link_map_cache = NULL;
}
void cw1200_queue_deinit(struct cw1200_queue *queue)
{
cw1200_queue_clear(queue);
timer_delete_sync(&queue->gc);
INIT_LIST_HEAD(&queue->free_pool);
kfree(queue->pool);
kfree(queue->link_map_cache);
queue->pool = NULL;
queue->link_map_cache = NULL;
queue->capacity = 0;
}
size_t cw1200_queue_get_num_queued(struct cw1200_queue *queue,
u32 link_id_map)
{
size_t ret;
int i, bit;
size_t map_capacity = queue->stats->map_capacity;
if (!link_id_map)
return 0;
spin_lock_bh(&queue->lock);
if (link_id_map == (u32)-1) {
ret = queue->num_queued - queue->num_pending;
} else {
ret = 0;
for (i = 0, bit = 1; i < map_capacity; ++i, bit <<= 1) {
if (link_id_map & bit)
ret += queue->link_map_cache[i];
}
}
spin_unlock_bh(&queue->lock);
return ret;
}
int cw1200_queue_put(struct cw1200_queue *queue,
struct sk_buff *skb,
struct cw1200_txpriv *txpriv)
{
int ret = 0;
struct cw1200_queue_stats *stats = queue->stats;
if (txpriv->link_id >= queue->stats->map_capacity)
return -EINVAL;
spin_lock_bh(&queue->lock);
if (!WARN_ON(list_empty(&queue->free_pool))) {
struct cw1200_queue_item *item = list_first_entry(
&queue->free_pool, struct cw1200_queue_item, head);
BUG_ON(item->skb);
list_move_tail(&item->head, &queue->queue);
item->skb = skb;
item->txpriv = *txpriv;
item->generation = 0;
item->packet_id = cw1200_queue_mk_packet_id(queue->generation,
queue->queue_id,
item->generation,
item - queue->pool);
item->queue_timestamp = jiffies;
++queue->num_queued;
++queue->link_map_cache[txpriv->link_id];
spin_lock_bh(&stats->lock);
++stats->num_queued;
++stats->link_map_cache[txpriv->link_id];
spin_unlock_bh(&stats->lock);
/* TX may happen in parallel sometimes.
* Leave extra queue slots so we don't overflow.
*/
if (queue->overfull == false &&
queue->num_queued >=
(queue->capacity - (num_present_cpus() - 1))) {
queue->overfull = true;
__cw1200_queue_lock(queue);
mod_timer(&queue->gc, jiffies);
}
} else {
ret = -ENOENT;
}
spin_unlock_bh(&queue->lock);
return ret;
}
int cw1200_queue_get(struct cw1200_queue *queue,
u32 link_id_map,
struct wsm_tx **tx,
struct ieee80211_tx_info **tx_info,
const struct cw1200_txpriv **txpriv)
{
int ret = -ENOENT;
struct cw1200_queue_item *item;
struct cw1200_queue_stats *stats = queue->stats;
bool wakeup_stats = false;
spin_lock_bh(&queue->lock);
list_for_each_entry(item, &queue->queue, head) {
if (link_id_map & BIT(item->txpriv.link_id)) {
ret = 0;
break;
}
}
if (!WARN_ON(ret)) {
*tx = (struct wsm_tx *)item->skb->data;
*tx_info = IEEE80211_SKB_CB(item->skb);
*txpriv = &item->txpriv;
(*tx)->packet_id = item->packet_id;
list_move_tail(&item->head, &queue->pending);
++queue->num_pending;
--queue->link_map_cache[item->txpriv.link_id];
item->xmit_timestamp = jiffies;
spin_lock_bh(&stats->lock);
--stats->num_queued;
if (!--stats->link_map_cache[item->txpriv.link_id])
wakeup_stats = true;
spin_unlock_bh(&stats->lock);
}
spin_unlock_bh(&queue->lock);
if (wakeup_stats)
wake_up(&stats->wait_link_id_empty);
return ret;
}
int cw1200_queue_requeue(struct cw1200_queue *queue, u32 packet_id)
{
int ret = 0;
u8 queue_generation, queue_id, item_generation, item_id;
struct cw1200_queue_item *item;
struct cw1200_queue_stats *stats = queue->stats;
cw1200_queue_parse_id(packet_id, &queue_generation, &queue_id,
&item_generation, &item_id);
item = &queue->pool[item_id];
spin_lock_bh(&queue->lock);
BUG_ON(queue_id != queue->queue_id);
if (queue_generation != queue->generation) {
ret = -ENOENT;
} else if (item_id >= (unsigned) queue->capacity) {
WARN_ON(1);
ret = -EINVAL;
} else if (item->generation != item_generation) {
WARN_ON(1);
ret = -ENOENT;
} else {
--queue->num_pending;
++queue->link_map_cache[item->txpriv.link_id];
spin_lock_bh(&stats->lock);
++stats->num_queued;
++stats->link_map_cache[item->txpriv.link_id];
spin_unlock_bh(&stats->lock);
item->generation = ++item_generation;
item->packet_id = cw1200_queue_mk_packet_id(queue_generation,
queue_id,
item_generation,
item_id);
list_move(&item->head, &queue->queue);
}
spin_unlock_bh(&queue->lock);
return ret;
}
int cw1200_queue_remove(struct cw1200_queue *queue, u32 packet_id)
{
int ret = 0;
u8 queue_generation, queue_id, item_generation, item_id;
struct cw1200_queue_item *item;
struct cw1200_queue_stats *stats = queue->stats;
struct sk_buff *gc_skb = NULL;
struct cw1200_txpriv gc_txpriv;
cw1200_queue_parse_id(packet_id, &queue_generation, &queue_id,
&item_generation, &item_id);
item = &queue->pool[item_id];
spin_lock_bh(&queue->lock);
BUG_ON(queue_id != queue->queue_id);
if (queue_generation != queue->generation) {
ret = -ENOENT;
} else if (item_id >= (unsigned) queue->capacity) {
WARN_ON(1);
ret = -EINVAL;
} else if (item->generation != item_generation) {
WARN_ON(1);
ret = -ENOENT;
} else {
gc_txpriv = item->txpriv;
gc_skb = item->skb;
item->skb = NULL;
--queue->num_pending;
--queue->num_queued;
++queue->num_sent;
++item->generation;
/* Do not use list_move_tail here, but list_move:
* try to utilize cache row.
*/
list_move(&item->head, &queue->free_pool);
if (queue->overfull &&
(queue->num_queued <= (queue->capacity >> 1))) {
queue->overfull = false;
__cw1200_queue_unlock(queue);
}
}
spin_unlock_bh(&queue->lock);
if (gc_skb)
stats->skb_dtor(stats->priv, gc_skb, &gc_txpriv);
return ret;
}
int cw1200_queue_get_skb(struct cw1200_queue *queue, u32 packet_id,
struct sk_buff **skb,
const struct cw1200_txpriv **txpriv)
{
int ret = 0;
u8 queue_generation, queue_id, item_generation, item_id;
struct cw1200_queue_item *item;
cw1200_queue_parse_id(packet_id, &queue_generation, &queue_id,
&item_generation, &item_id);
item = &queue->pool[item_id];
spin_lock_bh(&queue->lock);
BUG_ON(queue_id != queue->queue_id);
if (queue_generation != queue->generation) {
ret = -ENOENT;
} else if (item_id >= (unsigned) queue->capacity) {
WARN_ON(1);
ret = -EINVAL;
} else if (item->generation != item_generation) {
WARN_ON(1);
ret = -ENOENT;
} else {
*skb = item->skb;
*txpriv = &item->txpriv;
}
spin_unlock_bh(&queue->lock);
return ret;
}
void cw1200_queue_lock(struct cw1200_queue *queue)
{
spin_lock_bh(&queue->lock);
__cw1200_queue_lock(queue);
spin_unlock_bh(&queue->lock);
}
void cw1200_queue_unlock(struct cw1200_queue *queue)
{
spin_lock_bh(&queue->lock);
__cw1200_queue_unlock(queue);
spin_unlock_bh(&queue->lock);
}
bool cw1200_queue_get_xmit_timestamp(struct cw1200_queue *queue,
unsigned long *timestamp,
u32 pending_frame_id)
{
struct cw1200_queue_item *item;
bool ret;
spin_lock_bh(&queue->lock);
ret = !list_empty(&queue->pending);
if (ret) {
list_for_each_entry(item, &queue->pending, head) {
if (item->packet_id != pending_frame_id)
if (time_before(item->xmit_timestamp,
*timestamp))
*timestamp = item->xmit_timestamp;
}
}
spin_unlock_bh(&queue->lock);
return ret;
}
bool cw1200_queue_stats_is_empty(struct cw1200_queue_stats *stats,
u32 link_id_map)
{
bool empty = true;
spin_lock_bh(&stats->lock);
if (link_id_map == (u32)-1) {
empty = stats->num_queued == 0;
} else {
int i;
for (i = 0; i < stats->map_capacity; ++i) {
if (link_id_map & BIT(i)) {
if (stats->link_map_cache[i]) {
empty = false;
break;
}
}
}
}
spin_unlock_bh(&stats->lock);
return empty;
}
@@ -0,0 +1,112 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* O(1) TX queue with built-in allocator for ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#ifndef CW1200_QUEUE_H_INCLUDED
#define CW1200_QUEUE_H_INCLUDED
/* private */ struct cw1200_queue_item;
/* extern */ struct sk_buff;
/* extern */ struct wsm_tx;
/* extern */ struct cw1200_common;
/* extern */ struct ieee80211_tx_queue_stats;
/* extern */ struct cw1200_txpriv;
/* forward */ struct cw1200_queue_stats;
typedef void (*cw1200_queue_skb_dtor_t)(struct cw1200_common *priv,
struct sk_buff *skb,
const struct cw1200_txpriv *txpriv);
struct cw1200_queue {
struct cw1200_queue_stats *stats;
size_t capacity;
size_t num_queued;
size_t num_pending;
size_t num_sent;
struct cw1200_queue_item *pool;
struct list_head queue;
struct list_head free_pool;
struct list_head pending;
int tx_locked_cnt;
int *link_map_cache;
bool overfull;
spinlock_t lock; /* Protect queue entry */
u8 queue_id;
u8 generation;
struct timer_list gc;
unsigned long ttl;
};
struct cw1200_queue_stats {
spinlock_t lock; /* Protect stats entry */
int *link_map_cache;
int num_queued;
size_t map_capacity;
wait_queue_head_t wait_link_id_empty;
cw1200_queue_skb_dtor_t skb_dtor;
struct cw1200_common *priv;
};
struct cw1200_txpriv {
u8 link_id;
u8 raw_link_id;
u8 tid;
u8 rate_id;
u8 offset;
};
int cw1200_queue_stats_init(struct cw1200_queue_stats *stats,
size_t map_capacity,
cw1200_queue_skb_dtor_t skb_dtor,
struct cw1200_common *priv);
int cw1200_queue_init(struct cw1200_queue *queue,
struct cw1200_queue_stats *stats,
u8 queue_id,
size_t capacity,
unsigned long ttl);
int cw1200_queue_clear(struct cw1200_queue *queue);
void cw1200_queue_stats_deinit(struct cw1200_queue_stats *stats);
void cw1200_queue_deinit(struct cw1200_queue *queue);
size_t cw1200_queue_get_num_queued(struct cw1200_queue *queue,
u32 link_id_map);
int cw1200_queue_put(struct cw1200_queue *queue,
struct sk_buff *skb,
struct cw1200_txpriv *txpriv);
int cw1200_queue_get(struct cw1200_queue *queue,
u32 link_id_map,
struct wsm_tx **tx,
struct ieee80211_tx_info **tx_info,
const struct cw1200_txpriv **txpriv);
int cw1200_queue_requeue(struct cw1200_queue *queue, u32 packet_id);
int cw1200_queue_remove(struct cw1200_queue *queue,
u32 packet_id);
int cw1200_queue_get_skb(struct cw1200_queue *queue, u32 packet_id,
struct sk_buff **skb,
const struct cw1200_txpriv **txpriv);
void cw1200_queue_lock(struct cw1200_queue *queue);
void cw1200_queue_unlock(struct cw1200_queue *queue);
bool cw1200_queue_get_xmit_timestamp(struct cw1200_queue *queue,
unsigned long *timestamp,
u32 pending_frame_id);
bool cw1200_queue_stats_is_empty(struct cw1200_queue_stats *stats,
u32 link_id_map);
static inline u8 cw1200_queue_get_queue_id(u32 packet_id)
{
return (packet_id >> 16) & 0xFF;
}
static inline u8 cw1200_queue_get_generation(u32 packet_id)
{
return (packet_id >> 8) & 0xFF;
}
#endif /* CW1200_QUEUE_H_INCLUDED */
@@ -0,0 +1,460 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Scan implementation for ST-Ericsson CW1200 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#include <linux/sched.h>
#include "cw1200.h"
#include "scan.h"
#include "sta.h"
#include "pm.h"
static void cw1200_scan_restart_delayed(struct cw1200_common *priv);
static int cw1200_scan_start(struct cw1200_common *priv, struct wsm_scan *scan)
{
int ret, i;
int tmo = 2000;
switch (priv->join_status) {
case CW1200_JOIN_STATUS_PRE_STA:
case CW1200_JOIN_STATUS_JOINING:
return -EBUSY;
default:
break;
}
wiphy_dbg(priv->hw->wiphy, "[SCAN] hw req, type %d, %d channels, flags: 0x%x.\n",
scan->type, scan->num_channels, scan->flags);
for (i = 0; i < scan->num_channels; ++i)
tmo += scan->ch[i].max_chan_time + 10;
cancel_delayed_work_sync(&priv->clear_recent_scan_work);
atomic_set(&priv->scan.in_progress, 1);
atomic_set(&priv->recent_scan, 1);
cw1200_pm_stay_awake(&priv->pm_state, msecs_to_jiffies(tmo));
queue_delayed_work(priv->workqueue, &priv->scan.timeout,
msecs_to_jiffies(tmo));
ret = wsm_scan(priv, scan);
if (ret) {
atomic_set(&priv->scan.in_progress, 0);
cancel_delayed_work_sync(&priv->scan.timeout);
cw1200_scan_restart_delayed(priv);
}
return ret;
}
int cw1200_hw_scan(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_scan_request *hw_req)
{
struct cw1200_common *priv = hw->priv;
struct cfg80211_scan_request *req = &hw_req->req;
struct wsm_template_frame frame = {
.frame_type = WSM_FRAME_TYPE_PROBE_REQUEST,
};
int i, ret;
if (!priv->vif)
return -EINVAL;
/* Scan when P2P_GO corrupt firmware MiniAP mode */
if (priv->join_status == CW1200_JOIN_STATUS_AP)
return -EOPNOTSUPP;
if (req->n_ssids == 1 && !req->ssids[0].ssid_len)
req->n_ssids = 0;
wiphy_dbg(hw->wiphy, "[SCAN] Scan request for %d SSIDs.\n",
req->n_ssids);
if (req->n_ssids > WSM_SCAN_MAX_NUM_OF_SSIDS)
return -EINVAL;
frame.skb = ieee80211_probereq_get(hw, priv->vif->addr, NULL, 0,
req->ie_len);
if (!frame.skb)
return -ENOMEM;
if (req->ie_len)
skb_put_data(frame.skb, req->ie, req->ie_len);
/* will be unlocked in cw1200_scan_work() */
down(&priv->scan.lock);
mutex_lock(&priv->conf_mutex);
ret = wsm_set_template_frame(priv, &frame);
if (!ret) {
/* Host want to be the probe responder. */
ret = wsm_set_probe_responder(priv, true);
}
if (ret) {
mutex_unlock(&priv->conf_mutex);
up(&priv->scan.lock);
dev_kfree_skb(frame.skb);
return ret;
}
wsm_lock_tx(priv);
BUG_ON(priv->scan.req);
priv->scan.req = req;
priv->scan.n_ssids = 0;
priv->scan.status = 0;
priv->scan.begin = &req->channels[0];
priv->scan.curr = priv->scan.begin;
priv->scan.end = &req->channels[req->n_channels];
priv->scan.output_power = priv->output_power;
for (i = 0; i < req->n_ssids; ++i) {
struct wsm_ssid *dst = &priv->scan.ssids[priv->scan.n_ssids];
memcpy(&dst->ssid[0], req->ssids[i].ssid, sizeof(dst->ssid));
dst->length = req->ssids[i].ssid_len;
++priv->scan.n_ssids;
}
mutex_unlock(&priv->conf_mutex);
dev_kfree_skb(frame.skb);
queue_work(priv->workqueue, &priv->scan.work);
return 0;
}
void cw1200_scan_work(struct work_struct *work)
{
struct cw1200_common *priv = container_of(work, struct cw1200_common,
scan.work);
struct ieee80211_channel **it;
struct wsm_scan scan = {
.type = WSM_SCAN_TYPE_FOREGROUND,
.flags = WSM_SCAN_FLAG_SPLIT_METHOD,
};
bool first_run = (priv->scan.begin == priv->scan.curr &&
priv->scan.begin != priv->scan.end);
int i;
if (first_run) {
/* Firmware gets crazy if scan request is sent
* when STA is joined but not yet associated.
* Force unjoin in this case.
*/
if (cancel_delayed_work_sync(&priv->join_timeout) > 0)
cw1200_join_timeout(&priv->join_timeout.work);
}
mutex_lock(&priv->conf_mutex);
if (first_run) {
if (priv->join_status == CW1200_JOIN_STATUS_STA &&
!(priv->powersave_mode.mode & WSM_PSM_PS)) {
struct wsm_set_pm pm = priv->powersave_mode;
pm.mode = WSM_PSM_PS;
cw1200_set_pm(priv, &pm);
} else if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) {
/* FW bug: driver has to restart p2p-dev mode
* after scan
*/
cw1200_disable_listening(priv);
}
}
if (!priv->scan.req || (priv->scan.curr == priv->scan.end)) {
struct cfg80211_scan_info info = {
.aborted = priv->scan.status ? 1 : 0,
};
if (priv->scan.output_power != priv->output_power)
wsm_set_output_power(priv, priv->output_power * 10);
if (priv->join_status == CW1200_JOIN_STATUS_STA &&
!(priv->powersave_mode.mode & WSM_PSM_PS))
cw1200_set_pm(priv, &priv->powersave_mode);
if (priv->scan.status < 0)
wiphy_warn(priv->hw->wiphy,
"[SCAN] Scan failed (%d).\n",
priv->scan.status);
else if (priv->scan.req)
wiphy_dbg(priv->hw->wiphy,
"[SCAN] Scan completed.\n");
else
wiphy_dbg(priv->hw->wiphy,
"[SCAN] Scan canceled.\n");
priv->scan.req = NULL;
cw1200_scan_restart_delayed(priv);
wsm_unlock_tx(priv);
mutex_unlock(&priv->conf_mutex);
ieee80211_scan_completed(priv->hw, &info);
up(&priv->scan.lock);
return;
} else {
struct ieee80211_channel *first = *priv->scan.curr;
for (it = priv->scan.curr + 1, i = 1;
it != priv->scan.end && i < WSM_SCAN_MAX_NUM_OF_CHANNELS;
++it, ++i) {
if ((*it)->band != first->band)
break;
if (((*it)->flags ^ first->flags) &
IEEE80211_CHAN_NO_IR)
break;
if (!(first->flags & IEEE80211_CHAN_NO_IR) &&
(*it)->max_power != first->max_power)
break;
}
scan.band = first->band;
if (priv->scan.req->no_cck)
scan.max_tx_rate = WSM_TRANSMIT_RATE_6;
else
scan.max_tx_rate = WSM_TRANSMIT_RATE_1;
scan.num_probes =
(first->flags & IEEE80211_CHAN_NO_IR) ? 0 : 2;
scan.num_ssids = priv->scan.n_ssids;
scan.ssids = &priv->scan.ssids[0];
scan.num_channels = it - priv->scan.curr;
/* TODO: Is it optimal? */
scan.probe_delay = 100;
/* It is not stated in WSM specification, however
* FW team says that driver may not use FG scan
* when joined.
*/
if (priv->join_status == CW1200_JOIN_STATUS_STA) {
scan.type = WSM_SCAN_TYPE_BACKGROUND;
scan.flags = WSM_SCAN_FLAG_FORCE_BACKGROUND;
}
scan.ch = kzalloc_objs(struct wsm_scan_ch, it - priv->scan.curr);
if (!scan.ch) {
priv->scan.status = -ENOMEM;
goto fail;
}
for (i = 0; i < scan.num_channels; ++i) {
scan.ch[i].number = priv->scan.curr[i]->hw_value;
if (priv->scan.curr[i]->flags & IEEE80211_CHAN_NO_IR) {
scan.ch[i].min_chan_time = 50;
scan.ch[i].max_chan_time = 100;
} else {
scan.ch[i].min_chan_time = 10;
scan.ch[i].max_chan_time = 25;
}
}
if (!(first->flags & IEEE80211_CHAN_NO_IR) &&
priv->scan.output_power != first->max_power) {
priv->scan.output_power = first->max_power;
wsm_set_output_power(priv,
priv->scan.output_power * 10);
}
priv->scan.status = cw1200_scan_start(priv, &scan);
kfree(scan.ch);
if (priv->scan.status)
goto fail;
priv->scan.curr = it;
}
mutex_unlock(&priv->conf_mutex);
return;
fail:
priv->scan.curr = priv->scan.end;
mutex_unlock(&priv->conf_mutex);
queue_work(priv->workqueue, &priv->scan.work);
return;
}
static void cw1200_scan_restart_delayed(struct cw1200_common *priv)
{
/* FW bug: driver has to restart p2p-dev mode after scan. */
if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) {
cw1200_enable_listening(priv);
cw1200_update_filtering(priv);
}
if (priv->delayed_unjoin) {
priv->delayed_unjoin = false;
if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0)
wsm_unlock_tx(priv);
} else if (priv->delayed_link_loss) {
wiphy_dbg(priv->hw->wiphy, "[CQM] Requeue BSS loss.\n");
priv->delayed_link_loss = 0;
cw1200_cqm_bssloss_sm(priv, 1, 0, 0);
}
}
static void cw1200_scan_complete(struct cw1200_common *priv)
{
queue_delayed_work(priv->workqueue, &priv->clear_recent_scan_work, HZ);
if (priv->scan.direct_probe) {
wiphy_dbg(priv->hw->wiphy, "[SCAN] Direct probe complete.\n");
cw1200_scan_restart_delayed(priv);
priv->scan.direct_probe = 0;
up(&priv->scan.lock);
wsm_unlock_tx(priv);
} else {
cw1200_scan_work(&priv->scan.work);
}
}
void cw1200_scan_failed_cb(struct cw1200_common *priv)
{
if (priv->mode == NL80211_IFTYPE_UNSPECIFIED)
/* STA is stopped. */
return;
if (cancel_delayed_work_sync(&priv->scan.timeout) > 0) {
priv->scan.status = -EIO;
queue_delayed_work(priv->workqueue, &priv->scan.timeout, 0);
}
}
void cw1200_scan_complete_cb(struct cw1200_common *priv,
struct wsm_scan_complete *arg)
{
if (priv->mode == NL80211_IFTYPE_UNSPECIFIED)
/* STA is stopped. */
return;
if (cancel_delayed_work_sync(&priv->scan.timeout) > 0) {
priv->scan.status = 1;
queue_delayed_work(priv->workqueue, &priv->scan.timeout, 0);
}
}
void cw1200_clear_recent_scan_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common,
clear_recent_scan_work.work);
atomic_xchg(&priv->recent_scan, 0);
}
void cw1200_scan_timeout(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, scan.timeout.work);
if (atomic_xchg(&priv->scan.in_progress, 0)) {
if (priv->scan.status > 0) {
priv->scan.status = 0;
} else if (!priv->scan.status) {
wiphy_warn(priv->hw->wiphy,
"Timeout waiting for scan complete notification.\n");
priv->scan.status = -ETIMEDOUT;
priv->scan.curr = priv->scan.end;
wsm_stop_scan(priv);
}
cw1200_scan_complete(priv);
}
}
void cw1200_probe_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, scan.probe_work.work);
u8 queue_id = cw1200_queue_get_queue_id(priv->pending_frame_id);
struct cw1200_queue *queue = &priv->tx_queue[queue_id];
const struct cw1200_txpriv *txpriv;
struct wsm_tx *wsm;
struct wsm_template_frame frame = {
.frame_type = WSM_FRAME_TYPE_PROBE_REQUEST,
};
struct wsm_ssid ssids[1] = {{
.length = 0,
} };
struct wsm_scan_ch ch[1] = {{
.min_chan_time = 0,
.max_chan_time = 10,
} };
struct wsm_scan scan = {
.type = WSM_SCAN_TYPE_FOREGROUND,
.num_probes = 1,
.probe_delay = 0,
.num_channels = 1,
.ssids = ssids,
.ch = ch,
};
u8 *ies;
size_t ies_len;
int ret;
wiphy_dbg(priv->hw->wiphy, "[SCAN] Direct probe work.\n");
mutex_lock(&priv->conf_mutex);
if (down_trylock(&priv->scan.lock)) {
/* Scan is already in progress. Requeue self. */
schedule();
queue_delayed_work(priv->workqueue, &priv->scan.probe_work,
msecs_to_jiffies(100));
mutex_unlock(&priv->conf_mutex);
return;
}
/* Make sure we still have a pending probe req */
if (cw1200_queue_get_skb(queue, priv->pending_frame_id,
&frame.skb, &txpriv)) {
up(&priv->scan.lock);
mutex_unlock(&priv->conf_mutex);
wsm_unlock_tx(priv);
return;
}
wsm = (struct wsm_tx *)frame.skb->data;
scan.max_tx_rate = wsm->max_tx_rate;
scan.band = (priv->channel->band == NL80211_BAND_5GHZ) ?
WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G;
if (priv->join_status == CW1200_JOIN_STATUS_STA ||
priv->join_status == CW1200_JOIN_STATUS_IBSS) {
scan.type = WSM_SCAN_TYPE_BACKGROUND;
scan.flags = WSM_SCAN_FLAG_FORCE_BACKGROUND;
}
ch[0].number = priv->channel->hw_value;
skb_pull(frame.skb, txpriv->offset);
ies = &frame.skb->data[sizeof(struct ieee80211_hdr_3addr)];
ies_len = frame.skb->len - sizeof(struct ieee80211_hdr_3addr);
if (ies_len) {
u8 *ssidie =
(u8 *)cfg80211_find_ie(WLAN_EID_SSID, ies, ies_len);
if (ssidie && ssidie[1] && ssidie[1] <= sizeof(ssids[0].ssid)) {
u8 *nextie = &ssidie[2 + ssidie[1]];
/* Remove SSID from the IE list. It has to be provided
* as a separate argument in cw1200_scan_start call
*/
/* Store SSID localy */
ssids[0].length = ssidie[1];
memcpy(ssids[0].ssid, &ssidie[2], ssids[0].length);
scan.num_ssids = 1;
/* Remove SSID from IE list */
ssidie[1] = 0;
memmove(&ssidie[2], nextie, &ies[ies_len] - nextie);
skb_trim(frame.skb, frame.skb->len - ssids[0].length);
}
}
/* FW bug: driver has to restart p2p-dev mode after scan */
if (priv->join_status == CW1200_JOIN_STATUS_MONITOR)
cw1200_disable_listening(priv);
ret = wsm_set_template_frame(priv, &frame);
priv->scan.direct_probe = 1;
if (!ret) {
wsm_flush_tx(priv);
ret = cw1200_scan_start(priv, &scan);
}
mutex_unlock(&priv->conf_mutex);
skb_push(frame.skb, txpriv->offset);
if (!ret)
IEEE80211_SKB_CB(frame.skb)->flags |= IEEE80211_TX_STAT_ACK;
BUG_ON(cw1200_queue_remove(queue, priv->pending_frame_id));
if (ret) {
priv->scan.direct_probe = 0;
up(&priv->scan.lock);
wsm_unlock_tx(priv);
}
return;
}
@@ -0,0 +1,53 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Scan interface for ST-Ericsson CW1200 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#ifndef SCAN_H_INCLUDED
#define SCAN_H_INCLUDED
#include <linux/semaphore.h>
#include "wsm.h"
/* external */ struct sk_buff;
/* external */ struct cfg80211_scan_request;
/* external */ struct ieee80211_channel;
/* external */ struct ieee80211_hw;
/* external */ struct work_struct;
struct cw1200_scan {
struct semaphore lock;
struct work_struct work;
struct delayed_work timeout;
struct cfg80211_scan_request *req;
struct ieee80211_channel **begin;
struct ieee80211_channel **curr;
struct ieee80211_channel **end;
struct wsm_ssid ssids[WSM_SCAN_MAX_NUM_OF_SSIDS];
int output_power;
int n_ssids;
int status;
atomic_t in_progress;
/* Direct probe requests workaround */
struct delayed_work probe_work;
int direct_probe;
};
int cw1200_hw_scan(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_scan_request *hw_req);
void cw1200_scan_work(struct work_struct *work);
void cw1200_scan_timeout(struct work_struct *work);
void cw1200_clear_recent_scan_work(struct work_struct *work);
void cw1200_scan_complete_cb(struct cw1200_common *priv,
struct wsm_scan_complete *arg);
void cw1200_scan_failed_cb(struct cw1200_common *priv);
/* ******************************************************************** */
/* Raw probe requests TX workaround */
void cw1200_probe_work(struct work_struct *work);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,121 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Mac80211 STA interface for ST-Ericsson CW1200 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#ifndef STA_H_INCLUDED
#define STA_H_INCLUDED
/* ******************************************************************** */
/* mac80211 API */
int cw1200_start(struct ieee80211_hw *dev);
void cw1200_stop(struct ieee80211_hw *dev, bool suspend);
int cw1200_add_interface(struct ieee80211_hw *dev,
struct ieee80211_vif *vif);
void cw1200_remove_interface(struct ieee80211_hw *dev,
struct ieee80211_vif *vif);
int cw1200_change_interface(struct ieee80211_hw *dev,
struct ieee80211_vif *vif,
enum nl80211_iftype new_type,
bool p2p);
int cw1200_config(struct ieee80211_hw *dev, int radio_idx, u32 changed);
void cw1200_configure_filter(struct ieee80211_hw *dev,
unsigned int changed_flags,
unsigned int *total_flags,
u64 multicast);
int cw1200_conf_tx(struct ieee80211_hw *dev, struct ieee80211_vif *vif,
unsigned int link_id, u16 queue,
const struct ieee80211_tx_queue_params *params);
int cw1200_get_stats(struct ieee80211_hw *dev,
struct ieee80211_low_level_stats *stats);
int cw1200_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd,
struct ieee80211_vif *vif, struct ieee80211_sta *sta,
struct ieee80211_key_conf *key);
int cw1200_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx,
u32 value);
void cw1200_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
u32 queues, bool drop);
u64 cw1200_prepare_multicast(struct ieee80211_hw *hw,
struct netdev_hw_addr_list *mc_list);
int cw1200_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg);
/* ******************************************************************** */
/* WSM callbacks */
void cw1200_join_complete_cb(struct cw1200_common *priv,
struct wsm_join_complete *arg);
/* ******************************************************************** */
/* WSM events */
void cw1200_free_event_queue(struct cw1200_common *priv);
void cw1200_event_handler(struct work_struct *work);
void cw1200_bss_loss_work(struct work_struct *work);
void cw1200_bss_params_work(struct work_struct *work);
void cw1200_keep_alive_work(struct work_struct *work);
void cw1200_tx_failure_work(struct work_struct *work);
void __cw1200_cqm_bssloss_sm(struct cw1200_common *priv, int init, int good,
int bad);
static inline void cw1200_cqm_bssloss_sm(struct cw1200_common *priv,
int init, int good, int bad)
{
spin_lock(&priv->bss_loss_lock);
__cw1200_cqm_bssloss_sm(priv, init, good, bad);
spin_unlock(&priv->bss_loss_lock);
}
/* ******************************************************************** */
/* Internal API */
int cw1200_setup_mac(struct cw1200_common *priv);
void cw1200_join_timeout(struct work_struct *work);
void cw1200_unjoin_work(struct work_struct *work);
void cw1200_join_complete_work(struct work_struct *work);
void cw1200_wep_key_work(struct work_struct *work);
void cw1200_update_listening(struct cw1200_common *priv, bool enabled);
void cw1200_update_filtering(struct cw1200_common *priv);
void cw1200_update_filtering_work(struct work_struct *work);
void cw1200_set_beacon_wakeup_period_work(struct work_struct *work);
int cw1200_enable_listening(struct cw1200_common *priv);
int cw1200_disable_listening(struct cw1200_common *priv);
int cw1200_set_uapsd_param(struct cw1200_common *priv,
const struct wsm_edca_params *arg);
void cw1200_ba_work(struct work_struct *work);
void cw1200_ba_timer(unsigned long arg);
/* AP stuffs */
int cw1200_set_tim(struct ieee80211_hw *dev, struct ieee80211_sta *sta,
bool set);
int cw1200_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta);
int cw1200_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta);
void cw1200_sta_notify(struct ieee80211_hw *dev, struct ieee80211_vif *vif,
enum sta_notify_cmd notify_cmd,
struct ieee80211_sta *sta);
void cw1200_bss_info_changed(struct ieee80211_hw *dev,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *info,
u64 changed);
int cw1200_ampdu_action(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_ampdu_params *params);
void cw1200_suspend_resume(struct cw1200_common *priv,
struct wsm_suspend_resume *arg);
void cw1200_set_tim_work(struct work_struct *work);
void cw1200_set_cts_work(struct work_struct *work);
void cw1200_multicast_start_work(struct work_struct *work);
void cw1200_multicast_stop_work(struct work_struct *work);
void cw1200_mcast_timeout(struct timer_list *t);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,103 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Datapath interface for ST-Ericsson CW1200 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*/
#ifndef CW1200_TXRX_H
#define CW1200_TXRX_H
#include <linux/list.h>
/* extern */ struct ieee80211_hw;
/* extern */ struct sk_buff;
/* extern */ struct wsm_tx;
/* extern */ struct wsm_rx;
/* extern */ struct wsm_tx_confirm;
/* extern */ struct cw1200_txpriv;
struct tx_policy {
union {
__le32 tbl[3];
u8 raw[12];
};
u8 defined;
u8 usage_count;
u8 retry_count;
u8 uploaded;
};
struct tx_policy_cache_entry {
struct tx_policy policy;
struct list_head link;
};
#define TX_POLICY_CACHE_SIZE (8)
struct tx_policy_cache {
struct tx_policy_cache_entry cache[TX_POLICY_CACHE_SIZE];
struct list_head used;
struct list_head free;
spinlock_t lock; /* Protect policy cache */
};
/* ******************************************************************** */
/* TX policy cache */
/* Intention of TX policy cache is an overcomplicated WSM API.
* Device does not accept per-PDU tx retry sequence.
* It uses "tx retry policy id" instead, so driver code has to sync
* linux tx retry sequences with a retry policy table in the device.
*/
void tx_policy_init(struct cw1200_common *priv);
void tx_policy_upload_work(struct work_struct *work);
void tx_policy_clean(struct cw1200_common *priv);
/* ******************************************************************** */
/* TX implementation */
u32 cw1200_rate_mask_to_wsm(struct cw1200_common *priv,
u32 rates);
void cw1200_tx(struct ieee80211_hw *dev,
struct ieee80211_tx_control *control,
struct sk_buff *skb);
void cw1200_skb_dtor(struct cw1200_common *priv,
struct sk_buff *skb,
const struct cw1200_txpriv *txpriv);
/* ******************************************************************** */
/* WSM callbacks */
void cw1200_tx_confirm_cb(struct cw1200_common *priv,
int link_id,
struct wsm_tx_confirm *arg);
void cw1200_rx_cb(struct cw1200_common *priv,
struct wsm_rx *arg,
int link_id,
struct sk_buff **skb_p);
/* ******************************************************************** */
/* Timeout */
void cw1200_tx_timeout(struct work_struct *work);
/* ******************************************************************** */
/* Security */
int cw1200_alloc_key(struct cw1200_common *priv);
void cw1200_free_key(struct cw1200_common *priv, int idx);
void cw1200_free_keys(struct cw1200_common *priv);
int cw1200_upload_keys(struct cw1200_common *priv);
/* ******************************************************************** */
/* Workaround for WFD test case 6.1.10 */
void cw1200_link_id_reset(struct work_struct *work);
#define CW1200_LINK_ID_GC_TIMEOUT ((unsigned long)(10 * HZ))
int cw1200_find_link_id(struct cw1200_common *priv, const u8 *mac);
int cw1200_alloc_link_id(struct cw1200_common *priv, const u8 *mac);
void cw1200_link_id_work(struct work_struct *work);
void cw1200_link_id_gc_work(struct work_struct *work);
#endif /* CW1200_TXRX_H */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff