18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Device handling thread implementation for mac80211 ST-Ericsson CW1200 drivers
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2010, ST-Ericsson
68c2ecf20Sopenharmony_ci * Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Based on:
98c2ecf20Sopenharmony_ci * ST-Ericsson UMAC CW1200 driver, which is
108c2ecf20Sopenharmony_ci * Copyright (c) 2010, ST-Ericsson
118c2ecf20Sopenharmony_ci * Author: Ajitpal Singh <ajitpal.singh@stericsson.com>
128c2ecf20Sopenharmony_ci */
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <linux/module.h>
158c2ecf20Sopenharmony_ci#include <net/mac80211.h>
168c2ecf20Sopenharmony_ci#include <linux/kthread.h>
178c2ecf20Sopenharmony_ci#include <linux/timer.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include "cw1200.h"
208c2ecf20Sopenharmony_ci#include "bh.h"
218c2ecf20Sopenharmony_ci#include "hwio.h"
228c2ecf20Sopenharmony_ci#include "wsm.h"
238c2ecf20Sopenharmony_ci#include "hwbus.h"
248c2ecf20Sopenharmony_ci#include "debug.h"
258c2ecf20Sopenharmony_ci#include "fwio.h"
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistatic int cw1200_bh(void *arg);
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define DOWNLOAD_BLOCK_SIZE_WR	(0x1000 - 4)
308c2ecf20Sopenharmony_ci/* an SPI message cannot be bigger than (2"12-1)*2 bytes
318c2ecf20Sopenharmony_ci * "*2" to cvt to bytes
328c2ecf20Sopenharmony_ci */
338c2ecf20Sopenharmony_ci#define MAX_SZ_RD_WR_BUFFERS	(DOWNLOAD_BLOCK_SIZE_WR*2)
348c2ecf20Sopenharmony_ci#define PIGGYBACK_CTRL_REG	(2)
358c2ecf20Sopenharmony_ci#define EFFECTIVE_BUF_SIZE	(MAX_SZ_RD_WR_BUFFERS - PIGGYBACK_CTRL_REG)
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci/* Suspend state privates */
388c2ecf20Sopenharmony_cienum cw1200_bh_pm_state {
398c2ecf20Sopenharmony_ci	CW1200_BH_RESUMED = 0,
408c2ecf20Sopenharmony_ci	CW1200_BH_SUSPEND,
418c2ecf20Sopenharmony_ci	CW1200_BH_SUSPENDED,
428c2ecf20Sopenharmony_ci	CW1200_BH_RESUME,
438c2ecf20Sopenharmony_ci};
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_citypedef int (*cw1200_wsm_handler)(struct cw1200_common *priv,
468c2ecf20Sopenharmony_ci	u8 *data, size_t size);
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic void cw1200_bh_work(struct work_struct *work)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	struct cw1200_common *priv =
518c2ecf20Sopenharmony_ci	container_of(work, struct cw1200_common, bh_work);
528c2ecf20Sopenharmony_ci	cw1200_bh(priv);
538c2ecf20Sopenharmony_ci}
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ciint cw1200_register_bh(struct cw1200_common *priv)
568c2ecf20Sopenharmony_ci{
578c2ecf20Sopenharmony_ci	int err = 0;
588c2ecf20Sopenharmony_ci	/* Realtime workqueue */
598c2ecf20Sopenharmony_ci	priv->bh_workqueue = alloc_workqueue("cw1200_bh",
608c2ecf20Sopenharmony_ci				WQ_MEM_RECLAIM | WQ_HIGHPRI
618c2ecf20Sopenharmony_ci				| WQ_CPU_INTENSIVE, 1);
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	if (!priv->bh_workqueue)
648c2ecf20Sopenharmony_ci		return -ENOMEM;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	INIT_WORK(&priv->bh_work, cw1200_bh_work);
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	pr_debug("[BH] register.\n");
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	atomic_set(&priv->bh_rx, 0);
718c2ecf20Sopenharmony_ci	atomic_set(&priv->bh_tx, 0);
728c2ecf20Sopenharmony_ci	atomic_set(&priv->bh_term, 0);
738c2ecf20Sopenharmony_ci	atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED);
748c2ecf20Sopenharmony_ci	priv->bh_error = 0;
758c2ecf20Sopenharmony_ci	priv->hw_bufs_used = 0;
768c2ecf20Sopenharmony_ci	priv->buf_id_tx = 0;
778c2ecf20Sopenharmony_ci	priv->buf_id_rx = 0;
788c2ecf20Sopenharmony_ci	init_waitqueue_head(&priv->bh_wq);
798c2ecf20Sopenharmony_ci	init_waitqueue_head(&priv->bh_evt_wq);
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	err = !queue_work(priv->bh_workqueue, &priv->bh_work);
828c2ecf20Sopenharmony_ci	WARN_ON(err);
838c2ecf20Sopenharmony_ci	return err;
848c2ecf20Sopenharmony_ci}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_civoid cw1200_unregister_bh(struct cw1200_common *priv)
878c2ecf20Sopenharmony_ci{
888c2ecf20Sopenharmony_ci	atomic_add(1, &priv->bh_term);
898c2ecf20Sopenharmony_ci	wake_up(&priv->bh_wq);
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	flush_workqueue(priv->bh_workqueue);
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	destroy_workqueue(priv->bh_workqueue);
948c2ecf20Sopenharmony_ci	priv->bh_workqueue = NULL;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	pr_debug("[BH] unregistered.\n");
978c2ecf20Sopenharmony_ci}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_civoid cw1200_irq_handler(struct cw1200_common *priv)
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	pr_debug("[BH] irq.\n");
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	/* Disable Interrupts! */
1048c2ecf20Sopenharmony_ci	/* NOTE:  hwbus_ops->lock already held */
1058c2ecf20Sopenharmony_ci	__cw1200_irq_enable(priv, 0);
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	if (/* WARN_ON */(priv->bh_error))
1088c2ecf20Sopenharmony_ci		return;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	if (atomic_add_return(1, &priv->bh_rx) == 1)
1118c2ecf20Sopenharmony_ci		wake_up(&priv->bh_wq);
1128c2ecf20Sopenharmony_ci}
1138c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(cw1200_irq_handler);
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_civoid cw1200_bh_wakeup(struct cw1200_common *priv)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	pr_debug("[BH] wakeup.\n");
1188c2ecf20Sopenharmony_ci	if (priv->bh_error) {
1198c2ecf20Sopenharmony_ci		pr_err("[BH] wakeup failed (BH error)\n");
1208c2ecf20Sopenharmony_ci		return;
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	if (atomic_add_return(1, &priv->bh_tx) == 1)
1248c2ecf20Sopenharmony_ci		wake_up(&priv->bh_wq);
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ciint cw1200_bh_suspend(struct cw1200_common *priv)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	pr_debug("[BH] suspend.\n");
1308c2ecf20Sopenharmony_ci	if (priv->bh_error) {
1318c2ecf20Sopenharmony_ci		wiphy_warn(priv->hw->wiphy, "BH error -- can't suspend\n");
1328c2ecf20Sopenharmony_ci		return -EINVAL;
1338c2ecf20Sopenharmony_ci	}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	atomic_set(&priv->bh_suspend, CW1200_BH_SUSPEND);
1368c2ecf20Sopenharmony_ci	wake_up(&priv->bh_wq);
1378c2ecf20Sopenharmony_ci	return wait_event_timeout(priv->bh_evt_wq, priv->bh_error ||
1388c2ecf20Sopenharmony_ci		(CW1200_BH_SUSPENDED == atomic_read(&priv->bh_suspend)),
1398c2ecf20Sopenharmony_ci		 1 * HZ) ? 0 : -ETIMEDOUT;
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ciint cw1200_bh_resume(struct cw1200_common *priv)
1438c2ecf20Sopenharmony_ci{
1448c2ecf20Sopenharmony_ci	pr_debug("[BH] resume.\n");
1458c2ecf20Sopenharmony_ci	if (priv->bh_error) {
1468c2ecf20Sopenharmony_ci		wiphy_warn(priv->hw->wiphy, "BH error -- can't resume\n");
1478c2ecf20Sopenharmony_ci		return -EINVAL;
1488c2ecf20Sopenharmony_ci	}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	atomic_set(&priv->bh_suspend, CW1200_BH_RESUME);
1518c2ecf20Sopenharmony_ci	wake_up(&priv->bh_wq);
1528c2ecf20Sopenharmony_ci	return wait_event_timeout(priv->bh_evt_wq, priv->bh_error ||
1538c2ecf20Sopenharmony_ci		(CW1200_BH_RESUMED == atomic_read(&priv->bh_suspend)),
1548c2ecf20Sopenharmony_ci		1 * HZ) ? 0 : -ETIMEDOUT;
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic inline void wsm_alloc_tx_buffer(struct cw1200_common *priv)
1588c2ecf20Sopenharmony_ci{
1598c2ecf20Sopenharmony_ci	++priv->hw_bufs_used;
1608c2ecf20Sopenharmony_ci}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ciint wsm_release_tx_buffer(struct cw1200_common *priv, int count)
1638c2ecf20Sopenharmony_ci{
1648c2ecf20Sopenharmony_ci	int ret = 0;
1658c2ecf20Sopenharmony_ci	int hw_bufs_used = priv->hw_bufs_used;
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	priv->hw_bufs_used -= count;
1688c2ecf20Sopenharmony_ci	if (WARN_ON(priv->hw_bufs_used < 0))
1698c2ecf20Sopenharmony_ci		ret = -1;
1708c2ecf20Sopenharmony_ci	else if (hw_bufs_used >= priv->wsm_caps.input_buffers)
1718c2ecf20Sopenharmony_ci		ret = 1;
1728c2ecf20Sopenharmony_ci	if (!priv->hw_bufs_used)
1738c2ecf20Sopenharmony_ci		wake_up(&priv->bh_evt_wq);
1748c2ecf20Sopenharmony_ci	return ret;
1758c2ecf20Sopenharmony_ci}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_cistatic int cw1200_bh_read_ctrl_reg(struct cw1200_common *priv,
1788c2ecf20Sopenharmony_ci					  u16 *ctrl_reg)
1798c2ecf20Sopenharmony_ci{
1808c2ecf20Sopenharmony_ci	int ret;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	ret = cw1200_reg_read_16(priv,
1838c2ecf20Sopenharmony_ci			ST90TDS_CONTROL_REG_ID, ctrl_reg);
1848c2ecf20Sopenharmony_ci	if (ret) {
1858c2ecf20Sopenharmony_ci		ret = cw1200_reg_read_16(priv,
1868c2ecf20Sopenharmony_ci				ST90TDS_CONTROL_REG_ID, ctrl_reg);
1878c2ecf20Sopenharmony_ci		if (ret)
1888c2ecf20Sopenharmony_ci			pr_err("[BH] Failed to read control register.\n");
1898c2ecf20Sopenharmony_ci	}
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	return ret;
1928c2ecf20Sopenharmony_ci}
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_cistatic int cw1200_device_wakeup(struct cw1200_common *priv)
1958c2ecf20Sopenharmony_ci{
1968c2ecf20Sopenharmony_ci	u16 ctrl_reg;
1978c2ecf20Sopenharmony_ci	int ret;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	pr_debug("[BH] Device wakeup.\n");
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	/* First, set the dpll register */
2028c2ecf20Sopenharmony_ci	ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID,
2038c2ecf20Sopenharmony_ci				  cw1200_dpll_from_clk(priv->hw_refclk));
2048c2ecf20Sopenharmony_ci	if (WARN_ON(ret))
2058c2ecf20Sopenharmony_ci		return ret;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	/* To force the device to be always-on, the host sets WLAN_UP to 1 */
2088c2ecf20Sopenharmony_ci	ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID,
2098c2ecf20Sopenharmony_ci			ST90TDS_CONT_WUP_BIT);
2108c2ecf20Sopenharmony_ci	if (WARN_ON(ret))
2118c2ecf20Sopenharmony_ci		return ret;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	ret = cw1200_bh_read_ctrl_reg(priv, &ctrl_reg);
2148c2ecf20Sopenharmony_ci	if (WARN_ON(ret))
2158c2ecf20Sopenharmony_ci		return ret;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	/* If the device returns WLAN_RDY as 1, the device is active and will
2188c2ecf20Sopenharmony_ci	 * remain active.
2198c2ecf20Sopenharmony_ci	 */
2208c2ecf20Sopenharmony_ci	if (ctrl_reg & ST90TDS_CONT_RDY_BIT) {
2218c2ecf20Sopenharmony_ci		pr_debug("[BH] Device awake.\n");
2228c2ecf20Sopenharmony_ci		return 1;
2238c2ecf20Sopenharmony_ci	}
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	return 0;
2268c2ecf20Sopenharmony_ci}
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci/* Must be called from BH thraed. */
2298c2ecf20Sopenharmony_civoid cw1200_enable_powersave(struct cw1200_common *priv,
2308c2ecf20Sopenharmony_ci			     bool enable)
2318c2ecf20Sopenharmony_ci{
2328c2ecf20Sopenharmony_ci	pr_debug("[BH] Powerave is %s.\n",
2338c2ecf20Sopenharmony_ci		 enable ? "enabled" : "disabled");
2348c2ecf20Sopenharmony_ci	priv->powersave_enabled = enable;
2358c2ecf20Sopenharmony_ci}
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_cistatic int cw1200_bh_rx_helper(struct cw1200_common *priv,
2388c2ecf20Sopenharmony_ci			       uint16_t *ctrl_reg,
2398c2ecf20Sopenharmony_ci			       int *tx)
2408c2ecf20Sopenharmony_ci{
2418c2ecf20Sopenharmony_ci	size_t read_len = 0;
2428c2ecf20Sopenharmony_ci	struct sk_buff *skb_rx = NULL;
2438c2ecf20Sopenharmony_ci	struct wsm_hdr *wsm;
2448c2ecf20Sopenharmony_ci	size_t wsm_len;
2458c2ecf20Sopenharmony_ci	u16 wsm_id;
2468c2ecf20Sopenharmony_ci	u8 wsm_seq;
2478c2ecf20Sopenharmony_ci	int rx_resync = 1;
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	size_t alloc_len;
2508c2ecf20Sopenharmony_ci	u8 *data;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	read_len = (*ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) * 2;
2538c2ecf20Sopenharmony_ci	if (!read_len)
2548c2ecf20Sopenharmony_ci		return 0; /* No more work */
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	if (WARN_ON((read_len < sizeof(struct wsm_hdr)) ||
2578c2ecf20Sopenharmony_ci		    (read_len > EFFECTIVE_BUF_SIZE))) {
2588c2ecf20Sopenharmony_ci		pr_debug("Invalid read len: %zu (%04x)",
2598c2ecf20Sopenharmony_ci			 read_len, *ctrl_reg);
2608c2ecf20Sopenharmony_ci		goto err;
2618c2ecf20Sopenharmony_ci	}
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	/* Add SIZE of PIGGYBACK reg (CONTROL Reg)
2648c2ecf20Sopenharmony_ci	 * to the NEXT Message length + 2 Bytes for SKB
2658c2ecf20Sopenharmony_ci	 */
2668c2ecf20Sopenharmony_ci	read_len = read_len + 2;
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	alloc_len = priv->hwbus_ops->align_size(
2698c2ecf20Sopenharmony_ci		priv->hwbus_priv, read_len);
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	/* Check if not exceeding CW1200 capabilities */
2728c2ecf20Sopenharmony_ci	if (WARN_ON_ONCE(alloc_len > EFFECTIVE_BUF_SIZE)) {
2738c2ecf20Sopenharmony_ci		pr_debug("Read aligned len: %zu\n",
2748c2ecf20Sopenharmony_ci			 alloc_len);
2758c2ecf20Sopenharmony_ci	}
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	skb_rx = dev_alloc_skb(alloc_len);
2788c2ecf20Sopenharmony_ci	if (WARN_ON(!skb_rx))
2798c2ecf20Sopenharmony_ci		goto err;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	skb_trim(skb_rx, 0);
2828c2ecf20Sopenharmony_ci	skb_put(skb_rx, read_len);
2838c2ecf20Sopenharmony_ci	data = skb_rx->data;
2848c2ecf20Sopenharmony_ci	if (WARN_ON(!data))
2858c2ecf20Sopenharmony_ci		goto err;
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	if (WARN_ON(cw1200_data_read(priv, data, alloc_len))) {
2888c2ecf20Sopenharmony_ci		pr_err("rx blew up, len %zu\n", alloc_len);
2898c2ecf20Sopenharmony_ci		goto err;
2908c2ecf20Sopenharmony_ci	}
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	/* Piggyback */
2938c2ecf20Sopenharmony_ci	*ctrl_reg = __le16_to_cpu(
2948c2ecf20Sopenharmony_ci		((__le16 *)data)[alloc_len / 2 - 1]);
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	wsm = (struct wsm_hdr *)data;
2978c2ecf20Sopenharmony_ci	wsm_len = __le16_to_cpu(wsm->len);
2988c2ecf20Sopenharmony_ci	if (WARN_ON(wsm_len > read_len))
2998c2ecf20Sopenharmony_ci		goto err;
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	if (priv->wsm_enable_wsm_dumps)
3028c2ecf20Sopenharmony_ci		print_hex_dump_bytes("<-- ",
3038c2ecf20Sopenharmony_ci				     DUMP_PREFIX_NONE,
3048c2ecf20Sopenharmony_ci				     data, wsm_len);
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci	wsm_id  = __le16_to_cpu(wsm->id) & 0xFFF;
3078c2ecf20Sopenharmony_ci	wsm_seq = (__le16_to_cpu(wsm->id) >> 13) & 7;
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	skb_trim(skb_rx, wsm_len);
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci	if (wsm_id == 0x0800) {
3128c2ecf20Sopenharmony_ci		wsm_handle_exception(priv,
3138c2ecf20Sopenharmony_ci				     &data[sizeof(*wsm)],
3148c2ecf20Sopenharmony_ci				     wsm_len - sizeof(*wsm));
3158c2ecf20Sopenharmony_ci		goto err;
3168c2ecf20Sopenharmony_ci	} else if (!rx_resync) {
3178c2ecf20Sopenharmony_ci		if (WARN_ON(wsm_seq != priv->wsm_rx_seq))
3188c2ecf20Sopenharmony_ci			goto err;
3198c2ecf20Sopenharmony_ci	}
3208c2ecf20Sopenharmony_ci	priv->wsm_rx_seq = (wsm_seq + 1) & 7;
3218c2ecf20Sopenharmony_ci	rx_resync = 0;
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	if (wsm_id & 0x0400) {
3248c2ecf20Sopenharmony_ci		int rc = wsm_release_tx_buffer(priv, 1);
3258c2ecf20Sopenharmony_ci		if (WARN_ON(rc < 0))
3268c2ecf20Sopenharmony_ci			return rc;
3278c2ecf20Sopenharmony_ci		else if (rc > 0)
3288c2ecf20Sopenharmony_ci			*tx = 1;
3298c2ecf20Sopenharmony_ci	}
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	/* cw1200_wsm_rx takes care on SKB livetime */
3328c2ecf20Sopenharmony_ci	if (WARN_ON(wsm_handle_rx(priv, wsm_id, wsm, &skb_rx)))
3338c2ecf20Sopenharmony_ci		goto err;
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci	if (skb_rx) {
3368c2ecf20Sopenharmony_ci		dev_kfree_skb(skb_rx);
3378c2ecf20Sopenharmony_ci		skb_rx = NULL;
3388c2ecf20Sopenharmony_ci	}
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci	return 0;
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_cierr:
3438c2ecf20Sopenharmony_ci	if (skb_rx) {
3448c2ecf20Sopenharmony_ci		dev_kfree_skb(skb_rx);
3458c2ecf20Sopenharmony_ci		skb_rx = NULL;
3468c2ecf20Sopenharmony_ci	}
3478c2ecf20Sopenharmony_ci	return -1;
3488c2ecf20Sopenharmony_ci}
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_cistatic int cw1200_bh_tx_helper(struct cw1200_common *priv,
3518c2ecf20Sopenharmony_ci			       int *pending_tx,
3528c2ecf20Sopenharmony_ci			       int *tx_burst)
3538c2ecf20Sopenharmony_ci{
3548c2ecf20Sopenharmony_ci	size_t tx_len;
3558c2ecf20Sopenharmony_ci	u8 *data;
3568c2ecf20Sopenharmony_ci	int ret;
3578c2ecf20Sopenharmony_ci	struct wsm_hdr *wsm;
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	if (priv->device_can_sleep) {
3608c2ecf20Sopenharmony_ci		ret = cw1200_device_wakeup(priv);
3618c2ecf20Sopenharmony_ci		if (WARN_ON(ret < 0)) { /* Error in wakeup */
3628c2ecf20Sopenharmony_ci			*pending_tx = 1;
3638c2ecf20Sopenharmony_ci			return 0;
3648c2ecf20Sopenharmony_ci		} else if (ret) { /* Woke up */
3658c2ecf20Sopenharmony_ci			priv->device_can_sleep = false;
3668c2ecf20Sopenharmony_ci		} else { /* Did not awake */
3678c2ecf20Sopenharmony_ci			*pending_tx = 1;
3688c2ecf20Sopenharmony_ci			return 0;
3698c2ecf20Sopenharmony_ci		}
3708c2ecf20Sopenharmony_ci	}
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ci	wsm_alloc_tx_buffer(priv);
3738c2ecf20Sopenharmony_ci	ret = wsm_get_tx(priv, &data, &tx_len, tx_burst);
3748c2ecf20Sopenharmony_ci	if (ret <= 0) {
3758c2ecf20Sopenharmony_ci		wsm_release_tx_buffer(priv, 1);
3768c2ecf20Sopenharmony_ci		if (WARN_ON(ret < 0))
3778c2ecf20Sopenharmony_ci			return ret; /* Error */
3788c2ecf20Sopenharmony_ci		return 0; /* No work */
3798c2ecf20Sopenharmony_ci	}
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	wsm = (struct wsm_hdr *)data;
3828c2ecf20Sopenharmony_ci	BUG_ON(tx_len < sizeof(*wsm));
3838c2ecf20Sopenharmony_ci	BUG_ON(__le16_to_cpu(wsm->len) != tx_len);
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci	atomic_add(1, &priv->bh_tx);
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	tx_len = priv->hwbus_ops->align_size(
3888c2ecf20Sopenharmony_ci		priv->hwbus_priv, tx_len);
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_ci	/* Check if not exceeding CW1200 capabilities */
3918c2ecf20Sopenharmony_ci	if (WARN_ON_ONCE(tx_len > EFFECTIVE_BUF_SIZE))
3928c2ecf20Sopenharmony_ci		pr_debug("Write aligned len: %zu\n", tx_len);
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_ci	wsm->id &= __cpu_to_le16(0xffff ^ WSM_TX_SEQ(WSM_TX_SEQ_MAX));
3958c2ecf20Sopenharmony_ci	wsm->id |= __cpu_to_le16(WSM_TX_SEQ(priv->wsm_tx_seq));
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ci	if (WARN_ON(cw1200_data_write(priv, data, tx_len))) {
3988c2ecf20Sopenharmony_ci		pr_err("tx blew up, len %zu\n", tx_len);
3998c2ecf20Sopenharmony_ci		wsm_release_tx_buffer(priv, 1);
4008c2ecf20Sopenharmony_ci		return -1; /* Error */
4018c2ecf20Sopenharmony_ci	}
4028c2ecf20Sopenharmony_ci
4038c2ecf20Sopenharmony_ci	if (priv->wsm_enable_wsm_dumps)
4048c2ecf20Sopenharmony_ci		print_hex_dump_bytes("--> ",
4058c2ecf20Sopenharmony_ci				     DUMP_PREFIX_NONE,
4068c2ecf20Sopenharmony_ci				     data,
4078c2ecf20Sopenharmony_ci				     __le16_to_cpu(wsm->len));
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	wsm_txed(priv, data);
4108c2ecf20Sopenharmony_ci	priv->wsm_tx_seq = (priv->wsm_tx_seq + 1) & WSM_TX_SEQ_MAX;
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	if (*tx_burst > 1) {
4138c2ecf20Sopenharmony_ci		cw1200_debug_tx_burst(priv);
4148c2ecf20Sopenharmony_ci		return 1; /* Work remains */
4158c2ecf20Sopenharmony_ci	}
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci	return 0;
4188c2ecf20Sopenharmony_ci}
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_cistatic int cw1200_bh(void *arg)
4218c2ecf20Sopenharmony_ci{
4228c2ecf20Sopenharmony_ci	struct cw1200_common *priv = arg;
4238c2ecf20Sopenharmony_ci	int rx, tx, term, suspend;
4248c2ecf20Sopenharmony_ci	u16 ctrl_reg = 0;
4258c2ecf20Sopenharmony_ci	int tx_allowed;
4268c2ecf20Sopenharmony_ci	int pending_tx = 0;
4278c2ecf20Sopenharmony_ci	int tx_burst;
4288c2ecf20Sopenharmony_ci	long status;
4298c2ecf20Sopenharmony_ci	u32 dummy;
4308c2ecf20Sopenharmony_ci	int ret;
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ci	for (;;) {
4338c2ecf20Sopenharmony_ci		if (!priv->hw_bufs_used &&
4348c2ecf20Sopenharmony_ci		    priv->powersave_enabled &&
4358c2ecf20Sopenharmony_ci		    !priv->device_can_sleep &&
4368c2ecf20Sopenharmony_ci		    !atomic_read(&priv->recent_scan)) {
4378c2ecf20Sopenharmony_ci			status = 1 * HZ;
4388c2ecf20Sopenharmony_ci			pr_debug("[BH] Device wakedown. No data.\n");
4398c2ecf20Sopenharmony_ci			cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID, 0);
4408c2ecf20Sopenharmony_ci			priv->device_can_sleep = true;
4418c2ecf20Sopenharmony_ci		} else if (priv->hw_bufs_used) {
4428c2ecf20Sopenharmony_ci			/* Interrupt loss detection */
4438c2ecf20Sopenharmony_ci			status = 1 * HZ;
4448c2ecf20Sopenharmony_ci		} else {
4458c2ecf20Sopenharmony_ci			status = MAX_SCHEDULE_TIMEOUT;
4468c2ecf20Sopenharmony_ci		}
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ci		/* Dummy Read for SDIO retry mechanism*/
4498c2ecf20Sopenharmony_ci		if ((priv->hw_type != -1) &&
4508c2ecf20Sopenharmony_ci		    (atomic_read(&priv->bh_rx) == 0) &&
4518c2ecf20Sopenharmony_ci		    (atomic_read(&priv->bh_tx) == 0))
4528c2ecf20Sopenharmony_ci			cw1200_reg_read(priv, ST90TDS_CONFIG_REG_ID,
4538c2ecf20Sopenharmony_ci					&dummy, sizeof(dummy));
4548c2ecf20Sopenharmony_ci
4558c2ecf20Sopenharmony_ci		pr_debug("[BH] waiting ...\n");
4568c2ecf20Sopenharmony_ci		status = wait_event_interruptible_timeout(priv->bh_wq, ({
4578c2ecf20Sopenharmony_ci				rx = atomic_xchg(&priv->bh_rx, 0);
4588c2ecf20Sopenharmony_ci				tx = atomic_xchg(&priv->bh_tx, 0);
4598c2ecf20Sopenharmony_ci				term = atomic_xchg(&priv->bh_term, 0);
4608c2ecf20Sopenharmony_ci				suspend = pending_tx ?
4618c2ecf20Sopenharmony_ci					0 : atomic_read(&priv->bh_suspend);
4628c2ecf20Sopenharmony_ci				(rx || tx || term || suspend || priv->bh_error);
4638c2ecf20Sopenharmony_ci			}), status);
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_ci		pr_debug("[BH] - rx: %d, tx: %d, term: %d, bh_err: %d, suspend: %d, status: %ld\n",
4668c2ecf20Sopenharmony_ci			 rx, tx, term, suspend, priv->bh_error, status);
4678c2ecf20Sopenharmony_ci
4688c2ecf20Sopenharmony_ci		/* Did an error occur? */
4698c2ecf20Sopenharmony_ci		if ((status < 0 && status != -ERESTARTSYS) ||
4708c2ecf20Sopenharmony_ci		    term || priv->bh_error) {
4718c2ecf20Sopenharmony_ci			break;
4728c2ecf20Sopenharmony_ci		}
4738c2ecf20Sopenharmony_ci		if (!status) {  /* wait_event timed out */
4748c2ecf20Sopenharmony_ci			unsigned long timestamp = jiffies;
4758c2ecf20Sopenharmony_ci			long timeout;
4768c2ecf20Sopenharmony_ci			int pending = 0;
4778c2ecf20Sopenharmony_ci			int i;
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci			/* Check to see if we have any outstanding frames */
4808c2ecf20Sopenharmony_ci			if (priv->hw_bufs_used && (!rx || !tx)) {
4818c2ecf20Sopenharmony_ci				wiphy_warn(priv->hw->wiphy,
4828c2ecf20Sopenharmony_ci					   "Missed interrupt? (%d frames outstanding)\n",
4838c2ecf20Sopenharmony_ci					   priv->hw_bufs_used);
4848c2ecf20Sopenharmony_ci				rx = 1;
4858c2ecf20Sopenharmony_ci
4868c2ecf20Sopenharmony_ci				/* Get a timestamp of "oldest" frame */
4878c2ecf20Sopenharmony_ci				for (i = 0; i < 4; ++i)
4888c2ecf20Sopenharmony_ci					pending += cw1200_queue_get_xmit_timestamp(
4898c2ecf20Sopenharmony_ci						&priv->tx_queue[i],
4908c2ecf20Sopenharmony_ci						&timestamp,
4918c2ecf20Sopenharmony_ci						priv->pending_frame_id);
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_ci				/* Check if frame transmission is timed out.
4948c2ecf20Sopenharmony_ci				 * Add an extra second with respect to possible
4958c2ecf20Sopenharmony_ci				 * interrupt loss.
4968c2ecf20Sopenharmony_ci				 */
4978c2ecf20Sopenharmony_ci				timeout = timestamp +
4988c2ecf20Sopenharmony_ci					WSM_CMD_LAST_CHANCE_TIMEOUT +
4998c2ecf20Sopenharmony_ci					1 * HZ  -
5008c2ecf20Sopenharmony_ci					jiffies;
5018c2ecf20Sopenharmony_ci
5028c2ecf20Sopenharmony_ci				/* And terminate BH thread if the frame is "stuck" */
5038c2ecf20Sopenharmony_ci				if (pending && timeout < 0) {
5048c2ecf20Sopenharmony_ci					wiphy_warn(priv->hw->wiphy,
5058c2ecf20Sopenharmony_ci						   "Timeout waiting for TX confirm (%d/%d pending, %ld vs %lu).\n",
5068c2ecf20Sopenharmony_ci						   priv->hw_bufs_used, pending,
5078c2ecf20Sopenharmony_ci						   timestamp, jiffies);
5088c2ecf20Sopenharmony_ci					break;
5098c2ecf20Sopenharmony_ci				}
5108c2ecf20Sopenharmony_ci			} else if (!priv->device_can_sleep &&
5118c2ecf20Sopenharmony_ci				   !atomic_read(&priv->recent_scan)) {
5128c2ecf20Sopenharmony_ci				pr_debug("[BH] Device wakedown. Timeout.\n");
5138c2ecf20Sopenharmony_ci				cw1200_reg_write_16(priv,
5148c2ecf20Sopenharmony_ci						    ST90TDS_CONTROL_REG_ID, 0);
5158c2ecf20Sopenharmony_ci				priv->device_can_sleep = true;
5168c2ecf20Sopenharmony_ci			}
5178c2ecf20Sopenharmony_ci			goto done;
5188c2ecf20Sopenharmony_ci		} else if (suspend) {
5198c2ecf20Sopenharmony_ci			pr_debug("[BH] Device suspend.\n");
5208c2ecf20Sopenharmony_ci			if (priv->powersave_enabled) {
5218c2ecf20Sopenharmony_ci				pr_debug("[BH] Device wakedown. Suspend.\n");
5228c2ecf20Sopenharmony_ci				cw1200_reg_write_16(priv,
5238c2ecf20Sopenharmony_ci						    ST90TDS_CONTROL_REG_ID, 0);
5248c2ecf20Sopenharmony_ci				priv->device_can_sleep = true;
5258c2ecf20Sopenharmony_ci			}
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ci			atomic_set(&priv->bh_suspend, CW1200_BH_SUSPENDED);
5288c2ecf20Sopenharmony_ci			wake_up(&priv->bh_evt_wq);
5298c2ecf20Sopenharmony_ci			status = wait_event_interruptible(priv->bh_wq,
5308c2ecf20Sopenharmony_ci							  CW1200_BH_RESUME == atomic_read(&priv->bh_suspend));
5318c2ecf20Sopenharmony_ci			if (status < 0) {
5328c2ecf20Sopenharmony_ci				wiphy_err(priv->hw->wiphy,
5338c2ecf20Sopenharmony_ci					  "Failed to wait for resume: %ld.\n",
5348c2ecf20Sopenharmony_ci					  status);
5358c2ecf20Sopenharmony_ci				break;
5368c2ecf20Sopenharmony_ci			}
5378c2ecf20Sopenharmony_ci			pr_debug("[BH] Device resume.\n");
5388c2ecf20Sopenharmony_ci			atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED);
5398c2ecf20Sopenharmony_ci			wake_up(&priv->bh_evt_wq);
5408c2ecf20Sopenharmony_ci			atomic_add(1, &priv->bh_rx);
5418c2ecf20Sopenharmony_ci			goto done;
5428c2ecf20Sopenharmony_ci		}
5438c2ecf20Sopenharmony_ci
5448c2ecf20Sopenharmony_ci	rx:
5458c2ecf20Sopenharmony_ci		tx += pending_tx;
5468c2ecf20Sopenharmony_ci		pending_tx = 0;
5478c2ecf20Sopenharmony_ci
5488c2ecf20Sopenharmony_ci		if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg))
5498c2ecf20Sopenharmony_ci			break;
5508c2ecf20Sopenharmony_ci
5518c2ecf20Sopenharmony_ci		/* Don't bother trying to rx unless we have data to read */
5528c2ecf20Sopenharmony_ci		if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) {
5538c2ecf20Sopenharmony_ci			ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx);
5548c2ecf20Sopenharmony_ci			if (ret < 0)
5558c2ecf20Sopenharmony_ci				break;
5568c2ecf20Sopenharmony_ci			/* Double up here if there's more data.. */
5578c2ecf20Sopenharmony_ci			if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) {
5588c2ecf20Sopenharmony_ci				ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx);
5598c2ecf20Sopenharmony_ci				if (ret < 0)
5608c2ecf20Sopenharmony_ci					break;
5618c2ecf20Sopenharmony_ci			}
5628c2ecf20Sopenharmony_ci		}
5638c2ecf20Sopenharmony_ci
5648c2ecf20Sopenharmony_ci	tx:
5658c2ecf20Sopenharmony_ci		if (tx) {
5668c2ecf20Sopenharmony_ci			tx = 0;
5678c2ecf20Sopenharmony_ci
5688c2ecf20Sopenharmony_ci			BUG_ON(priv->hw_bufs_used > priv->wsm_caps.input_buffers);
5698c2ecf20Sopenharmony_ci			tx_burst = priv->wsm_caps.input_buffers - priv->hw_bufs_used;
5708c2ecf20Sopenharmony_ci			tx_allowed = tx_burst > 0;
5718c2ecf20Sopenharmony_ci
5728c2ecf20Sopenharmony_ci			if (!tx_allowed) {
5738c2ecf20Sopenharmony_ci				/* Buffers full.  Ensure we process tx
5748c2ecf20Sopenharmony_ci				 * after we handle rx..
5758c2ecf20Sopenharmony_ci				 */
5768c2ecf20Sopenharmony_ci				pending_tx = tx;
5778c2ecf20Sopenharmony_ci				goto done_rx;
5788c2ecf20Sopenharmony_ci			}
5798c2ecf20Sopenharmony_ci			ret = cw1200_bh_tx_helper(priv, &pending_tx, &tx_burst);
5808c2ecf20Sopenharmony_ci			if (ret < 0)
5818c2ecf20Sopenharmony_ci				break;
5828c2ecf20Sopenharmony_ci			if (ret > 0) /* More to transmit */
5838c2ecf20Sopenharmony_ci				tx = ret;
5848c2ecf20Sopenharmony_ci
5858c2ecf20Sopenharmony_ci			/* Re-read ctrl reg */
5868c2ecf20Sopenharmony_ci			if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg))
5878c2ecf20Sopenharmony_ci				break;
5888c2ecf20Sopenharmony_ci		}
5898c2ecf20Sopenharmony_ci
5908c2ecf20Sopenharmony_ci	done_rx:
5918c2ecf20Sopenharmony_ci		if (priv->bh_error)
5928c2ecf20Sopenharmony_ci			break;
5938c2ecf20Sopenharmony_ci		if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK)
5948c2ecf20Sopenharmony_ci			goto rx;
5958c2ecf20Sopenharmony_ci		if (tx)
5968c2ecf20Sopenharmony_ci			goto tx;
5978c2ecf20Sopenharmony_ci
5988c2ecf20Sopenharmony_ci	done:
5998c2ecf20Sopenharmony_ci		/* Re-enable device interrupts */
6008c2ecf20Sopenharmony_ci		priv->hwbus_ops->lock(priv->hwbus_priv);
6018c2ecf20Sopenharmony_ci		__cw1200_irq_enable(priv, 1);
6028c2ecf20Sopenharmony_ci		priv->hwbus_ops->unlock(priv->hwbus_priv);
6038c2ecf20Sopenharmony_ci	}
6048c2ecf20Sopenharmony_ci
6058c2ecf20Sopenharmony_ci	/* Explicitly disable device interrupts */
6068c2ecf20Sopenharmony_ci	priv->hwbus_ops->lock(priv->hwbus_priv);
6078c2ecf20Sopenharmony_ci	__cw1200_irq_enable(priv, 0);
6088c2ecf20Sopenharmony_ci	priv->hwbus_ops->unlock(priv->hwbus_priv);
6098c2ecf20Sopenharmony_ci
6108c2ecf20Sopenharmony_ci	if (!term) {
6118c2ecf20Sopenharmony_ci		pr_err("[BH] Fatal error, exiting.\n");
6128c2ecf20Sopenharmony_ci		priv->bh_error = 1;
6138c2ecf20Sopenharmony_ci		/* TODO: schedule_work(recovery) */
6148c2ecf20Sopenharmony_ci	}
6158c2ecf20Sopenharmony_ci	return 0;
6168c2ecf20Sopenharmony_ci}
617