xref: /linux/drivers/net/wireless/st/cw1200/bh.c (revision c441bfb5f2866de71e092c1b9d866a65978dfe1a)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2a910e4a9SSolomon Peachy /*
3a910e4a9SSolomon Peachy  * Device handling thread implementation for mac80211 ST-Ericsson CW1200 drivers
4a910e4a9SSolomon Peachy  *
5a910e4a9SSolomon Peachy  * Copyright (c) 2010, ST-Ericsson
6a910e4a9SSolomon Peachy  * Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
7a910e4a9SSolomon Peachy  *
8a910e4a9SSolomon Peachy  * Based on:
9a910e4a9SSolomon Peachy  * ST-Ericsson UMAC CW1200 driver, which is
10a910e4a9SSolomon Peachy  * Copyright (c) 2010, ST-Ericsson
11a910e4a9SSolomon Peachy  * Author: Ajitpal Singh <ajitpal.singh@stericsson.com>
12a910e4a9SSolomon Peachy  */
13a910e4a9SSolomon Peachy 
14a910e4a9SSolomon Peachy #include <linux/module.h>
15a910e4a9SSolomon Peachy #include <net/mac80211.h>
16a910e4a9SSolomon Peachy #include <linux/kthread.h>
17a910e4a9SSolomon Peachy #include <linux/timer.h>
18a910e4a9SSolomon Peachy 
19a910e4a9SSolomon Peachy #include "cw1200.h"
20a910e4a9SSolomon Peachy #include "bh.h"
21a910e4a9SSolomon Peachy #include "hwio.h"
22a910e4a9SSolomon Peachy #include "wsm.h"
23911373ccSSolomon Peachy #include "hwbus.h"
24a910e4a9SSolomon Peachy #include "debug.h"
25a910e4a9SSolomon Peachy #include "fwio.h"
26a910e4a9SSolomon Peachy 
27a910e4a9SSolomon Peachy static int cw1200_bh(void *arg);
28a910e4a9SSolomon Peachy 
29a910e4a9SSolomon Peachy #define DOWNLOAD_BLOCK_SIZE_WR	(0x1000 - 4)
30a910e4a9SSolomon Peachy /* an SPI message cannot be bigger than (2"12-1)*2 bytes
318b3e7be4SSolomon Peachy  * "*2" to cvt to bytes
328b3e7be4SSolomon Peachy  */
33a910e4a9SSolomon Peachy #define MAX_SZ_RD_WR_BUFFERS	(DOWNLOAD_BLOCK_SIZE_WR*2)
34a910e4a9SSolomon Peachy #define PIGGYBACK_CTRL_REG	(2)
35a910e4a9SSolomon Peachy #define EFFECTIVE_BUF_SIZE	(MAX_SZ_RD_WR_BUFFERS - PIGGYBACK_CTRL_REG)
36a910e4a9SSolomon Peachy 
37a910e4a9SSolomon Peachy /* Suspend state privates */
38a910e4a9SSolomon Peachy enum cw1200_bh_pm_state {
39a910e4a9SSolomon Peachy 	CW1200_BH_RESUMED = 0,
40a910e4a9SSolomon Peachy 	CW1200_BH_SUSPEND,
41a910e4a9SSolomon Peachy 	CW1200_BH_SUSPENDED,
42a910e4a9SSolomon Peachy 	CW1200_BH_RESUME,
43a910e4a9SSolomon Peachy };
44a910e4a9SSolomon Peachy 
45a910e4a9SSolomon Peachy static void cw1200_bh_work(struct work_struct *work)
46a910e4a9SSolomon Peachy {
47a910e4a9SSolomon Peachy 	struct cw1200_common *priv =
48a910e4a9SSolomon Peachy 	container_of(work, struct cw1200_common, bh_work);
49a910e4a9SSolomon Peachy 	cw1200_bh(priv);
50a910e4a9SSolomon Peachy }
51a910e4a9SSolomon Peachy 
52a910e4a9SSolomon Peachy int cw1200_register_bh(struct cw1200_common *priv)
53a910e4a9SSolomon Peachy {
54a910e4a9SSolomon Peachy 	int err = 0;
55a910e4a9SSolomon Peachy 	/* Realtime workqueue */
56a910e4a9SSolomon Peachy 	priv->bh_workqueue = alloc_workqueue("cw1200_bh",
57a910e4a9SSolomon Peachy 				WQ_MEM_RECLAIM | WQ_HIGHPRI
58a910e4a9SSolomon Peachy 				| WQ_CPU_INTENSIVE, 1);
59a910e4a9SSolomon Peachy 
60a910e4a9SSolomon Peachy 	if (!priv->bh_workqueue)
61a910e4a9SSolomon Peachy 		return -ENOMEM;
62a910e4a9SSolomon Peachy 
63a910e4a9SSolomon Peachy 	INIT_WORK(&priv->bh_work, cw1200_bh_work);
64a910e4a9SSolomon Peachy 
65a910e4a9SSolomon Peachy 	pr_debug("[BH] register.\n");
66a910e4a9SSolomon Peachy 
67a910e4a9SSolomon Peachy 	atomic_set(&priv->bh_rx, 0);
68a910e4a9SSolomon Peachy 	atomic_set(&priv->bh_tx, 0);
69a910e4a9SSolomon Peachy 	atomic_set(&priv->bh_term, 0);
70a910e4a9SSolomon Peachy 	atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED);
71a910e4a9SSolomon Peachy 	priv->bh_error = 0;
72a910e4a9SSolomon Peachy 	priv->hw_bufs_used = 0;
73a910e4a9SSolomon Peachy 	priv->buf_id_tx = 0;
74a910e4a9SSolomon Peachy 	priv->buf_id_rx = 0;
75a910e4a9SSolomon Peachy 	init_waitqueue_head(&priv->bh_wq);
76a910e4a9SSolomon Peachy 	init_waitqueue_head(&priv->bh_evt_wq);
77a910e4a9SSolomon Peachy 
78a910e4a9SSolomon Peachy 	err = !queue_work(priv->bh_workqueue, &priv->bh_work);
79a910e4a9SSolomon Peachy 	WARN_ON(err);
80a910e4a9SSolomon Peachy 	return err;
81a910e4a9SSolomon Peachy }
82a910e4a9SSolomon Peachy 
83a910e4a9SSolomon Peachy void cw1200_unregister_bh(struct cw1200_common *priv)
84a910e4a9SSolomon Peachy {
85*07f995caSYejune Deng 	atomic_inc(&priv->bh_term);
86a910e4a9SSolomon Peachy 	wake_up(&priv->bh_wq);
87a910e4a9SSolomon Peachy 
88a910e4a9SSolomon Peachy 	flush_workqueue(priv->bh_workqueue);
89a910e4a9SSolomon Peachy 
90a910e4a9SSolomon Peachy 	destroy_workqueue(priv->bh_workqueue);
91a910e4a9SSolomon Peachy 	priv->bh_workqueue = NULL;
92a910e4a9SSolomon Peachy 
93a910e4a9SSolomon Peachy 	pr_debug("[BH] unregistered.\n");
94a910e4a9SSolomon Peachy }
95a910e4a9SSolomon Peachy 
96a910e4a9SSolomon Peachy void cw1200_irq_handler(struct cw1200_common *priv)
97a910e4a9SSolomon Peachy {
98a910e4a9SSolomon Peachy 	pr_debug("[BH] irq.\n");
99a910e4a9SSolomon Peachy 
100a910e4a9SSolomon Peachy 	/* Disable Interrupts! */
101911373ccSSolomon Peachy 	/* NOTE:  hwbus_ops->lock already held */
102a910e4a9SSolomon Peachy 	__cw1200_irq_enable(priv, 0);
103a910e4a9SSolomon Peachy 
104a910e4a9SSolomon Peachy 	if (/* WARN_ON */(priv->bh_error))
105a910e4a9SSolomon Peachy 		return;
106a910e4a9SSolomon Peachy 
107*07f995caSYejune Deng 	if (atomic_inc_return(&priv->bh_rx) == 1)
108a910e4a9SSolomon Peachy 		wake_up(&priv->bh_wq);
109a910e4a9SSolomon Peachy }
110a910e4a9SSolomon Peachy EXPORT_SYMBOL_GPL(cw1200_irq_handler);
111a910e4a9SSolomon Peachy 
112a910e4a9SSolomon Peachy void cw1200_bh_wakeup(struct cw1200_common *priv)
113a910e4a9SSolomon Peachy {
114a910e4a9SSolomon Peachy 	pr_debug("[BH] wakeup.\n");
115a910e4a9SSolomon Peachy 	if (priv->bh_error) {
116a910e4a9SSolomon Peachy 		pr_err("[BH] wakeup failed (BH error)\n");
117a910e4a9SSolomon Peachy 		return;
118a910e4a9SSolomon Peachy 	}
119a910e4a9SSolomon Peachy 
120*07f995caSYejune Deng 	if (atomic_inc_return(&priv->bh_tx) == 1)
121a910e4a9SSolomon Peachy 		wake_up(&priv->bh_wq);
122a910e4a9SSolomon Peachy }
123a910e4a9SSolomon Peachy 
124a910e4a9SSolomon Peachy int cw1200_bh_suspend(struct cw1200_common *priv)
125a910e4a9SSolomon Peachy {
126a910e4a9SSolomon Peachy 	pr_debug("[BH] suspend.\n");
127a910e4a9SSolomon Peachy 	if (priv->bh_error) {
128a910e4a9SSolomon Peachy 		wiphy_warn(priv->hw->wiphy, "BH error -- can't suspend\n");
129a910e4a9SSolomon Peachy 		return -EINVAL;
130a910e4a9SSolomon Peachy 	}
131a910e4a9SSolomon Peachy 
132a910e4a9SSolomon Peachy 	atomic_set(&priv->bh_suspend, CW1200_BH_SUSPEND);
133a910e4a9SSolomon Peachy 	wake_up(&priv->bh_wq);
134a910e4a9SSolomon Peachy 	return wait_event_timeout(priv->bh_evt_wq, priv->bh_error ||
135a910e4a9SSolomon Peachy 		(CW1200_BH_SUSPENDED == atomic_read(&priv->bh_suspend)),
136a910e4a9SSolomon Peachy 		 1 * HZ) ? 0 : -ETIMEDOUT;
137a910e4a9SSolomon Peachy }
138a910e4a9SSolomon Peachy 
139a910e4a9SSolomon Peachy int cw1200_bh_resume(struct cw1200_common *priv)
140a910e4a9SSolomon Peachy {
141a910e4a9SSolomon Peachy 	pr_debug("[BH] resume.\n");
142a910e4a9SSolomon Peachy 	if (priv->bh_error) {
143a910e4a9SSolomon Peachy 		wiphy_warn(priv->hw->wiphy, "BH error -- can't resume\n");
144a910e4a9SSolomon Peachy 		return -EINVAL;
145a910e4a9SSolomon Peachy 	}
146a910e4a9SSolomon Peachy 
147a910e4a9SSolomon Peachy 	atomic_set(&priv->bh_suspend, CW1200_BH_RESUME);
148a910e4a9SSolomon Peachy 	wake_up(&priv->bh_wq);
149a910e4a9SSolomon Peachy 	return wait_event_timeout(priv->bh_evt_wq, priv->bh_error ||
150a910e4a9SSolomon Peachy 		(CW1200_BH_RESUMED == atomic_read(&priv->bh_suspend)),
151a910e4a9SSolomon Peachy 		1 * HZ) ? 0 : -ETIMEDOUT;
152a910e4a9SSolomon Peachy }
153a910e4a9SSolomon Peachy 
154a910e4a9SSolomon Peachy static inline void wsm_alloc_tx_buffer(struct cw1200_common *priv)
155a910e4a9SSolomon Peachy {
156a910e4a9SSolomon Peachy 	++priv->hw_bufs_used;
157a910e4a9SSolomon Peachy }
158a910e4a9SSolomon Peachy 
159a910e4a9SSolomon Peachy int wsm_release_tx_buffer(struct cw1200_common *priv, int count)
160a910e4a9SSolomon Peachy {
161a910e4a9SSolomon Peachy 	int ret = 0;
162a910e4a9SSolomon Peachy 	int hw_bufs_used = priv->hw_bufs_used;
163a910e4a9SSolomon Peachy 
164a910e4a9SSolomon Peachy 	priv->hw_bufs_used -= count;
165a910e4a9SSolomon Peachy 	if (WARN_ON(priv->hw_bufs_used < 0))
166a910e4a9SSolomon Peachy 		ret = -1;
167a910e4a9SSolomon Peachy 	else if (hw_bufs_used >= priv->wsm_caps.input_buffers)
168a910e4a9SSolomon Peachy 		ret = 1;
169a910e4a9SSolomon Peachy 	if (!priv->hw_bufs_used)
170a910e4a9SSolomon Peachy 		wake_up(&priv->bh_evt_wq);
171a910e4a9SSolomon Peachy 	return ret;
172a910e4a9SSolomon Peachy }
173a910e4a9SSolomon Peachy 
174a910e4a9SSolomon Peachy static int cw1200_bh_read_ctrl_reg(struct cw1200_common *priv,
175a910e4a9SSolomon Peachy 					  u16 *ctrl_reg)
176a910e4a9SSolomon Peachy {
177a910e4a9SSolomon Peachy 	int ret;
178a910e4a9SSolomon Peachy 
179a910e4a9SSolomon Peachy 	ret = cw1200_reg_read_16(priv,
180a910e4a9SSolomon Peachy 			ST90TDS_CONTROL_REG_ID, ctrl_reg);
181a910e4a9SSolomon Peachy 	if (ret) {
182a910e4a9SSolomon Peachy 		ret = cw1200_reg_read_16(priv,
183a910e4a9SSolomon Peachy 				ST90TDS_CONTROL_REG_ID, ctrl_reg);
184a910e4a9SSolomon Peachy 		if (ret)
185a910e4a9SSolomon Peachy 			pr_err("[BH] Failed to read control register.\n");
186a910e4a9SSolomon Peachy 	}
187a910e4a9SSolomon Peachy 
188a910e4a9SSolomon Peachy 	return ret;
189a910e4a9SSolomon Peachy }
190a910e4a9SSolomon Peachy 
191a910e4a9SSolomon Peachy static int cw1200_device_wakeup(struct cw1200_common *priv)
192a910e4a9SSolomon Peachy {
193a910e4a9SSolomon Peachy 	u16 ctrl_reg;
194a910e4a9SSolomon Peachy 	int ret;
195a910e4a9SSolomon Peachy 
196a910e4a9SSolomon Peachy 	pr_debug("[BH] Device wakeup.\n");
197a910e4a9SSolomon Peachy 
198a910e4a9SSolomon Peachy 	/* First, set the dpll register */
199a910e4a9SSolomon Peachy 	ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID,
200a910e4a9SSolomon Peachy 				  cw1200_dpll_from_clk(priv->hw_refclk));
201a910e4a9SSolomon Peachy 	if (WARN_ON(ret))
202a910e4a9SSolomon Peachy 		return ret;
203a910e4a9SSolomon Peachy 
204a910e4a9SSolomon Peachy 	/* To force the device to be always-on, the host sets WLAN_UP to 1 */
205a910e4a9SSolomon Peachy 	ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID,
206a910e4a9SSolomon Peachy 			ST90TDS_CONT_WUP_BIT);
207a910e4a9SSolomon Peachy 	if (WARN_ON(ret))
208a910e4a9SSolomon Peachy 		return ret;
209a910e4a9SSolomon Peachy 
210a910e4a9SSolomon Peachy 	ret = cw1200_bh_read_ctrl_reg(priv, &ctrl_reg);
211a910e4a9SSolomon Peachy 	if (WARN_ON(ret))
212a910e4a9SSolomon Peachy 		return ret;
213a910e4a9SSolomon Peachy 
214a910e4a9SSolomon Peachy 	/* If the device returns WLAN_RDY as 1, the device is active and will
2158b3e7be4SSolomon Peachy 	 * remain active.
2168b3e7be4SSolomon Peachy 	 */
217a910e4a9SSolomon Peachy 	if (ctrl_reg & ST90TDS_CONT_RDY_BIT) {
218a910e4a9SSolomon Peachy 		pr_debug("[BH] Device awake.\n");
219a910e4a9SSolomon Peachy 		return 1;
220a910e4a9SSolomon Peachy 	}
221a910e4a9SSolomon Peachy 
222a910e4a9SSolomon Peachy 	return 0;
223a910e4a9SSolomon Peachy }
224a910e4a9SSolomon Peachy 
225a910e4a9SSolomon Peachy /* Must be called from BH thraed. */
226a910e4a9SSolomon Peachy void cw1200_enable_powersave(struct cw1200_common *priv,
227a910e4a9SSolomon Peachy 			     bool enable)
228a910e4a9SSolomon Peachy {
229a910e4a9SSolomon Peachy 	pr_debug("[BH] Powerave is %s.\n",
230a910e4a9SSolomon Peachy 		 enable ? "enabled" : "disabled");
231a910e4a9SSolomon Peachy 	priv->powersave_enabled = enable;
232a910e4a9SSolomon Peachy }
233a910e4a9SSolomon Peachy 
234a910e4a9SSolomon Peachy static int cw1200_bh_rx_helper(struct cw1200_common *priv,
235a910e4a9SSolomon Peachy 			       uint16_t *ctrl_reg,
236a910e4a9SSolomon Peachy 			       int *tx)
237a910e4a9SSolomon Peachy {
238a910e4a9SSolomon Peachy 	size_t read_len = 0;
239a910e4a9SSolomon Peachy 	struct sk_buff *skb_rx = NULL;
240a910e4a9SSolomon Peachy 	struct wsm_hdr *wsm;
241a910e4a9SSolomon Peachy 	size_t wsm_len;
242a910e4a9SSolomon Peachy 	u16 wsm_id;
243a910e4a9SSolomon Peachy 	u8 wsm_seq;
244a910e4a9SSolomon Peachy 	int rx_resync = 1;
245a910e4a9SSolomon Peachy 
246a910e4a9SSolomon Peachy 	size_t alloc_len;
247a910e4a9SSolomon Peachy 	u8 *data;
248a910e4a9SSolomon Peachy 
249a910e4a9SSolomon Peachy 	read_len = (*ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) * 2;
250a910e4a9SSolomon Peachy 	if (!read_len)
251a910e4a9SSolomon Peachy 		return 0; /* No more work */
252a910e4a9SSolomon Peachy 
253a910e4a9SSolomon Peachy 	if (WARN_ON((read_len < sizeof(struct wsm_hdr)) ||
254a910e4a9SSolomon Peachy 		    (read_len > EFFECTIVE_BUF_SIZE))) {
255a910e4a9SSolomon Peachy 		pr_debug("Invalid read len: %zu (%04x)",
256a910e4a9SSolomon Peachy 			 read_len, *ctrl_reg);
257a910e4a9SSolomon Peachy 		goto err;
258a910e4a9SSolomon Peachy 	}
259a910e4a9SSolomon Peachy 
260a910e4a9SSolomon Peachy 	/* Add SIZE of PIGGYBACK reg (CONTROL Reg)
2618b3e7be4SSolomon Peachy 	 * to the NEXT Message length + 2 Bytes for SKB
2628b3e7be4SSolomon Peachy 	 */
263a910e4a9SSolomon Peachy 	read_len = read_len + 2;
264a910e4a9SSolomon Peachy 
265911373ccSSolomon Peachy 	alloc_len = priv->hwbus_ops->align_size(
266911373ccSSolomon Peachy 		priv->hwbus_priv, read_len);
267a910e4a9SSolomon Peachy 
268a910e4a9SSolomon Peachy 	/* Check if not exceeding CW1200 capabilities */
269a910e4a9SSolomon Peachy 	if (WARN_ON_ONCE(alloc_len > EFFECTIVE_BUF_SIZE)) {
270a910e4a9SSolomon Peachy 		pr_debug("Read aligned len: %zu\n",
271a910e4a9SSolomon Peachy 			 alloc_len);
272a910e4a9SSolomon Peachy 	}
273a910e4a9SSolomon Peachy 
274a910e4a9SSolomon Peachy 	skb_rx = dev_alloc_skb(alloc_len);
275a910e4a9SSolomon Peachy 	if (WARN_ON(!skb_rx))
276a910e4a9SSolomon Peachy 		goto err;
277a910e4a9SSolomon Peachy 
278a910e4a9SSolomon Peachy 	skb_trim(skb_rx, 0);
279a910e4a9SSolomon Peachy 	skb_put(skb_rx, read_len);
280a910e4a9SSolomon Peachy 	data = skb_rx->data;
281a910e4a9SSolomon Peachy 	if (WARN_ON(!data))
282a910e4a9SSolomon Peachy 		goto err;
283a910e4a9SSolomon Peachy 
284a910e4a9SSolomon Peachy 	if (WARN_ON(cw1200_data_read(priv, data, alloc_len))) {
285a910e4a9SSolomon Peachy 		pr_err("rx blew up, len %zu\n", alloc_len);
286a910e4a9SSolomon Peachy 		goto err;
287a910e4a9SSolomon Peachy 	}
288a910e4a9SSolomon Peachy 
289a910e4a9SSolomon Peachy 	/* Piggyback */
290a910e4a9SSolomon Peachy 	*ctrl_reg = __le16_to_cpu(
291a910e4a9SSolomon Peachy 		((__le16 *)data)[alloc_len / 2 - 1]);
292a910e4a9SSolomon Peachy 
293a910e4a9SSolomon Peachy 	wsm = (struct wsm_hdr *)data;
294a910e4a9SSolomon Peachy 	wsm_len = __le16_to_cpu(wsm->len);
295a910e4a9SSolomon Peachy 	if (WARN_ON(wsm_len > read_len))
296a910e4a9SSolomon Peachy 		goto err;
297a910e4a9SSolomon Peachy 
298a910e4a9SSolomon Peachy 	if (priv->wsm_enable_wsm_dumps)
299a910e4a9SSolomon Peachy 		print_hex_dump_bytes("<-- ",
300a910e4a9SSolomon Peachy 				     DUMP_PREFIX_NONE,
301a910e4a9SSolomon Peachy 				     data, wsm_len);
302a910e4a9SSolomon Peachy 
303a910e4a9SSolomon Peachy 	wsm_id  = __le16_to_cpu(wsm->id) & 0xFFF;
304a910e4a9SSolomon Peachy 	wsm_seq = (__le16_to_cpu(wsm->id) >> 13) & 7;
305a910e4a9SSolomon Peachy 
306a910e4a9SSolomon Peachy 	skb_trim(skb_rx, wsm_len);
307a910e4a9SSolomon Peachy 
308a910e4a9SSolomon Peachy 	if (wsm_id == 0x0800) {
309a910e4a9SSolomon Peachy 		wsm_handle_exception(priv,
310a910e4a9SSolomon Peachy 				     &data[sizeof(*wsm)],
311a910e4a9SSolomon Peachy 				     wsm_len - sizeof(*wsm));
312a910e4a9SSolomon Peachy 		goto err;
313a910e4a9SSolomon Peachy 	} else if (!rx_resync) {
314a910e4a9SSolomon Peachy 		if (WARN_ON(wsm_seq != priv->wsm_rx_seq))
315a910e4a9SSolomon Peachy 			goto err;
316a910e4a9SSolomon Peachy 	}
317a910e4a9SSolomon Peachy 	priv->wsm_rx_seq = (wsm_seq + 1) & 7;
318a910e4a9SSolomon Peachy 	rx_resync = 0;
319a910e4a9SSolomon Peachy 
320a910e4a9SSolomon Peachy 	if (wsm_id & 0x0400) {
321a910e4a9SSolomon Peachy 		int rc = wsm_release_tx_buffer(priv, 1);
322a910e4a9SSolomon Peachy 		if (WARN_ON(rc < 0))
323a910e4a9SSolomon Peachy 			return rc;
324a910e4a9SSolomon Peachy 		else if (rc > 0)
325a910e4a9SSolomon Peachy 			*tx = 1;
326a910e4a9SSolomon Peachy 	}
327a910e4a9SSolomon Peachy 
328a910e4a9SSolomon Peachy 	/* cw1200_wsm_rx takes care on SKB livetime */
329a910e4a9SSolomon Peachy 	if (WARN_ON(wsm_handle_rx(priv, wsm_id, wsm, &skb_rx)))
330a910e4a9SSolomon Peachy 		goto err;
331a910e4a9SSolomon Peachy 
332a910e4a9SSolomon Peachy 	if (skb_rx) {
333a910e4a9SSolomon Peachy 		dev_kfree_skb(skb_rx);
334a910e4a9SSolomon Peachy 		skb_rx = NULL;
335a910e4a9SSolomon Peachy 	}
336a910e4a9SSolomon Peachy 
337a910e4a9SSolomon Peachy 	return 0;
338a910e4a9SSolomon Peachy 
339a910e4a9SSolomon Peachy err:
340a910e4a9SSolomon Peachy 	if (skb_rx) {
341a910e4a9SSolomon Peachy 		dev_kfree_skb(skb_rx);
342a910e4a9SSolomon Peachy 		skb_rx = NULL;
343a910e4a9SSolomon Peachy 	}
344a910e4a9SSolomon Peachy 	return -1;
345a910e4a9SSolomon Peachy }
346a910e4a9SSolomon Peachy 
347a910e4a9SSolomon Peachy static int cw1200_bh_tx_helper(struct cw1200_common *priv,
348a910e4a9SSolomon Peachy 			       int *pending_tx,
349a910e4a9SSolomon Peachy 			       int *tx_burst)
350a910e4a9SSolomon Peachy {
351a910e4a9SSolomon Peachy 	size_t tx_len;
352a910e4a9SSolomon Peachy 	u8 *data;
353a910e4a9SSolomon Peachy 	int ret;
354a910e4a9SSolomon Peachy 	struct wsm_hdr *wsm;
355a910e4a9SSolomon Peachy 
356a910e4a9SSolomon Peachy 	if (priv->device_can_sleep) {
357a910e4a9SSolomon Peachy 		ret = cw1200_device_wakeup(priv);
358a910e4a9SSolomon Peachy 		if (WARN_ON(ret < 0)) { /* Error in wakeup */
359a910e4a9SSolomon Peachy 			*pending_tx = 1;
360a910e4a9SSolomon Peachy 			return 0;
361a910e4a9SSolomon Peachy 		} else if (ret) { /* Woke up */
362a910e4a9SSolomon Peachy 			priv->device_can_sleep = false;
363a910e4a9SSolomon Peachy 		} else { /* Did not awake */
364a910e4a9SSolomon Peachy 			*pending_tx = 1;
365a910e4a9SSolomon Peachy 			return 0;
366a910e4a9SSolomon Peachy 		}
367a910e4a9SSolomon Peachy 	}
368a910e4a9SSolomon Peachy 
369a910e4a9SSolomon Peachy 	wsm_alloc_tx_buffer(priv);
370a910e4a9SSolomon Peachy 	ret = wsm_get_tx(priv, &data, &tx_len, tx_burst);
371a910e4a9SSolomon Peachy 	if (ret <= 0) {
372a910e4a9SSolomon Peachy 		wsm_release_tx_buffer(priv, 1);
373a910e4a9SSolomon Peachy 		if (WARN_ON(ret < 0))
374a910e4a9SSolomon Peachy 			return ret; /* Error */
375a910e4a9SSolomon Peachy 		return 0; /* No work */
376a910e4a9SSolomon Peachy 	}
377a910e4a9SSolomon Peachy 
378a910e4a9SSolomon Peachy 	wsm = (struct wsm_hdr *)data;
379a910e4a9SSolomon Peachy 	BUG_ON(tx_len < sizeof(*wsm));
380a910e4a9SSolomon Peachy 	BUG_ON(__le16_to_cpu(wsm->len) != tx_len);
381a910e4a9SSolomon Peachy 
382*07f995caSYejune Deng 	atomic_inc(&priv->bh_tx);
383a910e4a9SSolomon Peachy 
384911373ccSSolomon Peachy 	tx_len = priv->hwbus_ops->align_size(
385911373ccSSolomon Peachy 		priv->hwbus_priv, tx_len);
386a910e4a9SSolomon Peachy 
387a910e4a9SSolomon Peachy 	/* Check if not exceeding CW1200 capabilities */
388a910e4a9SSolomon Peachy 	if (WARN_ON_ONCE(tx_len > EFFECTIVE_BUF_SIZE))
389a910e4a9SSolomon Peachy 		pr_debug("Write aligned len: %zu\n", tx_len);
390a910e4a9SSolomon Peachy 
391a910e4a9SSolomon Peachy 	wsm->id &= __cpu_to_le16(0xffff ^ WSM_TX_SEQ(WSM_TX_SEQ_MAX));
392a910e4a9SSolomon Peachy 	wsm->id |= __cpu_to_le16(WSM_TX_SEQ(priv->wsm_tx_seq));
393a910e4a9SSolomon Peachy 
394a910e4a9SSolomon Peachy 	if (WARN_ON(cw1200_data_write(priv, data, tx_len))) {
395a910e4a9SSolomon Peachy 		pr_err("tx blew up, len %zu\n", tx_len);
396a910e4a9SSolomon Peachy 		wsm_release_tx_buffer(priv, 1);
397a910e4a9SSolomon Peachy 		return -1; /* Error */
398a910e4a9SSolomon Peachy 	}
399a910e4a9SSolomon Peachy 
400a910e4a9SSolomon Peachy 	if (priv->wsm_enable_wsm_dumps)
401a910e4a9SSolomon Peachy 		print_hex_dump_bytes("--> ",
402a910e4a9SSolomon Peachy 				     DUMP_PREFIX_NONE,
403a910e4a9SSolomon Peachy 				     data,
404a910e4a9SSolomon Peachy 				     __le16_to_cpu(wsm->len));
405a910e4a9SSolomon Peachy 
406a910e4a9SSolomon Peachy 	wsm_txed(priv, data);
407a910e4a9SSolomon Peachy 	priv->wsm_tx_seq = (priv->wsm_tx_seq + 1) & WSM_TX_SEQ_MAX;
408a910e4a9SSolomon Peachy 
409a910e4a9SSolomon Peachy 	if (*tx_burst > 1) {
410a910e4a9SSolomon Peachy 		cw1200_debug_tx_burst(priv);
411a910e4a9SSolomon Peachy 		return 1; /* Work remains */
412a910e4a9SSolomon Peachy 	}
413a910e4a9SSolomon Peachy 
414a910e4a9SSolomon Peachy 	return 0;
415a910e4a9SSolomon Peachy }
416a910e4a9SSolomon Peachy 
417a910e4a9SSolomon Peachy static int cw1200_bh(void *arg)
418a910e4a9SSolomon Peachy {
419a910e4a9SSolomon Peachy 	struct cw1200_common *priv = arg;
420a910e4a9SSolomon Peachy 	int rx, tx, term, suspend;
421a910e4a9SSolomon Peachy 	u16 ctrl_reg = 0;
422a910e4a9SSolomon Peachy 	int tx_allowed;
423a910e4a9SSolomon Peachy 	int pending_tx = 0;
424a910e4a9SSolomon Peachy 	int tx_burst;
425a910e4a9SSolomon Peachy 	long status;
426a910e4a9SSolomon Peachy 	u32 dummy;
427a910e4a9SSolomon Peachy 	int ret;
428a910e4a9SSolomon Peachy 
429a910e4a9SSolomon Peachy 	for (;;) {
430a910e4a9SSolomon Peachy 		if (!priv->hw_bufs_used &&
431a910e4a9SSolomon Peachy 		    priv->powersave_enabled &&
432a910e4a9SSolomon Peachy 		    !priv->device_can_sleep &&
433a910e4a9SSolomon Peachy 		    !atomic_read(&priv->recent_scan)) {
434a910e4a9SSolomon Peachy 			status = 1 * HZ;
435a910e4a9SSolomon Peachy 			pr_debug("[BH] Device wakedown. No data.\n");
436a910e4a9SSolomon Peachy 			cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID, 0);
437a910e4a9SSolomon Peachy 			priv->device_can_sleep = true;
438a910e4a9SSolomon Peachy 		} else if (priv->hw_bufs_used) {
439a910e4a9SSolomon Peachy 			/* Interrupt loss detection */
440a910e4a9SSolomon Peachy 			status = 1 * HZ;
441a910e4a9SSolomon Peachy 		} else {
442a910e4a9SSolomon Peachy 			status = MAX_SCHEDULE_TIMEOUT;
443a910e4a9SSolomon Peachy 		}
444a910e4a9SSolomon Peachy 
445a910e4a9SSolomon Peachy 		/* Dummy Read for SDIO retry mechanism*/
446a910e4a9SSolomon Peachy 		if ((priv->hw_type != -1) &&
447a910e4a9SSolomon Peachy 		    (atomic_read(&priv->bh_rx) == 0) &&
448a910e4a9SSolomon Peachy 		    (atomic_read(&priv->bh_tx) == 0))
449a910e4a9SSolomon Peachy 			cw1200_reg_read(priv, ST90TDS_CONFIG_REG_ID,
450a910e4a9SSolomon Peachy 					&dummy, sizeof(dummy));
451a910e4a9SSolomon Peachy 
452a910e4a9SSolomon Peachy 		pr_debug("[BH] waiting ...\n");
453a910e4a9SSolomon Peachy 		status = wait_event_interruptible_timeout(priv->bh_wq, ({
454a910e4a9SSolomon Peachy 				rx = atomic_xchg(&priv->bh_rx, 0);
455a910e4a9SSolomon Peachy 				tx = atomic_xchg(&priv->bh_tx, 0);
456a910e4a9SSolomon Peachy 				term = atomic_xchg(&priv->bh_term, 0);
457a910e4a9SSolomon Peachy 				suspend = pending_tx ?
458a910e4a9SSolomon Peachy 					0 : atomic_read(&priv->bh_suspend);
459a910e4a9SSolomon Peachy 				(rx || tx || term || suspend || priv->bh_error);
460a910e4a9SSolomon Peachy 			}), status);
461a910e4a9SSolomon Peachy 
462076f0d20SSolomon Peachy 		pr_debug("[BH] - rx: %d, tx: %d, term: %d, bh_err: %d, suspend: %d, status: %ld\n",
463076f0d20SSolomon Peachy 			 rx, tx, term, suspend, priv->bh_error, status);
464a910e4a9SSolomon Peachy 
465a910e4a9SSolomon Peachy 		/* Did an error occur? */
466a910e4a9SSolomon Peachy 		if ((status < 0 && status != -ERESTARTSYS) ||
467a910e4a9SSolomon Peachy 		    term || priv->bh_error) {
468a910e4a9SSolomon Peachy 			break;
469a910e4a9SSolomon Peachy 		}
470a910e4a9SSolomon Peachy 		if (!status) {  /* wait_event timed out */
471a910e4a9SSolomon Peachy 			unsigned long timestamp = jiffies;
472a910e4a9SSolomon Peachy 			long timeout;
473a910e4a9SSolomon Peachy 			int pending = 0;
474a910e4a9SSolomon Peachy 			int i;
475a910e4a9SSolomon Peachy 
476a910e4a9SSolomon Peachy 			/* Check to see if we have any outstanding frames */
477a910e4a9SSolomon Peachy 			if (priv->hw_bufs_used && (!rx || !tx)) {
478a910e4a9SSolomon Peachy 				wiphy_warn(priv->hw->wiphy,
479a910e4a9SSolomon Peachy 					   "Missed interrupt? (%d frames outstanding)\n",
480a910e4a9SSolomon Peachy 					   priv->hw_bufs_used);
481a910e4a9SSolomon Peachy 				rx = 1;
482a910e4a9SSolomon Peachy 
483a910e4a9SSolomon Peachy 				/* Get a timestamp of "oldest" frame */
484a910e4a9SSolomon Peachy 				for (i = 0; i < 4; ++i)
485a910e4a9SSolomon Peachy 					pending += cw1200_queue_get_xmit_timestamp(
486a910e4a9SSolomon Peachy 						&priv->tx_queue[i],
487a910e4a9SSolomon Peachy 						&timestamp,
488a910e4a9SSolomon Peachy 						priv->pending_frame_id);
489a910e4a9SSolomon Peachy 
490a910e4a9SSolomon Peachy 				/* Check if frame transmission is timed out.
491a910e4a9SSolomon Peachy 				 * Add an extra second with respect to possible
492a910e4a9SSolomon Peachy 				 * interrupt loss.
493a910e4a9SSolomon Peachy 				 */
494a910e4a9SSolomon Peachy 				timeout = timestamp +
495a910e4a9SSolomon Peachy 					WSM_CMD_LAST_CHANCE_TIMEOUT +
496a910e4a9SSolomon Peachy 					1 * HZ  -
497a910e4a9SSolomon Peachy 					jiffies;
498a910e4a9SSolomon Peachy 
499a910e4a9SSolomon Peachy 				/* And terminate BH thread if the frame is "stuck" */
500a910e4a9SSolomon Peachy 				if (pending && timeout < 0) {
501a910e4a9SSolomon Peachy 					wiphy_warn(priv->hw->wiphy,
502a910e4a9SSolomon Peachy 						   "Timeout waiting for TX confirm (%d/%d pending, %ld vs %lu).\n",
503a910e4a9SSolomon Peachy 						   priv->hw_bufs_used, pending,
504a910e4a9SSolomon Peachy 						   timestamp, jiffies);
505a910e4a9SSolomon Peachy 					break;
506a910e4a9SSolomon Peachy 				}
507a910e4a9SSolomon Peachy 			} else if (!priv->device_can_sleep &&
508a910e4a9SSolomon Peachy 				   !atomic_read(&priv->recent_scan)) {
509a910e4a9SSolomon Peachy 				pr_debug("[BH] Device wakedown. Timeout.\n");
510a910e4a9SSolomon Peachy 				cw1200_reg_write_16(priv,
511a910e4a9SSolomon Peachy 						    ST90TDS_CONTROL_REG_ID, 0);
512a910e4a9SSolomon Peachy 				priv->device_can_sleep = true;
513a910e4a9SSolomon Peachy 			}
514a910e4a9SSolomon Peachy 			goto done;
515a910e4a9SSolomon Peachy 		} else if (suspend) {
516a910e4a9SSolomon Peachy 			pr_debug("[BH] Device suspend.\n");
517a910e4a9SSolomon Peachy 			if (priv->powersave_enabled) {
518a910e4a9SSolomon Peachy 				pr_debug("[BH] Device wakedown. Suspend.\n");
519a910e4a9SSolomon Peachy 				cw1200_reg_write_16(priv,
520a910e4a9SSolomon Peachy 						    ST90TDS_CONTROL_REG_ID, 0);
521a910e4a9SSolomon Peachy 				priv->device_can_sleep = true;
522a910e4a9SSolomon Peachy 			}
523a910e4a9SSolomon Peachy 
524a910e4a9SSolomon Peachy 			atomic_set(&priv->bh_suspend, CW1200_BH_SUSPENDED);
525a910e4a9SSolomon Peachy 			wake_up(&priv->bh_evt_wq);
526a910e4a9SSolomon Peachy 			status = wait_event_interruptible(priv->bh_wq,
527a910e4a9SSolomon Peachy 							  CW1200_BH_RESUME == atomic_read(&priv->bh_suspend));
528a910e4a9SSolomon Peachy 			if (status < 0) {
529a910e4a9SSolomon Peachy 				wiphy_err(priv->hw->wiphy,
530a910e4a9SSolomon Peachy 					  "Failed to wait for resume: %ld.\n",
531a910e4a9SSolomon Peachy 					  status);
532a910e4a9SSolomon Peachy 				break;
533a910e4a9SSolomon Peachy 			}
534a910e4a9SSolomon Peachy 			pr_debug("[BH] Device resume.\n");
535a910e4a9SSolomon Peachy 			atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED);
536a910e4a9SSolomon Peachy 			wake_up(&priv->bh_evt_wq);
537*07f995caSYejune Deng 			atomic_inc(&priv->bh_rx);
538a910e4a9SSolomon Peachy 			goto done;
539a910e4a9SSolomon Peachy 		}
540a910e4a9SSolomon Peachy 
541a910e4a9SSolomon Peachy 	rx:
542a910e4a9SSolomon Peachy 		tx += pending_tx;
543a910e4a9SSolomon Peachy 		pending_tx = 0;
544a910e4a9SSolomon Peachy 
545a910e4a9SSolomon Peachy 		if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg))
546a910e4a9SSolomon Peachy 			break;
547a910e4a9SSolomon Peachy 
548a910e4a9SSolomon Peachy 		/* Don't bother trying to rx unless we have data to read */
549a910e4a9SSolomon Peachy 		if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) {
550a910e4a9SSolomon Peachy 			ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx);
551a910e4a9SSolomon Peachy 			if (ret < 0)
552a910e4a9SSolomon Peachy 				break;
553a910e4a9SSolomon Peachy 			/* Double up here if there's more data.. */
554a910e4a9SSolomon Peachy 			if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) {
555a910e4a9SSolomon Peachy 				ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx);
556a910e4a9SSolomon Peachy 				if (ret < 0)
557a910e4a9SSolomon Peachy 					break;
558a910e4a9SSolomon Peachy 			}
559a910e4a9SSolomon Peachy 		}
560a910e4a9SSolomon Peachy 
561a910e4a9SSolomon Peachy 	tx:
562a910e4a9SSolomon Peachy 		if (tx) {
563a910e4a9SSolomon Peachy 			tx = 0;
564a910e4a9SSolomon Peachy 
565a910e4a9SSolomon Peachy 			BUG_ON(priv->hw_bufs_used > priv->wsm_caps.input_buffers);
566a910e4a9SSolomon Peachy 			tx_burst = priv->wsm_caps.input_buffers - priv->hw_bufs_used;
567a910e4a9SSolomon Peachy 			tx_allowed = tx_burst > 0;
568a910e4a9SSolomon Peachy 
569a910e4a9SSolomon Peachy 			if (!tx_allowed) {
570a910e4a9SSolomon Peachy 				/* Buffers full.  Ensure we process tx
571a910e4a9SSolomon Peachy 				 * after we handle rx..
572a910e4a9SSolomon Peachy 				 */
573a910e4a9SSolomon Peachy 				pending_tx = tx;
574a910e4a9SSolomon Peachy 				goto done_rx;
575a910e4a9SSolomon Peachy 			}
576a910e4a9SSolomon Peachy 			ret = cw1200_bh_tx_helper(priv, &pending_tx, &tx_burst);
577a910e4a9SSolomon Peachy 			if (ret < 0)
578a910e4a9SSolomon Peachy 				break;
579a910e4a9SSolomon Peachy 			if (ret > 0) /* More to transmit */
580a910e4a9SSolomon Peachy 				tx = ret;
581a910e4a9SSolomon Peachy 
582a910e4a9SSolomon Peachy 			/* Re-read ctrl reg */
583a910e4a9SSolomon Peachy 			if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg))
584a910e4a9SSolomon Peachy 				break;
585a910e4a9SSolomon Peachy 		}
586a910e4a9SSolomon Peachy 
587a910e4a9SSolomon Peachy 	done_rx:
588a910e4a9SSolomon Peachy 		if (priv->bh_error)
589a910e4a9SSolomon Peachy 			break;
590a910e4a9SSolomon Peachy 		if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK)
591a910e4a9SSolomon Peachy 			goto rx;
592a910e4a9SSolomon Peachy 		if (tx)
593a910e4a9SSolomon Peachy 			goto tx;
594a910e4a9SSolomon Peachy 
595a910e4a9SSolomon Peachy 	done:
596a910e4a9SSolomon Peachy 		/* Re-enable device interrupts */
597911373ccSSolomon Peachy 		priv->hwbus_ops->lock(priv->hwbus_priv);
598a910e4a9SSolomon Peachy 		__cw1200_irq_enable(priv, 1);
599911373ccSSolomon Peachy 		priv->hwbus_ops->unlock(priv->hwbus_priv);
600a910e4a9SSolomon Peachy 	}
601a910e4a9SSolomon Peachy 
602a910e4a9SSolomon Peachy 	/* Explicitly disable device interrupts */
603911373ccSSolomon Peachy 	priv->hwbus_ops->lock(priv->hwbus_priv);
604a910e4a9SSolomon Peachy 	__cw1200_irq_enable(priv, 0);
605911373ccSSolomon Peachy 	priv->hwbus_ops->unlock(priv->hwbus_priv);
606a910e4a9SSolomon Peachy 
607a910e4a9SSolomon Peachy 	if (!term) {
608a910e4a9SSolomon Peachy 		pr_err("[BH] Fatal error, exiting.\n");
609a910e4a9SSolomon Peachy 		priv->bh_error = 1;
610a910e4a9SSolomon Peachy 		/* TODO: schedule_work(recovery) */
611a910e4a9SSolomon Peachy 	}
612a910e4a9SSolomon Peachy 	return 0;
613a910e4a9SSolomon Peachy }
614