From 65569d506cc6816a4eac3a411b7047c2ca82cd85 Mon Sep 17 00:00:00 2001 From: Yu Wang Date: Wed, 18 Dec 2019 09:32:05 +0800 Subject: [PATCH 1/3] cnss2: check wlfw mac before switching to mission mode To make sure WLAN MAC address has already been well configured in firmware before switching to mission mode, send QMI message to WLAN firmware to query the current MAC status, and wait for 10 seconds at most if it's not ready. A new DTS property 'use-nv-mac' is also added: if 'use-nv-mac' is defined in WLAN dts node, the MAC checking will be performed; otherwise, it won't. Change-Id: I1e751d84fb304cb95ea0f989f6939bc7ba5a3ecc Signed-off-by: Yu Wang --- drivers/net/wireless/cnss2/main.c | 8 ++ drivers/net/wireless/cnss2/main.h | 1 + drivers/net/wireless/cnss2/qmi.c | 131 ++++++++++++++++++++++++++++++ include/linux/soc/qcom/qmi.h | 1 + 4 files changed, 141 insertions(+) diff --git a/drivers/net/wireless/cnss2/main.c b/drivers/net/wireless/cnss2/main.c index caa4058be40d..43178fa01c48 100644 --- a/drivers/net/wireless/cnss2/main.c +++ b/drivers/net/wireless/cnss2/main.c @@ -2095,6 +2095,13 @@ static const struct of_device_id cnss_of_match_table[] = { }; MODULE_DEVICE_TABLE(of, cnss_of_match_table); +static inline bool +cnss_use_nv_mac(struct cnss_plat_data *plat_priv) +{ + return of_property_read_bool(plat_priv->plat_dev->dev.of_node, + "use-nv-mac"); +} + static int cnss_probe(struct platform_device *plat_dev) { int ret = 0; @@ -2127,6 +2134,7 @@ static int cnss_probe(struct platform_device *plat_dev) plat_priv->plat_dev = plat_dev; plat_priv->device_id = device_id->driver_data; plat_priv->bus_type = cnss_get_bus_type(plat_priv->device_id); + plat_priv->use_nv_mac = cnss_use_nv_mac(plat_priv); cnss_set_plat_priv(plat_dev, plat_priv); platform_set_drvdata(plat_dev, plat_priv); INIT_LIST_HEAD(&plat_priv->vreg_list); diff --git a/drivers/net/wireless/cnss2/main.h b/drivers/net/wireless/cnss2/main.h index eee1bee56262..bb2b575f7f2f 100644 --- a/drivers/net/wireless/cnss2/main.h +++ b/drivers/net/wireless/cnss2/main.h @@ -372,6 +372,7 @@ struct cnss_plat_data { u64 dynamic_feature; void *get_info_cb_ctx; int (*get_info_cb)(void *ctx, void *event, int event_len); + u8 use_nv_mac; }; #ifdef CONFIG_ARCH_QCOM diff --git a/drivers/net/wireless/cnss2/qmi.c b/drivers/net/wireless/cnss2/qmi.c index 74c07f076a9e..22f059c3c166 100644 --- a/drivers/net/wireless/cnss2/qmi.c +++ b/drivers/net/wireless/cnss2/qmi.c @@ -28,6 +28,9 @@ #define QMI_WLFW_MAX_RECV_BUF_SIZE SZ_8K +#define QMI_WLFW_MAC_READY_TIMEOUT_MS 50 +#define QMI_WLFW_MAC_READY_MAX_RETRY 200 + static char *cnss_qmi_mode_to_str(enum cnss_driver_mode mode) { switch (mode) { @@ -692,6 +695,131 @@ out: return ret; } +static int cnss_wlfw_wlan_mac_req_send_sync(struct cnss_plat_data *plat_priv, + u8 *mac, u32 mac_len) +{ + struct wlfw_mac_addr_req_msg_v01 *req; + struct wlfw_mac_addr_resp_msg_v01 *resp; + struct qmi_txn txn; + int ret; + u8 is_query; + + if (!plat_priv) + return -ENODEV; + + /* NULL mac && zero mac_len means querying the status of MAC in FW */ + if ((mac && mac_len != QMI_WLFW_MAC_ADDR_SIZE_V01) || + (!mac && mac_len != 0)) + return -EINVAL; + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + ret = qmi_txn_init(&plat_priv->qmi_wlfw, &txn, + wlfw_mac_addr_resp_msg_v01_ei, resp); + if (ret < 0) { + cnss_pr_err("Failed to initialize txn for mac req, err: %d\n", + ret); + ret = -EIO; + goto out; + } + + is_query = !mac; + if (!is_query) { + /* DO NOT print this for mac query, that might be too many */ + cnss_pr_dbg("Sending WLAN mac req [%pM], state: 0x%lx\n", + mac, plat_priv->driver_state); + memcpy(req->mac_addr, mac, mac_len); + + /* 0 - query status of wlfw MAC; 1 - set wlfw MAC */ + req->mac_addr_valid = 1; + } + + ret = qmi_send_request(&plat_priv->qmi_wlfw, NULL, &txn, + QMI_WLFW_MAC_ADDR_REQ_V01, + WLFW_MAC_ADDR_REQ_MSG_V01_MAX_MSG_LEN, + wlfw_mac_addr_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + cnss_pr_err("Failed to send mac req, err: %d\n", ret); + + ret = -EIO; + goto out; + } + + ret = qmi_txn_wait(&txn, QMI_WLFW_TIMEOUT_JF); + if (ret < 0) { + cnss_pr_err("Failed to wait for resp of mac req, err: %d\n", + ret); + + ret = -EIO; + goto out; + } + + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + cnss_pr_err("WLAN mac req failed, result: %d, err: %d\n", + resp->resp.result); + + ret = -EIO; + goto out; + } + + if (resp->resp.error != QMI_ERR_NONE_V01) { + ret = ((resp->resp.error == QMI_ERR_NETWORK_NOT_READY_V01 && + is_query) ? -EAGAIN : -EIO); + if (ret != -EAGAIN) + cnss_pr_err("Got error resp for mac req, err: %d\n", + resp->resp.error); + goto out; + } + + cnss_pr_dbg("WLAN mac req completed\n"); + +out: + kfree(req); + kfree(resp); + return ret; +} + +static void cnss_wait_for_wlfw_mac_ready(struct cnss_plat_data *plat_priv) +{ + int ret, retry = 0; + + if (!plat_priv) + return; + + cnss_pr_dbg("Checking wlfw mac, state: 0x%lx\n", + plat_priv->driver_state); + do { + /* query the current status of WLAN MAC */ + ret = cnss_wlfw_wlan_mac_req_send_sync(plat_priv, NULL, 0); + if (!ret) { + cnss_pr_dbg("wlfw mac is ready\n"); + break; + } + + if (ret != -EAGAIN) { + cnss_pr_err("failed to query wlfw mac, error: %d\n", + ret); + break; + } + + if (++retry >= QMI_WLFW_MAC_READY_MAX_RETRY) { + cnss_pr_err("Timeout to wait for wlfw mac ready\n"); + break; + } + + msleep(QMI_WLFW_MAC_READY_TIMEOUT_MS); + } while (true); +} + int cnss_wlfw_wlan_mode_send_sync(struct cnss_plat_data *plat_priv, enum cnss_driver_mode mode) { @@ -703,6 +831,9 @@ int cnss_wlfw_wlan_mode_send_sync(struct cnss_plat_data *plat_priv, if (!plat_priv) return -ENODEV; + if (mode == CNSS_MISSION && plat_priv->use_nv_mac) + cnss_wait_for_wlfw_mac_ready(plat_priv); + cnss_pr_dbg("Sending mode message, mode: %s(%d), state: 0x%lx\n", cnss_qmi_mode_to_str(mode), mode, plat_priv->driver_state); diff --git a/include/linux/soc/qcom/qmi.h b/include/linux/soc/qcom/qmi.h index 6e8694e78dff..8a567c3d8549 100644 --- a/include/linux/soc/qcom/qmi.h +++ b/include/linux/soc/qcom/qmi.h @@ -87,6 +87,7 @@ struct qmi_elem_info { #define QMI_ERR_INTERNAL_V01 3 #define QMI_ERR_CLIENT_IDS_EXHAUSTED_V01 5 #define QMI_ERR_INVALID_ID_V01 41 +#define QMI_ERR_NETWORK_NOT_READY_V01 53 #define QMI_ERR_ENCODING_V01 58 #define QMI_ERR_DISABLED_V01 69 #define QMI_ERR_INCOMPATIBLE_STATE_V01 90 From f628a32c05703962e85d86e2585da899a2c41cdb Mon Sep 17 00:00:00 2001 From: Yue Ma Date: Mon, 13 Jan 2020 15:13:43 +0800 Subject: [PATCH 2/3] cnss2: Set WLAON_QFPROM_PWR_CTRL_REG during power on and off After device power on and link config, clear VDD4BLOW_EN bit of WLAON_QFPROM_PWR_CTRL_REG register to disable OTP write. Before device link suspend and power off, clear VDD4BLOW_EN bit and set SHUTDOWN_EN bit. Also add a 1ms sleep after writing WLAON_QFPROM_PWR_CTRL_REG register. Add a DTSI entry to control above operations. Change-Id: Icb307a85bb5d283b78bdd7615f9a4757867c8075 CRs-Fixed: 2601850 Signed-off-by: Yue Ma --- drivers/net/wireless/cnss2/main.c | 14 +++++- drivers/net/wireless/cnss2/main.h | 3 +- drivers/net/wireless/cnss2/pci.c | 71 ++++++++++++++++++++++++++++++- drivers/net/wireless/cnss2/reg.h | 4 +- 4 files changed, 87 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/cnss2/main.c b/drivers/net/wireless/cnss2/main.c index 43178fa01c48..0fbf8b7e7aae 100644 --- a/drivers/net/wireless/cnss2/main.c +++ b/drivers/net/wireless/cnss2/main.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ #include #include @@ -2070,6 +2070,17 @@ static void cnss_init_control_params(struct cnss_plat_data *plat_priv) plat_priv->ctrl_params.time_sync_period = CNSS_TIME_SYNC_PERIOD_DEFAULT; } +static void cnss_get_wlaon_pwr_ctrl_info(struct cnss_plat_data *plat_priv) +{ + struct device *dev = &plat_priv->plat_dev->dev; + + plat_priv->set_wlaon_pwr_ctrl = + of_property_read_bool(dev->of_node, "qcom,set-wlaon-pwr-ctrl"); + + cnss_pr_dbg("set_wlaon_pwr_ctrl is %d\n", + plat_priv->set_wlaon_pwr_ctrl); +} + static const struct platform_device_id cnss_platform_id_table[] = { { .name = "qca6174", .driver_data = QCA6174_DEVICE_ID, }, { .name = "qca6290", .driver_data = QCA6290_DEVICE_ID, }, @@ -2140,6 +2151,7 @@ static int cnss_probe(struct platform_device *plat_dev) INIT_LIST_HEAD(&plat_priv->vreg_list); INIT_LIST_HEAD(&plat_priv->clk_list); + cnss_get_wlaon_pwr_ctrl_info(plat_priv); cnss_get_cpr_info(plat_priv); cnss_init_control_params(plat_priv); diff --git a/drivers/net/wireless/cnss2/main.h b/drivers/net/wireless/cnss2/main.h index bb2b575f7f2f..3a88819b8480 100644 --- a/drivers/net/wireless/cnss2/main.h +++ b/drivers/net/wireless/cnss2/main.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ #ifndef _CNSS_MAIN_H #define _CNSS_MAIN_H @@ -373,6 +373,7 @@ struct cnss_plat_data { void *get_info_cb_ctx; int (*get_info_cb)(void *ctx, void *event, int event_len); u8 use_nv_mac; + u8 set_wlaon_pwr_ctrl; }; #ifdef CONFIG_ARCH_QCOM diff --git a/drivers/net/wireless/cnss2/pci.c b/drivers/net/wireless/cnss2/pci.c index 5054a0dd78f6..611861ec5afc 100644 --- a/drivers/net/wireless/cnss2/pci.c +++ b/drivers/net/wireless/cnss2/pci.c @@ -62,6 +62,9 @@ static DEFINE_SPINLOCK(time_sync_lock); #define MHI_TIMEOUT_OVERWRITE_MS (plat_priv->ctrl_params.mhi_timeout) #define MHI_M2_TIMEOUT_MS (plat_priv->ctrl_params.mhi_m2_timeout) +#define WLAON_PWR_CTRL_SHUTDOWN_DELAY_MIN_US 1000 +#define WLAON_PWR_CTRL_SHUTDOWN_DELAY_MAX_US 2000 + #define FORCE_WAKE_DELAY_MIN_US 4000 #define FORCE_WAKE_DELAY_MAX_US 6000 #define FORCE_WAKE_DELAY_TIMEOUT_US 60000 @@ -480,6 +483,7 @@ static int cnss_pci_force_wake_get(struct cnss_pci_data *pci_priv) if (cnss_pci_is_device_awake(dev) != true) { cnss_pr_err("Timed out to request force wake\n"); + cnss_pci_force_wake_release(dev); return -ETIMEDOUT; } @@ -1131,6 +1135,62 @@ static void cnss_pci_deinit_mhi(struct cnss_pci_data *pci_priv) cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_DEINIT); } +static void cnss_pci_set_wlaon_pwr_ctrl(struct cnss_pci_data *pci_priv, + bool set_vddd4blow, bool set_shutdown, + bool do_force_wake) +{ + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + int ret; + u32 val; + + if (!plat_priv->set_wlaon_pwr_ctrl) + return; + + if (do_force_wake) + if (cnss_pci_force_wake_get(pci_priv)) + return; + + ret = cnss_pci_reg_read(pci_priv, QCA6390_WLAON_QFPROM_PWR_CTRL_REG, + &val); + if (ret) { + cnss_pr_err("Failed to read register offset 0x%x, err = %d\n", + QCA6390_WLAON_QFPROM_PWR_CTRL_REG, ret); + goto force_wake_put; + } + + cnss_pr_dbg("Read register offset 0x%x, val = 0x%x\n", + QCA6390_WLAON_QFPROM_PWR_CTRL_REG, val); + + if (set_vddd4blow) + val |= QFPROM_PWR_CTRL_VDD4BLOW_SW_EN_MASK; + else + val &= ~QFPROM_PWR_CTRL_VDD4BLOW_SW_EN_MASK; + + if (set_shutdown) + val |= QFPROM_PWR_CTRL_SHUTDOWN_EN_MASK; + else + val &= ~QFPROM_PWR_CTRL_SHUTDOWN_EN_MASK; + + ret = cnss_pci_reg_write(pci_priv, QCA6390_WLAON_QFPROM_PWR_CTRL_REG, + val); + if (ret) { + cnss_pr_err("Failed to write register offset 0x%x, err = %d\n", + QCA6390_WLAON_QFPROM_PWR_CTRL_REG, ret); + goto force_wake_put; + } + + cnss_pr_dbg("Write val 0x%x to register offset 0x%x\n", val, + QCA6390_WLAON_QFPROM_PWR_CTRL_REG); + + if (set_shutdown) + usleep_range(WLAON_PWR_CTRL_SHUTDOWN_DELAY_MIN_US, + WLAON_PWR_CTRL_SHUTDOWN_DELAY_MAX_US); + +force_wake_put: + if (do_force_wake) + cnss_pci_force_wake_put(pci_priv); +} + static int cnss_pci_get_device_timestamp(struct cnss_pci_data *pci_priv, u64 *time_us) { @@ -1671,6 +1731,7 @@ retry: goto power_off; } + cnss_pci_set_wlaon_pwr_ctrl(pci_priv, false, false, false); timeout = cnss_get_boot_timeout(&pci_priv->pci_dev->dev); ret = cnss_pci_start_mhi(pci_priv); @@ -1705,6 +1766,7 @@ retry: return 0; stop_mhi: + cnss_pci_set_wlaon_pwr_ctrl(pci_priv, false, true, true); cnss_pci_power_off_mhi(pci_priv); cnss_suspend_pci_link(pci_priv); cnss_pci_deinit_mhi(pci_priv); @@ -1718,6 +1780,7 @@ static int cnss_qca6290_shutdown(struct cnss_pci_data *pci_priv) { int ret = 0; struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + int do_force_wake = true; cnss_pci_pm_runtime_resume(pci_priv); @@ -1744,6 +1807,10 @@ static int cnss_qca6290_shutdown(struct cnss_pci_data *pci_priv) goto skip_power_off; } + if (test_bit(CNSS_DEV_ERR_NOTIFY, &plat_priv->driver_state)) + do_force_wake = false; + + cnss_pci_set_wlaon_pwr_ctrl(pci_priv, false, true, do_force_wake); cnss_pci_power_off_mhi(pci_priv); ret = cnss_suspend_pci_link(pci_priv); if (ret) @@ -4113,6 +4180,7 @@ static int cnss_pci_probe(struct pci_dev *pci_dev, case QCA6290_DEVICE_ID: case QCA6390_DEVICE_ID: case QCA6490_DEVICE_ID: + cnss_pci_set_wlaon_pwr_ctrl(pci_priv, false, false, false); timer_setup(&pci_priv->dev_rddm_timer, cnss_dev_rddm_timeout_hdlr, 0); INIT_DELAYED_WORK(&pci_priv->time_sync_work, @@ -4127,11 +4195,10 @@ static int cnss_pci_probe(struct pci_dev *pci_dev, goto disable_bus; } cnss_pci_get_link_status(pci_priv); - cnss_pci_config_regs(pci_priv); - if (EMULATION_HW) break; + cnss_pci_set_wlaon_pwr_ctrl(pci_priv, false, true, false); ret = cnss_suspend_pci_link(pci_priv); if (ret) cnss_pr_err("Failed to suspend PCI link, err = %d\n", diff --git a/drivers/net/wireless/cnss2/reg.h b/drivers/net/wireless/cnss2/reg.h index 4052de4b78c5..6e7b70960625 100644 --- a/drivers/net/wireless/cnss2/reg.h +++ b/drivers/net/wireless/cnss2/reg.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (c) 2019, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. */ #ifndef _CNSS_REG_H #define _CNSS_REG_H @@ -204,6 +204,8 @@ #define QCA6390_WLAON_WL_CLK_CNTL_KDF_REG 0x1F80314 #define QCA6390_WLAON_WL_CLK_CNTL_PMU_HFRC_REG 0x1F80318 #define QCA6390_WLAON_QFPROM_PWR_CTRL_REG 0x1F8031C +#define QFPROM_PWR_CTRL_VDD4BLOW_SW_EN_MASK 0x4 +#define QFPROM_PWR_CTRL_SHUTDOWN_EN_MASK 0x1 #define QCA6390_WLAON_DLY_CONFIG 0x1F80400 #define QCA6390_WLAON_WLAON_Q6_IRQ_REG 0x1F80404 #define QCA6390_WLAON_PCIE_INTF_SW_CFG_REG 0x1F80408 From c3985a135a7e24da1f79eee30e64ae6cd64477c0 Mon Sep 17 00:00:00 2001 From: Yu Wang Date: Fri, 10 Jan 2020 16:43:16 -0800 Subject: [PATCH 3/3] cnss2: Add sysfs entry for system shutdown Add sysfs entry '/sys/kernel/shutdown_wlan/shutdown'. When system is shutting down or rebooting, this entry will be written so that drvier can be notified and handle it properly. Change-Id: I2537a2893f8787ff7c9d33608ef6c42075c3d128 Signed-off-by: Yu Wang Signed-off-by: Yue Ma --- drivers/net/wireless/cnss2/main.c | 64 ++++++++++++++++++++++++++++++- drivers/net/wireless/cnss2/main.h | 1 + 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/cnss2/main.c b/drivers/net/wireless/cnss2/main.c index 0fbf8b7e7aae..6971f7b94d1b 100644 --- a/drivers/net/wireless/cnss2/main.c +++ b/drivers/net/wireless/cnss2/main.c @@ -642,6 +642,13 @@ int cnss_idle_restart(struct device *dev) timeout = cnss_get_boot_timeout(dev); reinit_completion(&plat_priv->power_up_complete); + + if (test_bit(CNSS_IN_REBOOT, &plat_priv->driver_state)) { + cnss_pr_dbg("Reboot or shutdown is in progress, ignore idle restart\n"); + ret = -EINVAL; + goto out; + } + ret = wait_for_completion_timeout(&plat_priv->power_up_complete, msecs_to_jiffies(timeout) << 2); if (!ret) { @@ -1918,6 +1925,23 @@ static void cnss_unregister_bus_scale(struct cnss_plat_data *plat_priv) icc_put(bus_bw_info->cnss_path); } +static ssize_t shutdown_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct cnss_plat_data *plat_priv = cnss_get_plat_priv(NULL); + + if (plat_priv) { + set_bit(CNSS_IN_REBOOT, &plat_priv->driver_state); + del_timer(&plat_priv->fw_boot_timer); + complete_all(&plat_priv->power_up_complete); + } + + cnss_pr_dbg("Received shutdown notification\n"); + + return count; +} + static ssize_t fs_ready_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -1961,18 +1985,55 @@ static ssize_t fs_ready_store(struct device *dev, return count; } +static struct kobj_attribute shutdown_attribute = __ATTR_WO(shutdown); static DEVICE_ATTR_WO(fs_ready); +static int cnss_create_shutdown_sysfs(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + + plat_priv->shutdown_kobj = kobject_create_and_add("shutdown_wlan", + kernel_kobj); + if (!plat_priv->shutdown_kobj) { + cnss_pr_err("Failed to create shutdown_wlan kernel object\n"); + return -ENOMEM; + } + + ret = sysfs_create_file(plat_priv->shutdown_kobj, + &shutdown_attribute.attr); + if (ret) { + cnss_pr_err("Failed to create sysfs shutdown file, err = %d\n", + ret); + kobject_put(plat_priv->shutdown_kobj); + plat_priv->shutdown_kobj = NULL; + } + + return ret; +} + +static void cnss_remove_shutdown_sysfs(struct cnss_plat_data *plat_priv) +{ + if (plat_priv->shutdown_kobj) { + sysfs_remove_file(plat_priv->shutdown_kobj, + &shutdown_attribute.attr); + kobject_put(plat_priv->shutdown_kobj); + plat_priv->shutdown_kobj = NULL; + } +} + static int cnss_create_sysfs(struct cnss_plat_data *plat_priv) { int ret = 0; ret = device_create_file(&plat_priv->plat_dev->dev, &dev_attr_fs_ready); if (ret) { - cnss_pr_err("Failed to create device file, err = %d\n", ret); + cnss_pr_err("Failed to create device fs_ready file, err = %d\n", + ret); goto out; } + cnss_create_shutdown_sysfs(plat_priv); + return 0; out: return ret; @@ -1980,6 +2041,7 @@ out: static void cnss_remove_sysfs(struct cnss_plat_data *plat_priv) { + cnss_remove_shutdown_sysfs(plat_priv); device_remove_file(&plat_priv->plat_dev->dev, &dev_attr_fs_ready); } diff --git a/drivers/net/wireless/cnss2/main.h b/drivers/net/wireless/cnss2/main.h index 3a88819b8480..ca923bf0a5be 100644 --- a/drivers/net/wireless/cnss2/main.h +++ b/drivers/net/wireless/cnss2/main.h @@ -374,6 +374,7 @@ struct cnss_plat_data { int (*get_info_cb)(void *ctx, void *event, int event_len); u8 use_nv_mac; u8 set_wlaon_pwr_ctrl; + struct kobject *shutdown_kobj; }; #ifdef CONFIG_ARCH_QCOM