18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * mac80211 glue code 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 * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net> 108c2ecf20Sopenharmony_ci * Copyright (c) 2007-2009, Christian Lamparter <chunkeey@web.de> 118c2ecf20Sopenharmony_ci * Copyright 2008, Johannes Berg <johannes@sipsolutions.net> 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * Based on: 148c2ecf20Sopenharmony_ci * - the islsm (softmac prism54) driver, which is: 158c2ecf20Sopenharmony_ci * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al. 168c2ecf20Sopenharmony_ci * - stlc45xx driver 178c2ecf20Sopenharmony_ci * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). 188c2ecf20Sopenharmony_ci */ 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <linux/module.h> 218c2ecf20Sopenharmony_ci#include <linux/firmware.h> 228c2ecf20Sopenharmony_ci#include <linux/etherdevice.h> 238c2ecf20Sopenharmony_ci#include <linux/vmalloc.h> 248c2ecf20Sopenharmony_ci#include <linux/random.h> 258c2ecf20Sopenharmony_ci#include <linux/sched.h> 268c2ecf20Sopenharmony_ci#include <net/mac80211.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#include "cw1200.h" 298c2ecf20Sopenharmony_ci#include "txrx.h" 308c2ecf20Sopenharmony_ci#include "hwbus.h" 318c2ecf20Sopenharmony_ci#include "fwio.h" 328c2ecf20Sopenharmony_ci#include "hwio.h" 338c2ecf20Sopenharmony_ci#include "bh.h" 348c2ecf20Sopenharmony_ci#include "sta.h" 358c2ecf20Sopenharmony_ci#include "scan.h" 368c2ecf20Sopenharmony_ci#include "debug.h" 378c2ecf20Sopenharmony_ci#include "pm.h" 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ciMODULE_AUTHOR("Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>"); 408c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Softmac ST-Ericsson CW1200 common code"); 418c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 428c2ecf20Sopenharmony_ciMODULE_ALIAS("cw1200_core"); 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci/* Accept MAC address of the form macaddr=0x00,0x80,0xE1,0x30,0x40,0x50 */ 458c2ecf20Sopenharmony_cistatic u8 cw1200_mac_template[ETH_ALEN] = {0x02, 0x80, 0xe1, 0x00, 0x00, 0x00}; 468c2ecf20Sopenharmony_cimodule_param_array_named(macaddr, cw1200_mac_template, byte, NULL, 0444); 478c2ecf20Sopenharmony_ciMODULE_PARM_DESC(macaddr, "Override platform_data MAC address"); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic char *cw1200_sdd_path; 508c2ecf20Sopenharmony_cimodule_param(cw1200_sdd_path, charp, 0644); 518c2ecf20Sopenharmony_ciMODULE_PARM_DESC(cw1200_sdd_path, "Override platform_data SDD file"); 528c2ecf20Sopenharmony_cistatic int cw1200_refclk; 538c2ecf20Sopenharmony_cimodule_param(cw1200_refclk, int, 0644); 548c2ecf20Sopenharmony_ciMODULE_PARM_DESC(cw1200_refclk, "Override platform_data reference clock"); 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ciint cw1200_power_mode = wsm_power_mode_quiescent; 578c2ecf20Sopenharmony_cimodule_param(cw1200_power_mode, int, 0644); 588c2ecf20Sopenharmony_ciMODULE_PARM_DESC(cw1200_power_mode, "WSM power mode. 0 == active, 1 == doze, 2 == quiescent (default)"); 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci#define RATETAB_ENT(_rate, _rateid, _flags) \ 618c2ecf20Sopenharmony_ci { \ 628c2ecf20Sopenharmony_ci .bitrate = (_rate), \ 638c2ecf20Sopenharmony_ci .hw_value = (_rateid), \ 648c2ecf20Sopenharmony_ci .flags = (_flags), \ 658c2ecf20Sopenharmony_ci } 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic struct ieee80211_rate cw1200_rates[] = { 688c2ecf20Sopenharmony_ci RATETAB_ENT(10, 0, 0), 698c2ecf20Sopenharmony_ci RATETAB_ENT(20, 1, 0), 708c2ecf20Sopenharmony_ci RATETAB_ENT(55, 2, 0), 718c2ecf20Sopenharmony_ci RATETAB_ENT(110, 3, 0), 728c2ecf20Sopenharmony_ci RATETAB_ENT(60, 6, 0), 738c2ecf20Sopenharmony_ci RATETAB_ENT(90, 7, 0), 748c2ecf20Sopenharmony_ci RATETAB_ENT(120, 8, 0), 758c2ecf20Sopenharmony_ci RATETAB_ENT(180, 9, 0), 768c2ecf20Sopenharmony_ci RATETAB_ENT(240, 10, 0), 778c2ecf20Sopenharmony_ci RATETAB_ENT(360, 11, 0), 788c2ecf20Sopenharmony_ci RATETAB_ENT(480, 12, 0), 798c2ecf20Sopenharmony_ci RATETAB_ENT(540, 13, 0), 808c2ecf20Sopenharmony_ci}; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic struct ieee80211_rate cw1200_mcs_rates[] = { 838c2ecf20Sopenharmony_ci RATETAB_ENT(65, 14, IEEE80211_TX_RC_MCS), 848c2ecf20Sopenharmony_ci RATETAB_ENT(130, 15, IEEE80211_TX_RC_MCS), 858c2ecf20Sopenharmony_ci RATETAB_ENT(195, 16, IEEE80211_TX_RC_MCS), 868c2ecf20Sopenharmony_ci RATETAB_ENT(260, 17, IEEE80211_TX_RC_MCS), 878c2ecf20Sopenharmony_ci RATETAB_ENT(390, 18, IEEE80211_TX_RC_MCS), 888c2ecf20Sopenharmony_ci RATETAB_ENT(520, 19, IEEE80211_TX_RC_MCS), 898c2ecf20Sopenharmony_ci RATETAB_ENT(585, 20, IEEE80211_TX_RC_MCS), 908c2ecf20Sopenharmony_ci RATETAB_ENT(650, 21, IEEE80211_TX_RC_MCS), 918c2ecf20Sopenharmony_ci}; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci#define cw1200_a_rates (cw1200_rates + 4) 948c2ecf20Sopenharmony_ci#define cw1200_a_rates_size (ARRAY_SIZE(cw1200_rates) - 4) 958c2ecf20Sopenharmony_ci#define cw1200_g_rates (cw1200_rates + 0) 968c2ecf20Sopenharmony_ci#define cw1200_g_rates_size (ARRAY_SIZE(cw1200_rates)) 978c2ecf20Sopenharmony_ci#define cw1200_n_rates (cw1200_mcs_rates) 988c2ecf20Sopenharmony_ci#define cw1200_n_rates_size (ARRAY_SIZE(cw1200_mcs_rates)) 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci#define CHAN2G(_channel, _freq, _flags) { \ 1028c2ecf20Sopenharmony_ci .band = NL80211_BAND_2GHZ, \ 1038c2ecf20Sopenharmony_ci .center_freq = (_freq), \ 1048c2ecf20Sopenharmony_ci .hw_value = (_channel), \ 1058c2ecf20Sopenharmony_ci .flags = (_flags), \ 1068c2ecf20Sopenharmony_ci .max_antenna_gain = 0, \ 1078c2ecf20Sopenharmony_ci .max_power = 30, \ 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci#define CHAN5G(_channel, _flags) { \ 1118c2ecf20Sopenharmony_ci .band = NL80211_BAND_5GHZ, \ 1128c2ecf20Sopenharmony_ci .center_freq = 5000 + (5 * (_channel)), \ 1138c2ecf20Sopenharmony_ci .hw_value = (_channel), \ 1148c2ecf20Sopenharmony_ci .flags = (_flags), \ 1158c2ecf20Sopenharmony_ci .max_antenna_gain = 0, \ 1168c2ecf20Sopenharmony_ci .max_power = 30, \ 1178c2ecf20Sopenharmony_ci} 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic struct ieee80211_channel cw1200_2ghz_chantable[] = { 1208c2ecf20Sopenharmony_ci CHAN2G(1, 2412, 0), 1218c2ecf20Sopenharmony_ci CHAN2G(2, 2417, 0), 1228c2ecf20Sopenharmony_ci CHAN2G(3, 2422, 0), 1238c2ecf20Sopenharmony_ci CHAN2G(4, 2427, 0), 1248c2ecf20Sopenharmony_ci CHAN2G(5, 2432, 0), 1258c2ecf20Sopenharmony_ci CHAN2G(6, 2437, 0), 1268c2ecf20Sopenharmony_ci CHAN2G(7, 2442, 0), 1278c2ecf20Sopenharmony_ci CHAN2G(8, 2447, 0), 1288c2ecf20Sopenharmony_ci CHAN2G(9, 2452, 0), 1298c2ecf20Sopenharmony_ci CHAN2G(10, 2457, 0), 1308c2ecf20Sopenharmony_ci CHAN2G(11, 2462, 0), 1318c2ecf20Sopenharmony_ci CHAN2G(12, 2467, 0), 1328c2ecf20Sopenharmony_ci CHAN2G(13, 2472, 0), 1338c2ecf20Sopenharmony_ci CHAN2G(14, 2484, 0), 1348c2ecf20Sopenharmony_ci}; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_cistatic struct ieee80211_channel cw1200_5ghz_chantable[] = { 1378c2ecf20Sopenharmony_ci CHAN5G(34, 0), CHAN5G(36, 0), 1388c2ecf20Sopenharmony_ci CHAN5G(38, 0), CHAN5G(40, 0), 1398c2ecf20Sopenharmony_ci CHAN5G(42, 0), CHAN5G(44, 0), 1408c2ecf20Sopenharmony_ci CHAN5G(46, 0), CHAN5G(48, 0), 1418c2ecf20Sopenharmony_ci CHAN5G(52, 0), CHAN5G(56, 0), 1428c2ecf20Sopenharmony_ci CHAN5G(60, 0), CHAN5G(64, 0), 1438c2ecf20Sopenharmony_ci CHAN5G(100, 0), CHAN5G(104, 0), 1448c2ecf20Sopenharmony_ci CHAN5G(108, 0), CHAN5G(112, 0), 1458c2ecf20Sopenharmony_ci CHAN5G(116, 0), CHAN5G(120, 0), 1468c2ecf20Sopenharmony_ci CHAN5G(124, 0), CHAN5G(128, 0), 1478c2ecf20Sopenharmony_ci CHAN5G(132, 0), CHAN5G(136, 0), 1488c2ecf20Sopenharmony_ci CHAN5G(140, 0), CHAN5G(149, 0), 1498c2ecf20Sopenharmony_ci CHAN5G(153, 0), CHAN5G(157, 0), 1508c2ecf20Sopenharmony_ci CHAN5G(161, 0), CHAN5G(165, 0), 1518c2ecf20Sopenharmony_ci CHAN5G(184, 0), CHAN5G(188, 0), 1528c2ecf20Sopenharmony_ci CHAN5G(192, 0), CHAN5G(196, 0), 1538c2ecf20Sopenharmony_ci CHAN5G(200, 0), CHAN5G(204, 0), 1548c2ecf20Sopenharmony_ci CHAN5G(208, 0), CHAN5G(212, 0), 1558c2ecf20Sopenharmony_ci CHAN5G(216, 0), 1568c2ecf20Sopenharmony_ci}; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_cistatic struct ieee80211_supported_band cw1200_band_2ghz = { 1598c2ecf20Sopenharmony_ci .channels = cw1200_2ghz_chantable, 1608c2ecf20Sopenharmony_ci .n_channels = ARRAY_SIZE(cw1200_2ghz_chantable), 1618c2ecf20Sopenharmony_ci .bitrates = cw1200_g_rates, 1628c2ecf20Sopenharmony_ci .n_bitrates = cw1200_g_rates_size, 1638c2ecf20Sopenharmony_ci .ht_cap = { 1648c2ecf20Sopenharmony_ci .cap = IEEE80211_HT_CAP_GRN_FLD | 1658c2ecf20Sopenharmony_ci (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) | 1668c2ecf20Sopenharmony_ci IEEE80211_HT_CAP_MAX_AMSDU, 1678c2ecf20Sopenharmony_ci .ht_supported = 1, 1688c2ecf20Sopenharmony_ci .ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K, 1698c2ecf20Sopenharmony_ci .ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE, 1708c2ecf20Sopenharmony_ci .mcs = { 1718c2ecf20Sopenharmony_ci .rx_mask[0] = 0xFF, 1728c2ecf20Sopenharmony_ci .rx_highest = __cpu_to_le16(0x41), 1738c2ecf20Sopenharmony_ci .tx_params = IEEE80211_HT_MCS_TX_DEFINED, 1748c2ecf20Sopenharmony_ci }, 1758c2ecf20Sopenharmony_ci }, 1768c2ecf20Sopenharmony_ci}; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cistatic struct ieee80211_supported_band cw1200_band_5ghz = { 1798c2ecf20Sopenharmony_ci .channels = cw1200_5ghz_chantable, 1808c2ecf20Sopenharmony_ci .n_channels = ARRAY_SIZE(cw1200_5ghz_chantable), 1818c2ecf20Sopenharmony_ci .bitrates = cw1200_a_rates, 1828c2ecf20Sopenharmony_ci .n_bitrates = cw1200_a_rates_size, 1838c2ecf20Sopenharmony_ci .ht_cap = { 1848c2ecf20Sopenharmony_ci .cap = IEEE80211_HT_CAP_GRN_FLD | 1858c2ecf20Sopenharmony_ci (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) | 1868c2ecf20Sopenharmony_ci IEEE80211_HT_CAP_MAX_AMSDU, 1878c2ecf20Sopenharmony_ci .ht_supported = 1, 1888c2ecf20Sopenharmony_ci .ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K, 1898c2ecf20Sopenharmony_ci .ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE, 1908c2ecf20Sopenharmony_ci .mcs = { 1918c2ecf20Sopenharmony_ci .rx_mask[0] = 0xFF, 1928c2ecf20Sopenharmony_ci .rx_highest = __cpu_to_le16(0x41), 1938c2ecf20Sopenharmony_ci .tx_params = IEEE80211_HT_MCS_TX_DEFINED, 1948c2ecf20Sopenharmony_ci }, 1958c2ecf20Sopenharmony_ci }, 1968c2ecf20Sopenharmony_ci}; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cistatic const unsigned long cw1200_ttl[] = { 1998c2ecf20Sopenharmony_ci 1 * HZ, /* VO */ 2008c2ecf20Sopenharmony_ci 2 * HZ, /* VI */ 2018c2ecf20Sopenharmony_ci 5 * HZ, /* BE */ 2028c2ecf20Sopenharmony_ci 10 * HZ /* BK */ 2038c2ecf20Sopenharmony_ci}; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic const struct ieee80211_ops cw1200_ops = { 2068c2ecf20Sopenharmony_ci .start = cw1200_start, 2078c2ecf20Sopenharmony_ci .stop = cw1200_stop, 2088c2ecf20Sopenharmony_ci .add_interface = cw1200_add_interface, 2098c2ecf20Sopenharmony_ci .remove_interface = cw1200_remove_interface, 2108c2ecf20Sopenharmony_ci .change_interface = cw1200_change_interface, 2118c2ecf20Sopenharmony_ci .tx = cw1200_tx, 2128c2ecf20Sopenharmony_ci .hw_scan = cw1200_hw_scan, 2138c2ecf20Sopenharmony_ci .set_tim = cw1200_set_tim, 2148c2ecf20Sopenharmony_ci .sta_notify = cw1200_sta_notify, 2158c2ecf20Sopenharmony_ci .sta_add = cw1200_sta_add, 2168c2ecf20Sopenharmony_ci .sta_remove = cw1200_sta_remove, 2178c2ecf20Sopenharmony_ci .set_key = cw1200_set_key, 2188c2ecf20Sopenharmony_ci .set_rts_threshold = cw1200_set_rts_threshold, 2198c2ecf20Sopenharmony_ci .config = cw1200_config, 2208c2ecf20Sopenharmony_ci .bss_info_changed = cw1200_bss_info_changed, 2218c2ecf20Sopenharmony_ci .prepare_multicast = cw1200_prepare_multicast, 2228c2ecf20Sopenharmony_ci .configure_filter = cw1200_configure_filter, 2238c2ecf20Sopenharmony_ci .conf_tx = cw1200_conf_tx, 2248c2ecf20Sopenharmony_ci .get_stats = cw1200_get_stats, 2258c2ecf20Sopenharmony_ci .ampdu_action = cw1200_ampdu_action, 2268c2ecf20Sopenharmony_ci .flush = cw1200_flush, 2278c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 2288c2ecf20Sopenharmony_ci .suspend = cw1200_wow_suspend, 2298c2ecf20Sopenharmony_ci .resume = cw1200_wow_resume, 2308c2ecf20Sopenharmony_ci#endif 2318c2ecf20Sopenharmony_ci /* Intentionally not offloaded: */ 2328c2ecf20Sopenharmony_ci /*.channel_switch = cw1200_channel_switch, */ 2338c2ecf20Sopenharmony_ci /*.remain_on_channel = cw1200_remain_on_channel, */ 2348c2ecf20Sopenharmony_ci /*.cancel_remain_on_channel = cw1200_cancel_remain_on_channel, */ 2358c2ecf20Sopenharmony_ci}; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_cistatic int cw1200_ba_rx_tids = -1; 2388c2ecf20Sopenharmony_cistatic int cw1200_ba_tx_tids = -1; 2398c2ecf20Sopenharmony_cimodule_param(cw1200_ba_rx_tids, int, 0644); 2408c2ecf20Sopenharmony_cimodule_param(cw1200_ba_tx_tids, int, 0644); 2418c2ecf20Sopenharmony_ciMODULE_PARM_DESC(cw1200_ba_rx_tids, "Block ACK RX TIDs"); 2428c2ecf20Sopenharmony_ciMODULE_PARM_DESC(cw1200_ba_tx_tids, "Block ACK TX TIDs"); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 2458c2ecf20Sopenharmony_cistatic const struct wiphy_wowlan_support cw1200_wowlan_support = { 2468c2ecf20Sopenharmony_ci /* Support only for limited wowlan functionalities */ 2478c2ecf20Sopenharmony_ci .flags = WIPHY_WOWLAN_ANY | WIPHY_WOWLAN_DISCONNECT, 2488c2ecf20Sopenharmony_ci}; 2498c2ecf20Sopenharmony_ci#endif 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_cistatic struct ieee80211_hw *cw1200_init_common(const u8 *macaddr, 2538c2ecf20Sopenharmony_ci const bool have_5ghz) 2548c2ecf20Sopenharmony_ci{ 2558c2ecf20Sopenharmony_ci int i, band; 2568c2ecf20Sopenharmony_ci struct ieee80211_hw *hw; 2578c2ecf20Sopenharmony_ci struct cw1200_common *priv; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci hw = ieee80211_alloc_hw(sizeof(struct cw1200_common), &cw1200_ops); 2608c2ecf20Sopenharmony_ci if (!hw) 2618c2ecf20Sopenharmony_ci return NULL; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci priv = hw->priv; 2648c2ecf20Sopenharmony_ci priv->hw = hw; 2658c2ecf20Sopenharmony_ci priv->hw_type = -1; 2668c2ecf20Sopenharmony_ci priv->mode = NL80211_IFTYPE_UNSPECIFIED; 2678c2ecf20Sopenharmony_ci priv->rates = cw1200_rates; /* TODO: fetch from FW */ 2688c2ecf20Sopenharmony_ci priv->mcs_rates = cw1200_n_rates; 2698c2ecf20Sopenharmony_ci if (cw1200_ba_rx_tids != -1) 2708c2ecf20Sopenharmony_ci priv->ba_rx_tid_mask = cw1200_ba_rx_tids; 2718c2ecf20Sopenharmony_ci else 2728c2ecf20Sopenharmony_ci priv->ba_rx_tid_mask = 0xFF; /* Enable RX BLKACK for all TIDs */ 2738c2ecf20Sopenharmony_ci if (cw1200_ba_tx_tids != -1) 2748c2ecf20Sopenharmony_ci priv->ba_tx_tid_mask = cw1200_ba_tx_tids; 2758c2ecf20Sopenharmony_ci else 2768c2ecf20Sopenharmony_ci priv->ba_tx_tid_mask = 0xff; /* Enable TX BLKACK for all TIDs */ 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci ieee80211_hw_set(hw, NEED_DTIM_BEFORE_ASSOC); 2798c2ecf20Sopenharmony_ci ieee80211_hw_set(hw, TX_AMPDU_SETUP_IN_HW); 2808c2ecf20Sopenharmony_ci ieee80211_hw_set(hw, AMPDU_AGGREGATION); 2818c2ecf20Sopenharmony_ci ieee80211_hw_set(hw, CONNECTION_MONITOR); 2828c2ecf20Sopenharmony_ci ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS); 2838c2ecf20Sopenharmony_ci ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS); 2848c2ecf20Sopenharmony_ci ieee80211_hw_set(hw, SIGNAL_DBM); 2858c2ecf20Sopenharmony_ci ieee80211_hw_set(hw, SUPPORTS_PS); 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | 2888c2ecf20Sopenharmony_ci BIT(NL80211_IFTYPE_ADHOC) | 2898c2ecf20Sopenharmony_ci BIT(NL80211_IFTYPE_AP) | 2908c2ecf20Sopenharmony_ci BIT(NL80211_IFTYPE_MESH_POINT) | 2918c2ecf20Sopenharmony_ci BIT(NL80211_IFTYPE_P2P_CLIENT) | 2928c2ecf20Sopenharmony_ci BIT(NL80211_IFTYPE_P2P_GO); 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 2958c2ecf20Sopenharmony_ci hw->wiphy->wowlan = &cw1200_wowlan_support; 2968c2ecf20Sopenharmony_ci#endif 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci hw->queues = 4; 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci priv->rts_threshold = -1; 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci hw->max_rates = 8; 3058c2ecf20Sopenharmony_ci hw->max_rate_tries = 15; 3068c2ecf20Sopenharmony_ci hw->extra_tx_headroom = WSM_TX_EXTRA_HEADROOM + 3078c2ecf20Sopenharmony_ci 8; /* TKIP IV */ 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci hw->sta_data_size = sizeof(struct cw1200_sta_priv); 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci hw->wiphy->bands[NL80211_BAND_2GHZ] = &cw1200_band_2ghz; 3128c2ecf20Sopenharmony_ci if (have_5ghz) 3138c2ecf20Sopenharmony_ci hw->wiphy->bands[NL80211_BAND_5GHZ] = &cw1200_band_5ghz; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci /* Channel params have to be cleared before registering wiphy again */ 3168c2ecf20Sopenharmony_ci for (band = 0; band < NUM_NL80211_BANDS; band++) { 3178c2ecf20Sopenharmony_ci struct ieee80211_supported_band *sband = hw->wiphy->bands[band]; 3188c2ecf20Sopenharmony_ci if (!sband) 3198c2ecf20Sopenharmony_ci continue; 3208c2ecf20Sopenharmony_ci for (i = 0; i < sband->n_channels; i++) { 3218c2ecf20Sopenharmony_ci sband->channels[i].flags = 0; 3228c2ecf20Sopenharmony_ci sband->channels[i].max_antenna_gain = 0; 3238c2ecf20Sopenharmony_ci sband->channels[i].max_power = 30; 3248c2ecf20Sopenharmony_ci } 3258c2ecf20Sopenharmony_ci } 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci hw->wiphy->max_scan_ssids = 2; 3288c2ecf20Sopenharmony_ci hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci if (macaddr) 3318c2ecf20Sopenharmony_ci SET_IEEE80211_PERM_ADDR(hw, (u8 *)macaddr); 3328c2ecf20Sopenharmony_ci else 3338c2ecf20Sopenharmony_ci SET_IEEE80211_PERM_ADDR(hw, cw1200_mac_template); 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci /* Fix up mac address if necessary */ 3368c2ecf20Sopenharmony_ci if (hw->wiphy->perm_addr[3] == 0 && 3378c2ecf20Sopenharmony_ci hw->wiphy->perm_addr[4] == 0 && 3388c2ecf20Sopenharmony_ci hw->wiphy->perm_addr[5] == 0) { 3398c2ecf20Sopenharmony_ci get_random_bytes(&hw->wiphy->perm_addr[3], 3); 3408c2ecf20Sopenharmony_ci } 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci mutex_init(&priv->wsm_cmd_mux); 3438c2ecf20Sopenharmony_ci mutex_init(&priv->conf_mutex); 3448c2ecf20Sopenharmony_ci priv->workqueue = create_singlethread_workqueue("cw1200_wq"); 3458c2ecf20Sopenharmony_ci if (!priv->workqueue) { 3468c2ecf20Sopenharmony_ci ieee80211_free_hw(hw); 3478c2ecf20Sopenharmony_ci return NULL; 3488c2ecf20Sopenharmony_ci } 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci sema_init(&priv->scan.lock, 1); 3518c2ecf20Sopenharmony_ci INIT_WORK(&priv->scan.work, cw1200_scan_work); 3528c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&priv->scan.probe_work, cw1200_probe_work); 3538c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&priv->scan.timeout, cw1200_scan_timeout); 3548c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&priv->clear_recent_scan_work, 3558c2ecf20Sopenharmony_ci cw1200_clear_recent_scan_work); 3568c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&priv->join_timeout, cw1200_join_timeout); 3578c2ecf20Sopenharmony_ci INIT_WORK(&priv->unjoin_work, cw1200_unjoin_work); 3588c2ecf20Sopenharmony_ci INIT_WORK(&priv->join_complete_work, cw1200_join_complete_work); 3598c2ecf20Sopenharmony_ci INIT_WORK(&priv->wep_key_work, cw1200_wep_key_work); 3608c2ecf20Sopenharmony_ci INIT_WORK(&priv->tx_policy_upload_work, tx_policy_upload_work); 3618c2ecf20Sopenharmony_ci spin_lock_init(&priv->event_queue_lock); 3628c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&priv->event_queue); 3638c2ecf20Sopenharmony_ci INIT_WORK(&priv->event_handler, cw1200_event_handler); 3648c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&priv->bss_loss_work, cw1200_bss_loss_work); 3658c2ecf20Sopenharmony_ci INIT_WORK(&priv->bss_params_work, cw1200_bss_params_work); 3668c2ecf20Sopenharmony_ci spin_lock_init(&priv->bss_loss_lock); 3678c2ecf20Sopenharmony_ci spin_lock_init(&priv->ps_state_lock); 3688c2ecf20Sopenharmony_ci INIT_WORK(&priv->set_cts_work, cw1200_set_cts_work); 3698c2ecf20Sopenharmony_ci INIT_WORK(&priv->set_tim_work, cw1200_set_tim_work); 3708c2ecf20Sopenharmony_ci INIT_WORK(&priv->multicast_start_work, cw1200_multicast_start_work); 3718c2ecf20Sopenharmony_ci INIT_WORK(&priv->multicast_stop_work, cw1200_multicast_stop_work); 3728c2ecf20Sopenharmony_ci INIT_WORK(&priv->link_id_work, cw1200_link_id_work); 3738c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&priv->link_id_gc_work, cw1200_link_id_gc_work); 3748c2ecf20Sopenharmony_ci INIT_WORK(&priv->linkid_reset_work, cw1200_link_id_reset); 3758c2ecf20Sopenharmony_ci INIT_WORK(&priv->update_filtering_work, cw1200_update_filtering_work); 3768c2ecf20Sopenharmony_ci INIT_WORK(&priv->set_beacon_wakeup_period_work, 3778c2ecf20Sopenharmony_ci cw1200_set_beacon_wakeup_period_work); 3788c2ecf20Sopenharmony_ci timer_setup(&priv->mcast_timeout, cw1200_mcast_timeout, 0); 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_ci if (cw1200_queue_stats_init(&priv->tx_queue_stats, 3818c2ecf20Sopenharmony_ci CW1200_LINK_ID_MAX, 3828c2ecf20Sopenharmony_ci cw1200_skb_dtor, 3838c2ecf20Sopenharmony_ci priv)) { 3848c2ecf20Sopenharmony_ci destroy_workqueue(priv->workqueue); 3858c2ecf20Sopenharmony_ci ieee80211_free_hw(hw); 3868c2ecf20Sopenharmony_ci return NULL; 3878c2ecf20Sopenharmony_ci } 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci for (i = 0; i < 4; ++i) { 3908c2ecf20Sopenharmony_ci if (cw1200_queue_init(&priv->tx_queue[i], 3918c2ecf20Sopenharmony_ci &priv->tx_queue_stats, i, 16, 3928c2ecf20Sopenharmony_ci cw1200_ttl[i])) { 3938c2ecf20Sopenharmony_ci for (; i > 0; i--) 3948c2ecf20Sopenharmony_ci cw1200_queue_deinit(&priv->tx_queue[i - 1]); 3958c2ecf20Sopenharmony_ci cw1200_queue_stats_deinit(&priv->tx_queue_stats); 3968c2ecf20Sopenharmony_ci destroy_workqueue(priv->workqueue); 3978c2ecf20Sopenharmony_ci ieee80211_free_hw(hw); 3988c2ecf20Sopenharmony_ci return NULL; 3998c2ecf20Sopenharmony_ci } 4008c2ecf20Sopenharmony_ci } 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci init_waitqueue_head(&priv->channel_switch_done); 4038c2ecf20Sopenharmony_ci init_waitqueue_head(&priv->wsm_cmd_wq); 4048c2ecf20Sopenharmony_ci init_waitqueue_head(&priv->wsm_startup_done); 4058c2ecf20Sopenharmony_ci init_waitqueue_head(&priv->ps_mode_switch_done); 4068c2ecf20Sopenharmony_ci wsm_buf_init(&priv->wsm_cmd_buf); 4078c2ecf20Sopenharmony_ci spin_lock_init(&priv->wsm_cmd.lock); 4088c2ecf20Sopenharmony_ci priv->wsm_cmd.done = 1; 4098c2ecf20Sopenharmony_ci tx_policy_init(priv); 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci return hw; 4128c2ecf20Sopenharmony_ci} 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_cistatic int cw1200_register_common(struct ieee80211_hw *dev) 4158c2ecf20Sopenharmony_ci{ 4168c2ecf20Sopenharmony_ci struct cw1200_common *priv = dev->priv; 4178c2ecf20Sopenharmony_ci int err; 4188c2ecf20Sopenharmony_ci 4198c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 4208c2ecf20Sopenharmony_ci err = cw1200_pm_init(&priv->pm_state, priv); 4218c2ecf20Sopenharmony_ci if (err) { 4228c2ecf20Sopenharmony_ci pr_err("Cannot init PM. (%d).\n", 4238c2ecf20Sopenharmony_ci err); 4248c2ecf20Sopenharmony_ci return err; 4258c2ecf20Sopenharmony_ci } 4268c2ecf20Sopenharmony_ci#endif 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci err = ieee80211_register_hw(dev); 4298c2ecf20Sopenharmony_ci if (err) { 4308c2ecf20Sopenharmony_ci pr_err("Cannot register device (%d).\n", 4318c2ecf20Sopenharmony_ci err); 4328c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 4338c2ecf20Sopenharmony_ci cw1200_pm_deinit(&priv->pm_state); 4348c2ecf20Sopenharmony_ci#endif 4358c2ecf20Sopenharmony_ci return err; 4368c2ecf20Sopenharmony_ci } 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_ci cw1200_debug_init(priv); 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci pr_info("Registered as '%s'\n", wiphy_name(dev->wiphy)); 4418c2ecf20Sopenharmony_ci return 0; 4428c2ecf20Sopenharmony_ci} 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_cistatic void cw1200_free_common(struct ieee80211_hw *dev) 4458c2ecf20Sopenharmony_ci{ 4468c2ecf20Sopenharmony_ci ieee80211_free_hw(dev); 4478c2ecf20Sopenharmony_ci} 4488c2ecf20Sopenharmony_ci 4498c2ecf20Sopenharmony_cistatic void cw1200_unregister_common(struct ieee80211_hw *dev) 4508c2ecf20Sopenharmony_ci{ 4518c2ecf20Sopenharmony_ci struct cw1200_common *priv = dev->priv; 4528c2ecf20Sopenharmony_ci int i; 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_ci ieee80211_unregister_hw(dev); 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci del_timer_sync(&priv->mcast_timeout); 4578c2ecf20Sopenharmony_ci cw1200_unregister_bh(priv); 4588c2ecf20Sopenharmony_ci 4598c2ecf20Sopenharmony_ci cw1200_debug_release(priv); 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci mutex_destroy(&priv->conf_mutex); 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci wsm_buf_deinit(&priv->wsm_cmd_buf); 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci destroy_workqueue(priv->workqueue); 4668c2ecf20Sopenharmony_ci priv->workqueue = NULL; 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_ci if (priv->sdd) { 4698c2ecf20Sopenharmony_ci release_firmware(priv->sdd); 4708c2ecf20Sopenharmony_ci priv->sdd = NULL; 4718c2ecf20Sopenharmony_ci } 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci for (i = 0; i < 4; ++i) 4748c2ecf20Sopenharmony_ci cw1200_queue_deinit(&priv->tx_queue[i]); 4758c2ecf20Sopenharmony_ci 4768c2ecf20Sopenharmony_ci cw1200_queue_stats_deinit(&priv->tx_queue_stats); 4778c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 4788c2ecf20Sopenharmony_ci cw1200_pm_deinit(&priv->pm_state); 4798c2ecf20Sopenharmony_ci#endif 4808c2ecf20Sopenharmony_ci} 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci/* Clock is in KHz */ 4838c2ecf20Sopenharmony_ciu32 cw1200_dpll_from_clk(u16 clk_khz) 4848c2ecf20Sopenharmony_ci{ 4858c2ecf20Sopenharmony_ci switch (clk_khz) { 4868c2ecf20Sopenharmony_ci case 0x32C8: /* 13000 KHz */ 4878c2ecf20Sopenharmony_ci return 0x1D89D241; 4888c2ecf20Sopenharmony_ci case 0x3E80: /* 16000 KHz */ 4898c2ecf20Sopenharmony_ci return 0x000001E1; 4908c2ecf20Sopenharmony_ci case 0x41A0: /* 16800 KHz */ 4918c2ecf20Sopenharmony_ci return 0x124931C1; 4928c2ecf20Sopenharmony_ci case 0x4B00: /* 19200 KHz */ 4938c2ecf20Sopenharmony_ci return 0x00000191; 4948c2ecf20Sopenharmony_ci case 0x5DC0: /* 24000 KHz */ 4958c2ecf20Sopenharmony_ci return 0x00000141; 4968c2ecf20Sopenharmony_ci case 0x6590: /* 26000 KHz */ 4978c2ecf20Sopenharmony_ci return 0x0EC4F121; 4988c2ecf20Sopenharmony_ci case 0x8340: /* 33600 KHz */ 4998c2ecf20Sopenharmony_ci return 0x092490E1; 5008c2ecf20Sopenharmony_ci case 0x9600: /* 38400 KHz */ 5018c2ecf20Sopenharmony_ci return 0x100010C1; 5028c2ecf20Sopenharmony_ci case 0x9C40: /* 40000 KHz */ 5038c2ecf20Sopenharmony_ci return 0x000000C1; 5048c2ecf20Sopenharmony_ci case 0xBB80: /* 48000 KHz */ 5058c2ecf20Sopenharmony_ci return 0x000000A1; 5068c2ecf20Sopenharmony_ci case 0xCB20: /* 52000 KHz */ 5078c2ecf20Sopenharmony_ci return 0x07627091; 5088c2ecf20Sopenharmony_ci default: 5098c2ecf20Sopenharmony_ci pr_err("Unknown Refclk freq (0x%04x), using 26000KHz\n", 5108c2ecf20Sopenharmony_ci clk_khz); 5118c2ecf20Sopenharmony_ci return 0x0EC4F121; 5128c2ecf20Sopenharmony_ci } 5138c2ecf20Sopenharmony_ci} 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ciint cw1200_core_probe(const struct hwbus_ops *hwbus_ops, 5168c2ecf20Sopenharmony_ci struct hwbus_priv *hwbus, 5178c2ecf20Sopenharmony_ci struct device *pdev, 5188c2ecf20Sopenharmony_ci struct cw1200_common **core, 5198c2ecf20Sopenharmony_ci int ref_clk, const u8 *macaddr, 5208c2ecf20Sopenharmony_ci const char *sdd_path, bool have_5ghz) 5218c2ecf20Sopenharmony_ci{ 5228c2ecf20Sopenharmony_ci int err = -EINVAL; 5238c2ecf20Sopenharmony_ci struct ieee80211_hw *dev; 5248c2ecf20Sopenharmony_ci struct cw1200_common *priv; 5258c2ecf20Sopenharmony_ci struct wsm_operational_mode mode = { 5268c2ecf20Sopenharmony_ci .power_mode = cw1200_power_mode, 5278c2ecf20Sopenharmony_ci .disable_more_flag_usage = true, 5288c2ecf20Sopenharmony_ci }; 5298c2ecf20Sopenharmony_ci 5308c2ecf20Sopenharmony_ci dev = cw1200_init_common(macaddr, have_5ghz); 5318c2ecf20Sopenharmony_ci if (!dev) 5328c2ecf20Sopenharmony_ci goto err; 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci priv = dev->priv; 5358c2ecf20Sopenharmony_ci priv->hw_refclk = ref_clk; 5368c2ecf20Sopenharmony_ci if (cw1200_refclk) 5378c2ecf20Sopenharmony_ci priv->hw_refclk = cw1200_refclk; 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_ci priv->sdd_path = (char *)sdd_path; 5408c2ecf20Sopenharmony_ci if (cw1200_sdd_path) 5418c2ecf20Sopenharmony_ci priv->sdd_path = cw1200_sdd_path; 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci priv->hwbus_ops = hwbus_ops; 5448c2ecf20Sopenharmony_ci priv->hwbus_priv = hwbus; 5458c2ecf20Sopenharmony_ci priv->pdev = pdev; 5468c2ecf20Sopenharmony_ci SET_IEEE80211_DEV(priv->hw, pdev); 5478c2ecf20Sopenharmony_ci 5488c2ecf20Sopenharmony_ci /* Pass struct cw1200_common back up */ 5498c2ecf20Sopenharmony_ci *core = priv; 5508c2ecf20Sopenharmony_ci 5518c2ecf20Sopenharmony_ci err = cw1200_register_bh(priv); 5528c2ecf20Sopenharmony_ci if (err) 5538c2ecf20Sopenharmony_ci goto err1; 5548c2ecf20Sopenharmony_ci 5558c2ecf20Sopenharmony_ci err = cw1200_load_firmware(priv); 5568c2ecf20Sopenharmony_ci if (err) 5578c2ecf20Sopenharmony_ci goto err2; 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci if (wait_event_interruptible_timeout(priv->wsm_startup_done, 5608c2ecf20Sopenharmony_ci priv->firmware_ready, 5618c2ecf20Sopenharmony_ci 3*HZ) <= 0) { 5628c2ecf20Sopenharmony_ci /* TODO: Need to find how to reset device 5638c2ecf20Sopenharmony_ci in QUEUE mode properly. 5648c2ecf20Sopenharmony_ci */ 5658c2ecf20Sopenharmony_ci pr_err("Timeout waiting on device startup\n"); 5668c2ecf20Sopenharmony_ci err = -ETIMEDOUT; 5678c2ecf20Sopenharmony_ci goto err2; 5688c2ecf20Sopenharmony_ci } 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci /* Set low-power mode. */ 5718c2ecf20Sopenharmony_ci wsm_set_operational_mode(priv, &mode); 5728c2ecf20Sopenharmony_ci 5738c2ecf20Sopenharmony_ci /* Enable multi-TX confirmation */ 5748c2ecf20Sopenharmony_ci wsm_use_multi_tx_conf(priv, true); 5758c2ecf20Sopenharmony_ci 5768c2ecf20Sopenharmony_ci err = cw1200_register_common(dev); 5778c2ecf20Sopenharmony_ci if (err) 5788c2ecf20Sopenharmony_ci goto err2; 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_ci return err; 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_cierr2: 5838c2ecf20Sopenharmony_ci cw1200_unregister_bh(priv); 5848c2ecf20Sopenharmony_cierr1: 5858c2ecf20Sopenharmony_ci cw1200_free_common(dev); 5868c2ecf20Sopenharmony_cierr: 5878c2ecf20Sopenharmony_ci *core = NULL; 5888c2ecf20Sopenharmony_ci return err; 5898c2ecf20Sopenharmony_ci} 5908c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(cw1200_core_probe); 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_civoid cw1200_core_release(struct cw1200_common *self) 5938c2ecf20Sopenharmony_ci{ 5948c2ecf20Sopenharmony_ci /* Disable device interrupts */ 5958c2ecf20Sopenharmony_ci self->hwbus_ops->lock(self->hwbus_priv); 5968c2ecf20Sopenharmony_ci __cw1200_irq_enable(self, 0); 5978c2ecf20Sopenharmony_ci self->hwbus_ops->unlock(self->hwbus_priv); 5988c2ecf20Sopenharmony_ci 5998c2ecf20Sopenharmony_ci /* And then clean up */ 6008c2ecf20Sopenharmony_ci cw1200_unregister_common(self->hw); 6018c2ecf20Sopenharmony_ci cw1200_free_common(self->hw); 6028c2ecf20Sopenharmony_ci return; 6038c2ecf20Sopenharmony_ci} 6048c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(cw1200_core_release); 605