]> git.proxmox.com Git - mirror_ubuntu-focal-kernel.git/commitdiff
ath10k: add support for hardware rfkill
authorWen Gong <wgong@codeaurora.org>
Fri, 3 Jan 2020 06:33:31 +0000 (14:33 +0800)
committerSeth Forshee <seth.forshee@canonical.com>
Tue, 7 Jan 2020 22:59:31 +0000 (16:59 -0600)
BugLink: https://bugs.launchpad.net/bugs/1858166
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>
(cherry picked from commit 1382993f882b6b96c99837fd8b705300a208de3a)
Signed-off-by: AceLan Kao <acelan.kao@canonical.com>
Acked-by: Marcelo Henrique Cerri <marcelo.cerri@canonical.com>
Acked-by: Khaled Elmously <khalid.elmously@canonical.com>
Signed-off-by: Seth Forshee <seth.forshee@canonical.com>
drivers/net/wireless/ath/ath10k/core.h
drivers/net/wireless/ath/ath10k/hw.c
drivers/net/wireless/ath/ath10k/hw.h
drivers/net/wireless/ath/ath10k/mac.c
drivers/net/wireless/ath/ath10k/mac.h
drivers/net/wireless/ath/ath10k/wmi-tlv.c
drivers/net/wireless/ath/ath10k/wmi-tlv.h
drivers/net/wireless/ath/ath10k/wmi.c
drivers/net/wireless/ath/ath10k/wmi.h

index 4d7db07db6ba23c87c8291c9c24da1b3a40d08c4..af7d9a8007ae612c35377cae5e8e04d7a0bb820a 100644 (file)
@@ -969,6 +969,11 @@ struct ath10k {
        u32 low_5ghz_chan;
        u32 high_5ghz_chan;
        bool ani_enabled;
+       u32 sys_cap_info;
+
+       /* protected by data_lock */
+       bool hw_rfkill_on;
+
        /* protected by conf_mutex */
        u8 ps_state_enable;
 
index c415e971735b508e682cd631ab330669a029a712..55849173e55dc09a76830f3e649f7ee294a8643c 100644 (file)
@@ -155,6 +155,9 @@ const struct ath10k_hw_values qca6174_values = {
        .num_target_ce_config_wlan      = 7,
        .ce_desc_meta_data_mask         = 0xFFFC,
        .ce_desc_meta_data_lsb          = 2,
+       .rfkill_pin                     = 16,
+       .rfkill_cfg                     = 0,
+       .rfkill_on_level                = 1,
 };
 
 const struct ath10k_hw_values qca99x0_values = {
index 2ae57c1de7b55bddcbb206f8ef6358479205da3b..35a362329a4f175e5957769d18fc4e682118b683 100644 (file)
@@ -379,6 +379,9 @@ struct ath10k_hw_values {
        u8 num_target_ce_config_wlan;
        u16 ce_desc_meta_data_mask;
        u8 ce_desc_meta_data_lsb;
+       u32 rfkill_pin;
+       u32 rfkill_cfg;
+       bool rfkill_on_level;
 };
 
 extern const struct ath10k_hw_values qca988x_values;
index 36d24ea126a29d1e2936ab2a6333530dd38211c4..0896601effefa6da629caeb27875640c48539cf5 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/etherdevice.h>
 #include <linux/acpi.h>
 #include <linux/of.h>
+#include <linux/bitfield.h>
 
 #include "hif.h"
 #include "core.h"
@@ -4756,6 +4757,63 @@ static int __ath10k_fetch_bb_timing_dt(struct ath10k *ar,
        return 0;
 }
 
+static int ath10k_mac_rfkill_config(struct ath10k *ar)
+{
+       u32 param;
+       int ret;
+
+       if (ar->hw_values->rfkill_pin == 0) {
+               ath10k_warn(ar, "ath10k does not support hardware rfkill with this device\n");
+               return -EOPNOTSUPP;
+       }
+
+       ath10k_dbg(ar, ATH10K_DBG_MAC,
+                  "mac rfkill_pin %d rfkill_cfg %d rfkill_on_level %d",
+                  ar->hw_values->rfkill_pin, ar->hw_values->rfkill_cfg,
+                  ar->hw_values->rfkill_on_level);
+
+       param = FIELD_PREP(WMI_TLV_RFKILL_CFG_RADIO_LEVEL,
+                          ar->hw_values->rfkill_on_level) |
+               FIELD_PREP(WMI_TLV_RFKILL_CFG_GPIO_PIN_NUM,
+                          ar->hw_values->rfkill_pin) |
+               FIELD_PREP(WMI_TLV_RFKILL_CFG_PIN_AS_GPIO,
+                          ar->hw_values->rfkill_cfg);
+
+       ret = ath10k_wmi_pdev_set_param(ar,
+                                       ar->wmi.pdev_param->rfkill_config,
+                                       param);
+       if (ret) {
+               ath10k_warn(ar,
+                           "failed to set rfkill config 0x%x: %d\n",
+                           param, ret);
+               return ret;
+       }
+       return 0;
+}
+
+int ath10k_mac_rfkill_enable_radio(struct ath10k *ar, bool enable)
+{
+       enum wmi_tlv_rfkill_enable_radio param;
+       int ret;
+
+       if (enable)
+               param = WMI_TLV_RFKILL_ENABLE_RADIO_ON;
+       else
+               param = WMI_TLV_RFKILL_ENABLE_RADIO_OFF;
+
+       ath10k_dbg(ar, ATH10K_DBG_MAC, "mac rfkill enable %d", param);
+
+       ret = ath10k_wmi_pdev_set_param(ar, ar->wmi.pdev_param->rfkill_enable,
+                                       param);
+       if (ret) {
+               ath10k_warn(ar, "failed to set rfkill enable param %d: %d\n",
+                           param, ret);
+               return ret;
+       }
+
+       return 0;
+}
+
 static int ath10k_start(struct ieee80211_hw *hw)
 {
        struct ath10k *ar = hw->priv;
@@ -4790,6 +4848,16 @@ static int ath10k_start(struct ieee80211_hw *hw)
                goto err;
        }
 
+       spin_lock_bh(&ar->data_lock);
+
+       if (ar->hw_rfkill_on) {
+               ar->hw_rfkill_on = false;
+               spin_unlock_bh(&ar->data_lock);
+               goto err;
+       }
+
+       spin_unlock_bh(&ar->data_lock);
+
        ret = ath10k_hif_power_up(ar, ATH10K_FIRMWARE_MODE_NORMAL);
        if (ret) {
                ath10k_err(ar, "Could not init hif: %d\n", ret);
@@ -4803,6 +4871,14 @@ static int ath10k_start(struct ieee80211_hw *hw)
                goto err_power_down;
        }
 
+       if (ar->sys_cap_info & WMI_TLV_SYS_CAP_INFO_RFKILL) {
+               ret = ath10k_mac_rfkill_config(ar);
+               if (ret && ret != -EOPNOTSUPP) {
+                       ath10k_warn(ar, "failed to configure rfkill: %d", ret);
+                       goto err_core_stop;
+               }
+       }
+
        param = ar->wmi.pdev_param->pmf_qos;
        ret = ath10k_wmi_pdev_set_param(ar, param, 1);
        if (ret) {
@@ -4962,7 +5038,8 @@ static void ath10k_stop(struct ieee80211_hw *hw)
 
        mutex_lock(&ar->conf_mutex);
        if (ar->state != ATH10K_STATE_OFF) {
-               ath10k_halt(ar);
+               if (!ar->hw_rfkill_on)
+                       ath10k_halt(ar);
                ar->state = ATH10K_STATE_OFF;
        }
        mutex_unlock(&ar->conf_mutex);
index 1fe84948b8685631420fd82ef29929fa71d06cef..98d83a26ea602d5d991c9627d54cbf5ecf1740b4 100644 (file)
@@ -72,6 +72,7 @@ struct ieee80211_txq *ath10k_mac_txq_lookup(struct ath10k *ar,
                                            u8 tid);
 int ath10k_mac_ext_resource_config(struct ath10k *ar, u32 val);
 void ath10k_mac_wait_tx_complete(struct ath10k *ar);
+int ath10k_mac_rfkill_enable_radio(struct ath10k *ar, bool enable);
 
 static inline void ath10k_tx_h_seq_no(struct ieee80211_vif *vif,
                                      struct sk_buff *skb)
index 4d5d10c0106453376d48b30d7b5f60ca7b156768..a737a0e6c50b4e7a8fa27b6f55ba09be3500ebd2 100644 (file)
@@ -409,6 +409,49 @@ static int ath10k_wmi_tlv_event_tx_pause(struct ath10k *ar,
        return 0;
 }
 
+static void ath10k_wmi_tlv_event_rfkill_state_change(struct ath10k *ar,
+                                                    struct sk_buff *skb)
+{
+       const struct wmi_tlv_rfkill_state_change_ev *ev;
+       const void **tb;
+       bool radio;
+       int ret;
+
+       tb = ath10k_wmi_tlv_parse_alloc(ar, skb->data, skb->len, GFP_ATOMIC);
+       if (IS_ERR(tb)) {
+               ret = PTR_ERR(tb);
+               ath10k_warn(ar,
+                           "failed to parse rfkill state change event: %d\n",
+                           ret);
+               return;
+       }
+
+       ev = tb[WMI_TLV_TAG_STRUCT_RFKILL_EVENT];
+       if (!ev) {
+               kfree(tb);
+               return;
+       }
+
+       ath10k_dbg(ar, ATH10K_DBG_MAC,
+                  "wmi tlv rfkill state change gpio %d type %d radio_state %d\n",
+                  __le32_to_cpu(ev->gpio_pin_num),
+                  __le32_to_cpu(ev->int_type),
+                  __le32_to_cpu(ev->radio_state));
+
+       radio = (__le32_to_cpu(ev->radio_state) == WMI_TLV_RFKILL_RADIO_STATE_ON);
+
+       spin_lock_bh(&ar->data_lock);
+
+       if (!radio)
+               ar->hw_rfkill_on = true;
+
+       spin_unlock_bh(&ar->data_lock);
+
+       /* notify cfg80211 radio state change */
+       ath10k_mac_rfkill_enable_radio(ar, radio);
+       wiphy_rfkill_set_hw_state(ar->hw->wiphy, !radio);
+}
+
 static int ath10k_wmi_tlv_event_temperature(struct ath10k *ar,
                                            struct sk_buff *skb)
 {
@@ -629,6 +672,9 @@ static void ath10k_wmi_tlv_op_rx(struct ath10k *ar, struct sk_buff *skb)
        case WMI_TLV_TX_PAUSE_EVENTID:
                ath10k_wmi_tlv_event_tx_pause(ar, skb);
                break;
+       case WMI_TLV_RFKILL_STATE_CHANGE_EVENTID:
+               ath10k_wmi_tlv_event_rfkill_state_change(ar, skb);
+               break;
        case WMI_TLV_PDEV_TEMPERATURE_EVENTID:
                ath10k_wmi_tlv_event_temperature(ar, skb);
                break;
@@ -1212,6 +1258,7 @@ static int ath10k_wmi_tlv_op_pull_svc_rdy_ev(struct ath10k *ar,
        arg->num_mem_reqs = ev->num_mem_reqs;
        arg->service_map = svc_bmap;
        arg->service_map_len = ath10k_wmi_tlv_len(svc_bmap);
+       arg->sys_cap_info = ev->sys_cap_info;
 
        ret = ath10k_wmi_tlv_iter(ar, mem_reqs, ath10k_wmi_tlv_len(mem_reqs),
                                  ath10k_wmi_tlv_parse_mem_reqs, arg);
@@ -4204,6 +4251,8 @@ static struct wmi_pdev_param_map wmi_tlv_pdev_param_map = {
        .wapi_mbssid_offset = WMI_PDEV_PARAM_UNSUPPORTED,
        .arp_srcaddr = WMI_PDEV_PARAM_UNSUPPORTED,
        .arp_dstaddr = WMI_PDEV_PARAM_UNSUPPORTED,
+       .rfkill_config = WMI_TLV_PDEV_PARAM_HW_RFKILL_CONFIG,
+       .rfkill_enable = WMI_TLV_PDEV_PARAM_RFKILL_ENABLE,
 };
 
 static struct wmi_vdev_param_map wmi_tlv_vdev_param_map = {
index 649b229a41e9f2fe3efe4d43610a5e1945caf594..2bfcfa4760e18eaf2abbb13fcc3cb07f05bf95c1 100644 (file)
@@ -7,6 +7,8 @@
 #ifndef _WMI_TLV_H
 #define _WMI_TLV_H
 
+#include <linux/bitops.h>
+
 #define WMI_TLV_CMD(grp_id) (((grp_id) << 12) | 0x1)
 #define WMI_TLV_EV(grp_id) (((grp_id) << 12) | 0x1)
 #define WMI_TLV_CMD_UNSUPPORTED 0
@@ -2235,6 +2237,31 @@ struct wmi_tlv_tdls_peer_event {
        __le32 vdev_id;
 } __packed;
 
+enum wmi_tlv_sys_cap_info_flags {
+       WMI_TLV_SYS_CAP_INFO_RXTX_LED   = BIT(0),
+       WMI_TLV_SYS_CAP_INFO_RFKILL     = BIT(1),
+};
+
+#define WMI_TLV_RFKILL_CFG_GPIO_PIN_NUM                GENMASK(5, 0)
+#define WMI_TLV_RFKILL_CFG_RADIO_LEVEL         BIT(6)
+#define WMI_TLV_RFKILL_CFG_PIN_AS_GPIO         GENMASK(10, 7)
+
+enum wmi_tlv_rfkill_enable_radio {
+       WMI_TLV_RFKILL_ENABLE_RADIO_ON  = 0,
+       WMI_TLV_RFKILL_ENABLE_RADIO_OFF = 1,
+};
+
+enum wmi_tlv_rfkill_radio_state {
+       WMI_TLV_RFKILL_RADIO_STATE_OFF  = 1,
+       WMI_TLV_RFKILL_RADIO_STATE_ON   = 2,
+};
+
+struct wmi_tlv_rfkill_state_change_ev {
+       __le32 gpio_pin_num;
+       __le32 int_type;
+       __le32 radio_state;
+};
+
 void ath10k_wmi_tlv_attach(struct ath10k *ar);
 
 enum wmi_nlo_auth_algorithm {
index 4f707c6394bbaa2f45fe14caf4768db4d593e0f4..721bbddc6fcdac7acac929665817390254c65d77 100644 (file)
@@ -5393,6 +5393,12 @@ ath10k_wmi_10x_op_pull_svc_rdy_ev(struct ath10k *ar, struct sk_buff *skb,
        arg->service_map = ev->wmi_service_bitmap;
        arg->service_map_len = sizeof(ev->wmi_service_bitmap);
 
+       /* Deliberately skipping ev->sys_cap_info as WMI and WMI-TLV have
+        * different values. We would need a translation to handle that,
+        * but as we don't currently need anything from sys_cap_info from
+        * WMI interface (only from WMI-TLV) safest it to skip it.
+        */
+
        n = min_t(size_t, __le32_to_cpu(arg->num_mem_reqs),
                  ARRAY_SIZE(arg->mem_reqs));
        for (i = 0; i < n; i++)
@@ -5443,9 +5449,12 @@ static void ath10k_wmi_event_service_ready_work(struct work_struct *work)
        ar->hw_eeprom_rd = __le32_to_cpu(arg.eeprom_rd);
        ar->low_5ghz_chan = __le32_to_cpu(arg.low_5ghz_chan);
        ar->high_5ghz_chan = __le32_to_cpu(arg.high_5ghz_chan);
+       ar->sys_cap_info = __le32_to_cpu(arg.sys_cap_info);
 
        ath10k_dbg_dump(ar, ATH10K_DBG_WMI, NULL, "wmi svc: ",
                        arg.service_map, arg.service_map_len);
+       ath10k_dbg(ar, ATH10K_DBG_WMI, "wmi sys_cap_info 0x%x\n",
+                  ar->sys_cap_info);
 
        if (ar->num_rf_chains > ar->max_spatial_stream) {
                ath10k_warn(ar, "hardware advertises support for more spatial streams than it should (%d > %d)\n",
index e80dbe7e8f4cf86c16bf1cc2f624aa6da1886ccf..c1c9053d17655524a0cc006c64c2da408487840c 100644 (file)
@@ -3786,6 +3786,8 @@ struct wmi_pdev_param_map {
        u32 arp_srcaddr;
        u32 arp_dstaddr;
        u32 enable_btcoex;
+       u32 rfkill_config;
+       u32 rfkill_enable;
 };
 
 #define WMI_PDEV_PARAM_UNSUPPORTED 0
@@ -6851,6 +6853,7 @@ struct wmi_svc_rdy_ev_arg {
        __le32 num_mem_reqs;
        __le32 low_5ghz_chan;
        __le32 high_5ghz_chan;
+       __le32 sys_cap_info;
        const __le32 *service_map;
        size_t service_map_len;
        const struct wlan_host_mem_req *mem_reqs[WMI_MAX_MEM_REQS];