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 ×tamp, 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