wifi: ath12k: add support for hardware rfkill for WCN7850
authorLingbo Kong <quic_lingbok@quicinc.com>
Wed, 6 Sep 2023 11:04:12 +0000 (19:04 +0800)
committerKalle Valo <quic_kvalo@quicinc.com>
Thu, 28 Sep 2023 14:59:41 +0000 (17:59 +0300)
When hardware rfkill is enabled in the firmware, it will report the
capability using WMI_SYS_CAP_INFO_RFKILL bit in the WMI_SERVICE_READY event
to the host. Currently ath12k does not process this service capability. In
order to support this, update ath12k to check if the capability is enabled,
if so, send the GPIO information to firmware. When the firmware detects
hardware rfkill is enabled by the user, it will report it using
WMI_RFKILL_STATE_CHANGE_EVENTID. When ath12k receive the event, it will set
the value of rfkill_radio_on based on whether radio_state is equal to
WMI_RFKILL_RADIO_STATE_ON, then send WMI_PDEV_PARAM_RFKILL_ENABLE to
firmware.

Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0-03427-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.15378.4

Signed-off-by: Lingbo Kong <quic_lingbok@quicinc.com>
Acked-by: Jeff Johnson <quic_jjohnson@quicinc.com>
Signed-off-by: Kalle Valo <quic_kvalo@quicinc.com>
Link: https://lore.kernel.org/r/20230906110412.182176-1-quic_lingbok@quicinc.com
drivers/net/wireless/ath/ath12k/core.c
drivers/net/wireless/ath/ath12k/core.h
drivers/net/wireless/ath/ath12k/hw.c
drivers/net/wireless/ath/ath12k/hw.h
drivers/net/wireless/ath/ath12k/mac.c
drivers/net/wireless/ath/ath12k/mac.h
drivers/net/wireless/ath/ath12k/wmi.c
drivers/net/wireless/ath/ath12k/wmi.h

index 39f938fafa8107d23aa7c682475a57b4ff12b06b..c6fb1e435d86b7b6dbce1518a2ef4d0a53d76355 100644 (file)
@@ -19,6 +19,27 @@ unsigned int ath12k_debug_mask;
 module_param_named(debug_mask, ath12k_debug_mask, uint, 0644);
 MODULE_PARM_DESC(debug_mask, "Debugging mask");
 
+static int ath12k_core_rfkill_config(struct ath12k_base *ab)
+{
+       struct ath12k *ar;
+       int ret = 0, i;
+
+       if (!(ab->target_caps.sys_cap_info & WMI_SYS_CAP_INFO_RFKILL))
+               return 0;
+
+       for (i = 0; i < ab->num_radios; i++) {
+               ar = ab->pdevs[i].ar;
+
+               ret = ath12k_mac_rfkill_config(ar);
+               if (ret && ret != -EOPNOTSUPP) {
+                       ath12k_warn(ab, "failed to configure rfkill: %d", ret);
+                       return ret;
+               }
+       }
+
+       return ret;
+}
+
 int ath12k_core_suspend(struct ath12k_base *ab)
 {
        int ret;
@@ -603,6 +624,13 @@ int ath12k_core_qmi_firmware_ready(struct ath12k_base *ab)
                goto err_core_stop;
        }
        ath12k_hif_irq_enable(ab);
+
+       ret = ath12k_core_rfkill_config(ab);
+       if (ret && ret != -EOPNOTSUPP) {
+               ath12k_err(ab, "failed to config rfkill: %d\n", ret);
+               goto err_core_stop;
+       }
+
        mutex_unlock(&ab->core_lock);
 
        return 0;
@@ -655,6 +683,27 @@ err_hal_srng_deinit:
        return ret;
 }
 
+static void ath12k_rfkill_work(struct work_struct *work)
+{
+       struct ath12k_base *ab = container_of(work, struct ath12k_base, rfkill_work);
+       struct ath12k *ar;
+       bool rfkill_radio_on;
+       int i;
+
+       spin_lock_bh(&ab->base_lock);
+       rfkill_radio_on = ab->rfkill_radio_on;
+       spin_unlock_bh(&ab->base_lock);
+
+       for (i = 0; i < ab->num_radios; i++) {
+               ar = ab->pdevs[i].ar;
+               if (!ar)
+                       continue;
+
+               ath12k_mac_rfkill_enable_radio(ar, rfkill_radio_on);
+               wiphy_rfkill_set_hw_state(ar->hw->wiphy, !rfkill_radio_on);
+       }
+}
+
 void ath12k_core_halt(struct ath12k *ar)
 {
        struct ath12k_base *ab = ar->ab;
@@ -668,6 +717,7 @@ void ath12k_core_halt(struct ath12k *ar)
        ath12k_mac_peer_cleanup_all(ar);
        cancel_delayed_work_sync(&ar->scan.timeout);
        cancel_work_sync(&ar->regd_update_work);
+       cancel_work_sync(&ab->rfkill_work);
 
        rcu_assign_pointer(ab->pdevs_active[ar->pdev_idx], NULL);
        synchronize_rcu();
@@ -921,6 +971,8 @@ struct ath12k_base *ath12k_core_alloc(struct device *dev, size_t priv_size,
        init_waitqueue_head(&ab->wmi_ab.tx_credits_wq);
        INIT_WORK(&ab->restart_work, ath12k_core_restart);
        INIT_WORK(&ab->reset_work, ath12k_core_reset);
+       INIT_WORK(&ab->rfkill_work, ath12k_rfkill_work);
+
        timer_setup(&ab->rx_replenish_retry, ath12k_ce_rx_replenish_retry, 0);
        init_completion(&ab->htc_suspend);
 
index d873b573dac669111ce622149801bcf078b522f7..3f5f0471f640428aa4c11c4e4dc98e07c704d01a 100644 (file)
@@ -771,6 +771,10 @@ struct ath12k_base {
        u64 fw_soc_drop_count;
        bool static_window_map;
 
+       struct work_struct rfkill_work;
+       /* true means radio is on */
+       bool rfkill_radio_on;
+
        /* must be last */
        u8 drv_priv[] __aligned(sizeof(void *));
 };
index 5991cc91cd00f034826eccd96244d355604e7ead..f69649f58e822ab48f48d416fa4ebe14b9f77977 100644 (file)
@@ -907,6 +907,10 @@ static const struct ath12k_hw_params ath12k_hw_params[] = {
                .hal_ops = &hal_qcn9274_ops,
 
                .qmi_cnss_feature_bitmap = BIT(CNSS_QDSS_CFG_MISS_V01),
+
+               .rfkill_pin = 0,
+               .rfkill_cfg = 0,
+               .rfkill_on_level = 0,
        },
        {
                .name = "wcn7850 hw2.0",
@@ -964,6 +968,10 @@ static const struct ath12k_hw_params ath12k_hw_params[] = {
 
                .qmi_cnss_feature_bitmap = BIT(CNSS_QDSS_CFG_MISS_V01) |
                                           BIT(CNSS_PCIE_PERST_NO_PULL_V01),
+
+               .rfkill_pin = 48,
+               .rfkill_cfg = 0,
+               .rfkill_on_level = 1,
        },
        {
                .name = "qcn9274 hw2.0",
@@ -1019,6 +1027,10 @@ static const struct ath12k_hw_params ath12k_hw_params[] = {
                .hal_ops = &hal_qcn9274_ops,
 
                .qmi_cnss_feature_bitmap = BIT(CNSS_QDSS_CFG_MISS_V01),
+
+               .rfkill_pin = 0,
+               .rfkill_cfg = 0,
+               .rfkill_on_level = 0,
        },
 };
 
index e6c4223c283c30fb0c823ca0c864bf538d84fede..1b4912bf57ad1dee69842421def48e64d6c5f4a3 100644 (file)
@@ -186,6 +186,10 @@ struct ath12k_hw_params {
        const struct hal_ops *hal_ops;
 
        u64 qmi_cnss_feature_bitmap;
+
+       u32 rfkill_pin;
+       u32 rfkill_cfg;
+       u32 rfkill_on_level;
 };
 
 struct ath12k_hw_ops {
index 85602d64b607e80ffce3e7ffbbc092f4b1072e41..486d062b0a76e7f14ad1a63bb34f78718406851a 100644 (file)
@@ -5108,6 +5108,63 @@ err:
        return ret;
 }
 
+int ath12k_mac_rfkill_config(struct ath12k *ar)
+{
+       struct ath12k_base *ab = ar->ab;
+       u32 param;
+       int ret;
+
+       if (ab->hw_params->rfkill_pin == 0)
+               return -EOPNOTSUPP;
+
+       ath12k_dbg(ab, ATH12K_DBG_MAC,
+                  "mac rfkill_pin %d rfkill_cfg %d rfkill_on_level %d",
+                  ab->hw_params->rfkill_pin, ab->hw_params->rfkill_cfg,
+                  ab->hw_params->rfkill_on_level);
+
+       param = u32_encode_bits(ab->hw_params->rfkill_on_level,
+                               WMI_RFKILL_CFG_RADIO_LEVEL) |
+               u32_encode_bits(ab->hw_params->rfkill_pin,
+                               WMI_RFKILL_CFG_GPIO_PIN_NUM) |
+               u32_encode_bits(ab->hw_params->rfkill_cfg,
+                               WMI_RFKILL_CFG_PIN_AS_GPIO);
+
+       ret = ath12k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_HW_RFKILL_CONFIG,
+                                       param, ar->pdev->pdev_id);
+       if (ret) {
+               ath12k_warn(ab,
+                           "failed to set rfkill config 0x%x: %d\n",
+                           param, ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+int ath12k_mac_rfkill_enable_radio(struct ath12k *ar, bool enable)
+{
+       enum wmi_rfkill_enable_radio param;
+       int ret;
+
+       if (enable)
+               param = WMI_RFKILL_ENABLE_RADIO_ON;
+       else
+               param = WMI_RFKILL_ENABLE_RADIO_OFF;
+
+       ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "mac %d rfkill enable %d",
+                  ar->pdev_idx, param);
+
+       ret = ath12k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_RFKILL_ENABLE,
+                                       param, ar->pdev->pdev_id);
+       if (ret) {
+               ath12k_warn(ar->ab, "failed to set rfkill enable param %d: %d\n",
+                           param, ret);
+               return ret;
+       }
+
+       return 0;
+}
+
 static void ath12k_mac_op_stop(struct ieee80211_hw *hw)
 {
        struct ath12k *ar = hw->priv;
@@ -5128,6 +5185,7 @@ static void ath12k_mac_op_stop(struct ieee80211_hw *hw)
 
        cancel_delayed_work_sync(&ar->scan.timeout);
        cancel_work_sync(&ar->regd_update_work);
+       cancel_work_sync(&ar->ab->rfkill_work);
 
        spin_lock_bh(&ar->data_lock);
        list_for_each_entry_safe(ppdu_stats, tmp, &ar->ppdu_stats_info, list) {
index 7b16b70df4fa89ba474f3cc3c68c4ebabe0dc5dd..59b4e8f5eee059c2a1f61f0edd77765865e83e6f 100644 (file)
@@ -73,4 +73,6 @@ int ath12k_mac_tx_mgmt_pending_free(int buf_id, void *skb, void *ctx);
 enum rate_info_bw ath12k_mac_bw_to_mac80211_bw(enum ath12k_supported_bw bw);
 enum ath12k_supported_bw ath12k_mac_mac80211_bw_to_ath12k_bw(enum rate_info_bw bw);
 enum hal_encrypt_type ath12k_dp_tx_get_encrypt_type(u32 cipher);
+int ath12k_mac_rfkill_enable_radio(struct ath12k *ar, bool enable);
+int ath12k_mac_rfkill_config(struct ath12k *ar);
 #endif
index a771ffa9a30968b0d050dc30380b60686cba4ebc..135d7d7b3ed5969a77f527dc37f8fc322278aa7c 100644 (file)
@@ -152,6 +152,8 @@ static const struct ath12k_wmi_tlv_policy ath12k_wmi_tlv_policies[] = {
                .min_len = sizeof(struct wmi_service_available_event) },
        [WMI_TAG_PEER_ASSOC_CONF_EVENT] = {
                .min_len = sizeof(struct wmi_peer_assoc_conf_event) },
+       [WMI_TAG_RFKILL_EVENT] = {
+               .min_len = sizeof(struct wmi_rfkill_state_change_event) },
        [WMI_TAG_PDEV_CTL_FAILSAFE_CHECK_EVENT] = {
                .min_len = sizeof(struct wmi_pdev_ctl_failsafe_chk_event) },
        [WMI_TAG_HOST_SWFDA_EVENT] = {
@@ -6604,6 +6606,40 @@ static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
        kfree(tb);
 }
 
+static void ath12k_rfkill_state_change_event(struct ath12k_base *ab,
+                                            struct sk_buff *skb)
+{
+       const struct wmi_rfkill_state_change_event *ev;
+       const void **tb;
+       int ret;
+
+       tb = ath12k_wmi_tlv_parse_alloc(ab, skb->data, skb->len, GFP_ATOMIC);
+       if (IS_ERR(tb)) {
+               ret = PTR_ERR(tb);
+               ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
+               return;
+       }
+
+       ev = tb[WMI_TAG_RFKILL_EVENT];
+       if (!ev) {
+               kfree(tb);
+               return;
+       }
+
+       ath12k_dbg(ab, ATH12K_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));
+
+       spin_lock_bh(&ab->base_lock);
+       ab->rfkill_radio_on = (ev->radio_state == cpu_to_le32(WMI_RFKILL_RADIO_STATE_ON));
+       spin_unlock_bh(&ab->base_lock);
+
+       queue_work(ab->workqueue, &ab->rfkill_work);
+       kfree(tb);
+}
+
 static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb)
 {
        struct wmi_cmd_hdr *cmd_hdr;
@@ -6696,6 +6732,9 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb)
        case WMI_OFFLOAD_PROB_RESP_TX_STATUS_EVENTID:
                ath12k_probe_resp_tx_status_event(ab, skb);
                break;
+       case WMI_RFKILL_STATE_CHANGE_EVENTID:
+               ath12k_rfkill_state_change_event(ab, skb);
+               break;
        /* add Unsupported events here */
        case WMI_TBTTOFFSET_EXT_UPDATE_EVENTID:
        case WMI_PEER_OPER_MODE_CHANGE_EVENTID:
index c75a6fa1f7e089158b6371987314e6362aba9ae7..965755b4cbfdb5aa11ad65076f5b47dcf57eea9a 100644 (file)
@@ -4793,6 +4793,31 @@ struct ath12k_wmi_base {
 
 #define ATH12K_FW_STATS_BUF_SIZE (1024 * 1024)
 
+enum wmi_sys_cap_info_flags {
+       WMI_SYS_CAP_INFO_RXTX_LED       = BIT(0),
+       WMI_SYS_CAP_INFO_RFKILL         = BIT(1),
+};
+
+#define WMI_RFKILL_CFG_GPIO_PIN_NUM            GENMASK(5, 0)
+#define WMI_RFKILL_CFG_RADIO_LEVEL             BIT(6)
+#define WMI_RFKILL_CFG_PIN_AS_GPIO             GENMASK(10, 7)
+
+enum wmi_rfkill_enable_radio {
+       WMI_RFKILL_ENABLE_RADIO_ON      = 0,
+       WMI_RFKILL_ENABLE_RADIO_OFF     = 1,
+};
+
+enum wmi_rfkill_radio_state {
+       WMI_RFKILL_RADIO_STATE_OFF      = 1,
+       WMI_RFKILL_RADIO_STATE_ON       = 2,
+};
+
+struct wmi_rfkill_state_change_event {
+       __le32 gpio_pin_num;
+       __le32 int_type;
+       __le32 radio_state;
+} __packed;
+
 void ath12k_wmi_init_qcn9274(struct ath12k_base *ab,
                             struct ath12k_wmi_resource_config_arg *config);
 void ath12k_wmi_init_wcn7850(struct ath12k_base *ab,