Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ath10k: add support for hardware rfkill

When hardware rfkill is enabled in the firmware it will report the
capability via using WMI_TLV_SYS_CAP_INFO_RFKILL bit in the WMI_SERVICE_READY
event to the host. ath10k will check the capability, and if it is enabled then
ath10k will set the GPIO information to firmware using WMI_PDEV_SET_PARAM. When
the firmware detects hardware rfkill is enabled by the user, it will report it
via WMI_RFKILL_STATE_CHANGE_EVENTID. Once ath10k receives the event it will
send wmi command WMI_PDEV_SET_PARAM to the firmware to enable/disable the radio
and also notifies cfg80211.

We can't power off the device when rfkill is enabled, as otherwise the
firmware would not be able to detect GPIO changes and report them to the
host. So when rfkill is enabled, we need to keep the firmware running.

Tested with QCA6174 PCI with firmware
WLAN.RM.4.4.1-00109-QCARMSWPZ-1.

Signed-off-by: Alan Liu <alanliu@codeaurora.org>
Signed-off-by: Wen Gong <wgong@codeaurora.org>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>

authored by

Wen Gong and committed by
Kalle Valo
1382993f f8914a14

+178 -1
+5
drivers/net/wireless/ath/ath10k/core.h
··· 973 973 u32 low_5ghz_chan; 974 974 u32 high_5ghz_chan; 975 975 bool ani_enabled; 976 + u32 sys_cap_info; 977 + 978 + /* protected by data_lock */ 979 + bool hw_rfkill_on; 980 + 976 981 /* protected by conf_mutex */ 977 982 u8 ps_state_enable; 978 983
+3
drivers/net/wireless/ath/ath10k/hw.c
··· 155 155 .num_target_ce_config_wlan = 7, 156 156 .ce_desc_meta_data_mask = 0xFFFC, 157 157 .ce_desc_meta_data_lsb = 2, 158 + .rfkill_pin = 16, 159 + .rfkill_cfg = 0, 160 + .rfkill_on_level = 1, 158 161 }; 159 162 160 163 const struct ath10k_hw_values qca99x0_values = {
+3
drivers/net/wireless/ath/ath10k/hw.h
··· 379 379 u8 num_target_ce_config_wlan; 380 380 u16 ce_desc_meta_data_mask; 381 381 u8 ce_desc_meta_data_lsb; 382 + u32 rfkill_pin; 383 + u32 rfkill_cfg; 384 + bool rfkill_on_level; 382 385 }; 383 386 384 387 extern const struct ath10k_hw_values qca988x_values;
+78 -1
drivers/net/wireless/ath/ath10k/mac.c
··· 12 12 #include <linux/etherdevice.h> 13 13 #include <linux/acpi.h> 14 14 #include <linux/of.h> 15 + #include <linux/bitfield.h> 15 16 16 17 #include "hif.h" 17 18 #include "core.h" ··· 4755 4754 return 0; 4756 4755 } 4757 4756 4757 + static int ath10k_mac_rfkill_config(struct ath10k *ar) 4758 + { 4759 + u32 param; 4760 + int ret; 4761 + 4762 + if (ar->hw_values->rfkill_pin == 0) { 4763 + ath10k_warn(ar, "ath10k does not support hardware rfkill with this device\n"); 4764 + return -EOPNOTSUPP; 4765 + } 4766 + 4767 + ath10k_dbg(ar, ATH10K_DBG_MAC, 4768 + "mac rfkill_pin %d rfkill_cfg %d rfkill_on_level %d", 4769 + ar->hw_values->rfkill_pin, ar->hw_values->rfkill_cfg, 4770 + ar->hw_values->rfkill_on_level); 4771 + 4772 + param = FIELD_PREP(WMI_TLV_RFKILL_CFG_RADIO_LEVEL, 4773 + ar->hw_values->rfkill_on_level) | 4774 + FIELD_PREP(WMI_TLV_RFKILL_CFG_GPIO_PIN_NUM, 4775 + ar->hw_values->rfkill_pin) | 4776 + FIELD_PREP(WMI_TLV_RFKILL_CFG_PIN_AS_GPIO, 4777 + ar->hw_values->rfkill_cfg); 4778 + 4779 + ret = ath10k_wmi_pdev_set_param(ar, 4780 + ar->wmi.pdev_param->rfkill_config, 4781 + param); 4782 + if (ret) { 4783 + ath10k_warn(ar, 4784 + "failed to set rfkill config 0x%x: %d\n", 4785 + param, ret); 4786 + return ret; 4787 + } 4788 + return 0; 4789 + } 4790 + 4791 + int ath10k_mac_rfkill_enable_radio(struct ath10k *ar, bool enable) 4792 + { 4793 + enum wmi_tlv_rfkill_enable_radio param; 4794 + int ret; 4795 + 4796 + if (enable) 4797 + param = WMI_TLV_RFKILL_ENABLE_RADIO_ON; 4798 + else 4799 + param = WMI_TLV_RFKILL_ENABLE_RADIO_OFF; 4800 + 4801 + ath10k_dbg(ar, ATH10K_DBG_MAC, "mac rfkill enable %d", param); 4802 + 4803 + ret = ath10k_wmi_pdev_set_param(ar, ar->wmi.pdev_param->rfkill_enable, 4804 + param); 4805 + if (ret) { 4806 + ath10k_warn(ar, "failed to set rfkill enable param %d: %d\n", 4807 + param, ret); 4808 + return ret; 4809 + } 4810 + 4811 + return 0; 4812 + } 4813 + 4758 4814 static int ath10k_start(struct ieee80211_hw *hw) 4759 4815 { 4760 4816 struct ath10k *ar = hw->priv; ··· 4846 4788 goto err; 4847 4789 } 4848 4790 4791 + spin_lock_bh(&ar->data_lock); 4792 + 4793 + if (ar->hw_rfkill_on) { 4794 + ar->hw_rfkill_on = false; 4795 + spin_unlock_bh(&ar->data_lock); 4796 + goto err; 4797 + } 4798 + 4799 + spin_unlock_bh(&ar->data_lock); 4800 + 4849 4801 ret = ath10k_hif_power_up(ar, ATH10K_FIRMWARE_MODE_NORMAL); 4850 4802 if (ret) { 4851 4803 ath10k_err(ar, "Could not init hif: %d\n", ret); ··· 4867 4799 if (ret) { 4868 4800 ath10k_err(ar, "Could not init core: %d\n", ret); 4869 4801 goto err_power_down; 4802 + } 4803 + 4804 + if (ar->sys_cap_info & WMI_TLV_SYS_CAP_INFO_RFKILL) { 4805 + ret = ath10k_mac_rfkill_config(ar); 4806 + if (ret && ret != -EOPNOTSUPP) { 4807 + ath10k_warn(ar, "failed to configure rfkill: %d", ret); 4808 + goto err_core_stop; 4809 + } 4870 4810 } 4871 4811 4872 4812 param = ar->wmi.pdev_param->pmf_qos; ··· 5036 4960 5037 4961 mutex_lock(&ar->conf_mutex); 5038 4962 if (ar->state != ATH10K_STATE_OFF) { 5039 - ath10k_halt(ar); 4963 + if (!ar->hw_rfkill_on) 4964 + ath10k_halt(ar); 5040 4965 ar->state = ATH10K_STATE_OFF; 5041 4966 } 5042 4967 mutex_unlock(&ar->conf_mutex);
+1
drivers/net/wireless/ath/ath10k/mac.h
··· 72 72 u8 tid); 73 73 int ath10k_mac_ext_resource_config(struct ath10k *ar, u32 val); 74 74 void ath10k_mac_wait_tx_complete(struct ath10k *ar); 75 + int ath10k_mac_rfkill_enable_radio(struct ath10k *ar, bool enable); 75 76 76 77 static inline void ath10k_tx_h_seq_no(struct ieee80211_vif *vif, 77 78 struct sk_buff *skb)
+49
drivers/net/wireless/ath/ath10k/wmi-tlv.c
··· 409 409 return 0; 410 410 } 411 411 412 + static void ath10k_wmi_tlv_event_rfkill_state_change(struct ath10k *ar, 413 + struct sk_buff *skb) 414 + { 415 + const struct wmi_tlv_rfkill_state_change_ev *ev; 416 + const void **tb; 417 + bool radio; 418 + int ret; 419 + 420 + tb = ath10k_wmi_tlv_parse_alloc(ar, skb->data, skb->len, GFP_ATOMIC); 421 + if (IS_ERR(tb)) { 422 + ret = PTR_ERR(tb); 423 + ath10k_warn(ar, 424 + "failed to parse rfkill state change event: %d\n", 425 + ret); 426 + return; 427 + } 428 + 429 + ev = tb[WMI_TLV_TAG_STRUCT_RFKILL_EVENT]; 430 + if (!ev) { 431 + kfree(tb); 432 + return; 433 + } 434 + 435 + ath10k_dbg(ar, ATH10K_DBG_MAC, 436 + "wmi tlv rfkill state change gpio %d type %d radio_state %d\n", 437 + __le32_to_cpu(ev->gpio_pin_num), 438 + __le32_to_cpu(ev->int_type), 439 + __le32_to_cpu(ev->radio_state)); 440 + 441 + radio = (__le32_to_cpu(ev->radio_state) == WMI_TLV_RFKILL_RADIO_STATE_ON); 442 + 443 + spin_lock_bh(&ar->data_lock); 444 + 445 + if (!radio) 446 + ar->hw_rfkill_on = true; 447 + 448 + spin_unlock_bh(&ar->data_lock); 449 + 450 + /* notify cfg80211 radio state change */ 451 + ath10k_mac_rfkill_enable_radio(ar, radio); 452 + wiphy_rfkill_set_hw_state(ar->hw->wiphy, !radio); 453 + } 454 + 412 455 static int ath10k_wmi_tlv_event_temperature(struct ath10k *ar, 413 456 struct sk_buff *skb) 414 457 { ··· 671 628 break; 672 629 case WMI_TLV_TX_PAUSE_EVENTID: 673 630 ath10k_wmi_tlv_event_tx_pause(ar, skb); 631 + break; 632 + case WMI_TLV_RFKILL_STATE_CHANGE_EVENTID: 633 + ath10k_wmi_tlv_event_rfkill_state_change(ar, skb); 674 634 break; 675 635 case WMI_TLV_PDEV_TEMPERATURE_EVENTID: 676 636 ath10k_wmi_tlv_event_temperature(ar, skb); ··· 1261 1215 arg->num_mem_reqs = ev->num_mem_reqs; 1262 1216 arg->service_map = svc_bmap; 1263 1217 arg->service_map_len = ath10k_wmi_tlv_len(svc_bmap); 1218 + arg->sys_cap_info = ev->sys_cap_info; 1264 1219 1265 1220 ret = ath10k_wmi_tlv_iter(ar, mem_reqs, ath10k_wmi_tlv_len(mem_reqs), 1266 1221 ath10k_wmi_tlv_parse_mem_reqs, arg); ··· 4261 4214 .wapi_mbssid_offset = WMI_PDEV_PARAM_UNSUPPORTED, 4262 4215 .arp_srcaddr = WMI_PDEV_PARAM_UNSUPPORTED, 4263 4216 .arp_dstaddr = WMI_PDEV_PARAM_UNSUPPORTED, 4217 + .rfkill_config = WMI_TLV_PDEV_PARAM_HW_RFKILL_CONFIG, 4218 + .rfkill_enable = WMI_TLV_PDEV_PARAM_RFKILL_ENABLE, 4264 4219 }; 4265 4220 4266 4221 static struct wmi_peer_param_map wmi_tlv_peer_param_map = {
+27
drivers/net/wireless/ath/ath10k/wmi-tlv.h
··· 7 7 #ifndef _WMI_TLV_H 8 8 #define _WMI_TLV_H 9 9 10 + #include <linux/bitops.h> 11 + 10 12 #define WMI_TLV_CMD(grp_id) (((grp_id) << 12) | 0x1) 11 13 #define WMI_TLV_EV(grp_id) (((grp_id) << 12) | 0x1) 12 14 #define WMI_TLV_CMD_UNSUPPORTED 0 ··· 2277 2275 __le32 peer_reason; 2278 2276 __le32 vdev_id; 2279 2277 } __packed; 2278 + 2279 + enum wmi_tlv_sys_cap_info_flags { 2280 + WMI_TLV_SYS_CAP_INFO_RXTX_LED = BIT(0), 2281 + WMI_TLV_SYS_CAP_INFO_RFKILL = BIT(1), 2282 + }; 2283 + 2284 + #define WMI_TLV_RFKILL_CFG_GPIO_PIN_NUM GENMASK(5, 0) 2285 + #define WMI_TLV_RFKILL_CFG_RADIO_LEVEL BIT(6) 2286 + #define WMI_TLV_RFKILL_CFG_PIN_AS_GPIO GENMASK(10, 7) 2287 + 2288 + enum wmi_tlv_rfkill_enable_radio { 2289 + WMI_TLV_RFKILL_ENABLE_RADIO_ON = 0, 2290 + WMI_TLV_RFKILL_ENABLE_RADIO_OFF = 1, 2291 + }; 2292 + 2293 + enum wmi_tlv_rfkill_radio_state { 2294 + WMI_TLV_RFKILL_RADIO_STATE_OFF = 1, 2295 + WMI_TLV_RFKILL_RADIO_STATE_ON = 2, 2296 + }; 2297 + 2298 + struct wmi_tlv_rfkill_state_change_ev { 2299 + __le32 gpio_pin_num; 2300 + __le32 int_type; 2301 + __le32 radio_state; 2302 + }; 2280 2303 2281 2304 void ath10k_wmi_tlv_attach(struct ath10k *ar); 2282 2305
+9
drivers/net/wireless/ath/ath10k/wmi.c
··· 5412 5412 arg->service_map = ev->wmi_service_bitmap; 5413 5413 arg->service_map_len = sizeof(ev->wmi_service_bitmap); 5414 5414 5415 + /* Deliberately skipping ev->sys_cap_info as WMI and WMI-TLV have 5416 + * different values. We would need a translation to handle that, 5417 + * but as we don't currently need anything from sys_cap_info from 5418 + * WMI interface (only from WMI-TLV) safest it to skip it. 5419 + */ 5420 + 5415 5421 n = min_t(size_t, __le32_to_cpu(arg->num_mem_reqs), 5416 5422 ARRAY_SIZE(arg->mem_reqs)); 5417 5423 for (i = 0; i < n; i++) ··· 5471 5465 ar->high_2ghz_chan = __le32_to_cpu(arg.high_2ghz_chan); 5472 5466 ar->low_5ghz_chan = __le32_to_cpu(arg.low_5ghz_chan); 5473 5467 ar->high_5ghz_chan = __le32_to_cpu(arg.high_5ghz_chan); 5468 + ar->sys_cap_info = __le32_to_cpu(arg.sys_cap_info); 5474 5469 5475 5470 ath10k_dbg_dump(ar, ATH10K_DBG_WMI, NULL, "wmi svc: ", 5476 5471 arg.service_map, arg.service_map_len); 5472 + ath10k_dbg(ar, ATH10K_DBG_WMI, "wmi sys_cap_info 0x%x\n", 5473 + ar->sys_cap_info); 5477 5474 5478 5475 if (ar->num_rf_chains > ar->max_spatial_stream) { 5479 5476 ath10k_warn(ar, "hardware advertises support for more spatial streams than it should (%d > %d)\n",
+3
drivers/net/wireless/ath/ath10k/wmi.h
··· 3788 3788 u32 arp_srcaddr; 3789 3789 u32 arp_dstaddr; 3790 3790 u32 enable_btcoex; 3791 + u32 rfkill_config; 3792 + u32 rfkill_enable; 3791 3793 }; 3792 3794 3793 3795 #define WMI_PDEV_PARAM_UNSUPPORTED 0 ··· 6877 6875 __le32 high_2ghz_chan; 6878 6876 __le32 low_5ghz_chan; 6879 6877 __le32 high_5ghz_chan; 6878 + __le32 sys_cap_info; 6880 6879 const __le32 *service_map; 6881 6880 size_t service_map_len; 6882 6881 const struct wlan_host_mem_req *mem_reqs[WMI_MAX_MEM_REQS];