From bb06fdbcd02fb0d51b3282f0b3edec55ab7d73fb Mon Sep 17 00:00:00 2001 From: Paul Zhang Date: Fri, 18 Dec 2020 17:13:51 +0800 Subject: [PATCH] net: cnss: add snapshot of cnss platform driver This is a snapshot of the CNSS driver and associated files as of msm-4.14 commit b56fde2f9abd44 ("cnss: Add cnss driver to msm-4.14"). Add Kconfig dependency. Comment obsolete codes to fix some compiler issues. Change-Id: I89bf0a85b086c7482609b331273d9eaf2fb66648 Signed-off-by: Paul Zhang --- drivers/net/wireless/Kconfig | 19 +- drivers/net/wireless/Makefile | 2 + drivers/net/wireless/cnss/Kconfig | 134 + drivers/net/wireless/cnss/Makefile | 9 + drivers/net/wireless/cnss/cnss_common.c | 436 ++ drivers/net/wireless/cnss/cnss_common.h | 56 + drivers/net/wireless/cnss/cnss_pci.c | 3915 +++++++++++++++++ drivers/net/wireless/cnss/cnss_sdio.c | 1598 +++++++ drivers/net/wireless/cnss/logger/Kconfig | 11 + drivers/net/wireless/cnss/logger/Makefile | 11 + drivers/net/wireless/cnss/logger/debugfs.c | 125 + drivers/net/wireless/cnss/logger/logger.h | 94 + drivers/net/wireless/cnss/logger/main.c | 47 + drivers/net/wireless/cnss/logger/nl_service.c | 467 ++ drivers/net/wireless/cnss_crypto/Makefile | 6 + drivers/net/wireless/cnss_crypto/cnss_secif.c | 161 + include/linux/esoc_client.h | 86 + include/linux/qcomwlan_secif.h | 32 + include/net/cnss.h | 253 ++ include/net/cnss_logger.h | 56 + include/uapi/linux/esoc_ctrl.h | 87 + 21 files changed, 7600 insertions(+), 5 deletions(-) create mode 100644 drivers/net/wireless/cnss/Kconfig create mode 100644 drivers/net/wireless/cnss/Makefile create mode 100644 drivers/net/wireless/cnss/cnss_common.c create mode 100644 drivers/net/wireless/cnss/cnss_common.h create mode 100644 drivers/net/wireless/cnss/cnss_pci.c create mode 100644 drivers/net/wireless/cnss/cnss_sdio.c create mode 100644 drivers/net/wireless/cnss/logger/Kconfig create mode 100644 drivers/net/wireless/cnss/logger/Makefile create mode 100644 drivers/net/wireless/cnss/logger/debugfs.c create mode 100644 drivers/net/wireless/cnss/logger/logger.h create mode 100644 drivers/net/wireless/cnss/logger/main.c create mode 100644 drivers/net/wireless/cnss/logger/nl_service.c create mode 100644 drivers/net/wireless/cnss_crypto/Makefile create mode 100644 drivers/net/wireless/cnss_crypto/cnss_secif.c create mode 100644 include/linux/esoc_client.h create mode 100644 include/linux/qcomwlan_secif.h create mode 100644 include/net/cnss.h create mode 100644 include/net/cnss_logger.h create mode 100644 include/uapi/linux/esoc_ctrl.h diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index e99233bf9e53..8406424ea0fa 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -9,7 +9,7 @@ menuconfig WLAN depends on NET select WIRELESS default y - ---help--- + help This section contains all the pre 802.11 and 802.11 wireless device drivers. For a complete list of drivers and documentation on them refer to the wireless wiki: @@ -54,7 +54,7 @@ config PCMCIA_RAYCS select WIRELESS_EXT select WEXT_SPY select WEXT_PRIV - ---help--- + help Say Y here if you intend to attach an Aviator/Raytheon PCMCIA (PC-card) wireless Ethernet networking card to your computer. Please read the file for @@ -76,7 +76,7 @@ config PCMCIA_WL3501 config MAC80211_HWSIM tristate "Simulated radio testing tool for mac80211" depends on MAC80211 - ---help--- + help This driver is a developer testing tool that can be used to test IEEE 802.11 networking stack (mac80211) functionality. This is not needed for normal wireless LAN usage and is only for testing. See @@ -94,7 +94,7 @@ config USB_NET_RNDIS_WLAN select USB_USBNET select USB_NET_CDCETHER select USB_NET_RNDIS_HOST - ---help--- + help This is a driver for wireless RNDIS devices. These are USB based adapters found in devices such as: @@ -118,7 +118,7 @@ config USB_NET_RNDIS_WLAN config VIRT_WIFI tristate "Wifi wrapper for ethernet drivers" depends on CFG80211 - ---help--- + help This option adds support for ethernet connections to appear as if they are wifi connections through a special rtnetlink device. @@ -135,7 +135,16 @@ config CLD_LL_CORE Select Y to compile the driver in order to have WLAN functionality support. +config CNSS_CRYPTO + tristate "Enable CNSS crypto support" + help + Add crypto support for the WLAN driver module. + This feature enable wlan driver to use the crypto APIs exported + from cnss platform driver. This crypto APIs used to generate cipher + key and add support for the WLAN driver module security protocol. + source "drivers/net/wireless/cnss2/Kconfig" +source "drivers/net/wireless/cnss/Kconfig" source "drivers/net/wireless/cnss_utils/Kconfig" source "drivers/net/wireless/cnss_genl/Kconfig" source "drivers/net/wireless/cnss_prealloc/Kconfig" diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index ced606e8d2ab..6a9cf02008be 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -31,6 +31,8 @@ obj-$(CONFIG_MAC80211_HWSIM) += mac80211_hwsim.o obj-$(CONFIG_VIRT_WIFI) += virt_wifi.o obj-$(CONFIG_CNSS2) += cnss2/ +obj-$(CONFIG_CNSS) += cnss/ obj-$(CONFIG_CNSS_UTILS) += cnss_utils/ obj-$(CONFIG_CNSS_GENL) += cnss_genl/ obj-$(CONFIG_WCNSS_MEM_PRE_ALLOC) += cnss_prealloc/ +obj-$(CONFIG_CNSS_CRYPTO) += cnss_crypto/ diff --git a/drivers/net/wireless/cnss/Kconfig b/drivers/net/wireless/cnss/Kconfig new file mode 100644 index 000000000000..013def7313ef --- /dev/null +++ b/drivers/net/wireless/cnss/Kconfig @@ -0,0 +1,134 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# cnss device configuration +# + +config CNSS + tristate "CNSS driver for wifi module" + select CNSS_UTILS + select CRYPTO + select CRYPTO_HASH + select CRYPTO_BLKCIPHER + help + This module adds support for the CNSS connectivity subsystem used + for wifi devices based on the QCA AR6320 chipset. + This driver also adds support to integrate WLAN module to subsystem + restart framework. + +config CNSS_SDIO + bool "Enable/disable cnss sdio platform driver for wifi module" + depends on CNSS + depends on MMC + help + This module adds support for the CNSS wlan module interfaced + with SDIO bus. + This driver also adds support to integrate WLAN module to subsystem + restart framework, power on WLAN chip and registered the WLAN module + as a SDIO client device. + +config CNSS_PCI + bool "Enable/disable cnss pci platform driver for wifi module" + depends on CNSS + depends on PCI + help + This module adds support for the CNSS wlan module interfaced + with PCIe bus. + This driver also adds support to integrate WLAN module to subsystem + restart framework, power on WLAN chip and registered the WLAN module + as a PCIe client device. + +config CNSS_ASYNC + bool "Enable/disable cnss pci platform driver asynchronous probe" + depends on CNSS_PCI + help + If enabled, CNSS PCI platform driver would do asynchronous probe. + Using asynchronous probe will allow CNSS PCI platform driver to + probe in parallel with other device drivers and will help to + reduce kernel boot time. + +config CNSS_MAC_BUG + bool "Enable/disable 0-4K memory initialization for QCA6174" + depends on CNSS + help + If enabled, 0-4K memory is reserved for QCA6174 to address + a MAC HW bug. MAC would do an invalid pointer fetch based on + the data, that was read from 0 to 4K. So fill it with zero's; + to an address for which PCIe root complex would honor the read + without any errors. + +config CLD_DEBUG + bool "Enable/disable CLD debug features" + help + WLAN CLD driver uses this config to enable certain debug features. + Some of the debug features may affect performance or may compromise + on security. + + Say N, if you are building a release kernel for production use. + Only say Y, if you are building a kernel with debug support. + +config CLD_USB_CORE + tristate "Qualcomm Technologies Inc. Core wlan driver for QCA USB interface" + select WIRELESS_EXT + select WEXT_PRIV + select WEXT_CORE + select WEXT_SPY + select NL80211_TESTMODE + help + This section contains the necessary modules needed to enable the + core WLAN driver for Qualcomm Technologies Inc USB wlan chipset. + Select Y to compile the driver in order to have WLAN functionality + support. + +config CLD_HL_SDIO_CORE + tristate "Qualcomm Technologies Inc. Core wlan driver for QCA SDIO interface" + select WIRELESS_EXT + select WEXT_PRIV + select WEXT_CORE + select WEXT_SPY + select NL80211_TESTMODE + depends on ARCH_QCOM + depends on MMC + +config CLD_LL_CORE + tristate "Qualcomm Technologies Inc. Core wlan driver" + select NL80211_TESTMODE + select WEXT_CORE + select WEXT_PRIV + select WEXT_SPY + select WIRELESS_EXT + help + This section contains the necessary modules needed to enable the + core WLAN driver for Qualcomm Technologies Inc QCA6174 chipset. + Select Y to compile the driver in order to have WLAN functionality + support. + +config CNSS_SECURE_FW + bool "Enable/Disable Memory Allocation for Secure Firmware Feature" + depends on CNSS + help + CLD Driver can use this for holding local copy of firmware + binaries which is used for sha crypto computation. + The Memory Allocation is done only if this Config Parameter is + enabled + +config BUS_AUTO_SUSPEND + bool "Enable/Disable Runtime PM support for PCIe based WLAN Drivers" + depends on CNSS + depends on PCI + help + Runtime Power Management is supported for PCIe based WLAN Drivers. + The features enable cld wlan driver to suspend pcie bus when APPS + is awake based on the driver inactivity with the Firmware. + The Feature uses runtime power management framework from kernel to + track bus access clients and to synchronize the driver activity + during system pm. + This config flag controls the feature per target based. The feature + requires CNSS driver support. + +source "drivers/net/wireless/cnss/logger/Kconfig" + +config WLAN_FEATURE_RX_WAKELOCK + bool "Enable RX wake lock feature" + help + Enable WLAN_FEATURE_HOLD_RX_WAKELOCK which is required to take rx + wakelock when driver receives packets from fw. diff --git a/drivers/net/wireless/cnss/Makefile b/drivers/net/wireless/cnss/Makefile new file mode 100644 index 000000000000..c1ca4c3821e6 --- /dev/null +++ b/drivers/net/wireless/cnss/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for CNSS platform driver +# + +obj-$(CONFIG_CNSS_PCI) += cnss_pci.o +obj-$(CONFIG_CNSS_SDIO) += cnss_sdio.o +obj-$(CONFIG_CNSS) += cnss_common.o +obj-$(CONFIG_CNSS_LOGGER) += logger/ diff --git a/drivers/net/wireless/cnss/cnss_common.c b/drivers/net/wireless/cnss/cnss_common.c new file mode 100644 index 000000000000..dbdd13e0abca --- /dev/null +++ b/drivers/net/wireless/cnss/cnss_common.c @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cnss_common.h" +#include + +#define AR6320_REV1_VERSION 0x5000000 +#define AR6320_REV1_1_VERSION 0x5000001 +#define AR6320_REV1_3_VERSION 0x5000003 +#define AR6320_REV2_1_VERSION 0x5010000 +#define AR6320_REV3_VERSION 0x5020000 +#define AR6320_REV3_2_VERSION 0x5030000 +#define AR900B_DEV_VERSION 0x1000000 +#define QCA9377_REV1_1_VERSION 0x5020001 + +static struct cnss_fw_files FW_FILES_QCA6174_FW_1_1 = { + "qwlan11.bin", "bdwlan11.bin", "otp11.bin", "utf11.bin", + "utfbd11.bin", "epping11.bin", "evicted11.bin"}; +static struct cnss_fw_files FW_FILES_QCA6174_FW_2_0 = { + "qwlan20.bin", "bdwlan20.bin", "otp20.bin", "utf20.bin", + "utfbd20.bin", "epping20.bin", "evicted20.bin"}; +static struct cnss_fw_files FW_FILES_QCA6174_FW_1_3 = { + "qwlan13.bin", "bdwlan13.bin", "otp13.bin", "utf13.bin", + "utfbd13.bin", "epping13.bin", "evicted13.bin"}; +static struct cnss_fw_files FW_FILES_QCA6174_FW_3_0 = { + "qwlan30.bin", "bdwlan30.bin", "otp30.bin", "utf30.bin", + "utfbd30.bin", "epping30.bin", "evicted30.bin"}; +static struct cnss_fw_files FW_FILES_DEFAULT = { + "qwlan.bin", "bdwlan.bin", "otp.bin", "utf.bin", + "utfbd.bin", "epping.bin", "evicted.bin"}; + +enum cnss_dev_bus_type { + CNSS_BUS_NONE = -1, + CNSS_BUS_PCI, + CNSS_BUS_SDIO +}; + +static DEFINE_MUTEX(unsafe_channel_list_lock); +static DEFINE_MUTEX(dfs_nol_info_lock); + +static struct cnss_unsafe_channel_list { + u16 unsafe_ch_count; + u16 unsafe_ch_list[CNSS_MAX_CH_NUM]; +} unsafe_channel_list; + +static struct cnss_dfs_nol_info { + void *dfs_nol_info; + u16 dfs_nol_info_len; +} dfs_nol_info; + +static enum cnss_cc_src cnss_cc_source = CNSS_SOURCE_CORE; + +int cnss_set_wlan_unsafe_channel(u16 *unsafe_ch_list, u16 ch_count) +{ + mutex_lock(&unsafe_channel_list_lock); + if (!unsafe_ch_list || ch_count > CNSS_MAX_CH_NUM) { + mutex_unlock(&unsafe_channel_list_lock); + return -EINVAL; + } + + unsafe_channel_list.unsafe_ch_count = ch_count; + + if (ch_count != 0) { + memcpy((char *)unsafe_channel_list.unsafe_ch_list, + (char *)unsafe_ch_list, ch_count * sizeof(u16)); + } + mutex_unlock(&unsafe_channel_list_lock); + + return 0; +} +EXPORT_SYMBOL(cnss_set_wlan_unsafe_channel); + +int cnss_get_wlan_unsafe_channel(u16 *unsafe_ch_list, + u16 *ch_count, u16 buf_len) +{ + mutex_lock(&unsafe_channel_list_lock); + if (!unsafe_ch_list || !ch_count) { + mutex_unlock(&unsafe_channel_list_lock); + return -EINVAL; + } + + if (buf_len < (unsafe_channel_list.unsafe_ch_count * sizeof(u16))) { + mutex_unlock(&unsafe_channel_list_lock); + return -ENOMEM; + } + + *ch_count = unsafe_channel_list.unsafe_ch_count; + memcpy((char *)unsafe_ch_list, + (char *)unsafe_channel_list.unsafe_ch_list, + unsafe_channel_list.unsafe_ch_count * sizeof(u16)); + mutex_unlock(&unsafe_channel_list_lock); + + return 0; +} +EXPORT_SYMBOL(cnss_get_wlan_unsafe_channel); + +int cnss_wlan_set_dfs_nol(const void *info, u16 info_len) +{ + void *temp; + struct cnss_dfs_nol_info *dfs_info; + + mutex_lock(&dfs_nol_info_lock); + if (!info || !info_len) { + mutex_unlock(&dfs_nol_info_lock); + return -EINVAL; + } + + temp = kmemdup(info, info_len, GFP_KERNEL); + if (!temp) { + mutex_unlock(&dfs_nol_info_lock); + return -ENOMEM; + } + + dfs_info = &dfs_nol_info; + kfree(dfs_info->dfs_nol_info); + + dfs_info->dfs_nol_info = temp; + dfs_info->dfs_nol_info_len = info_len; + mutex_unlock(&dfs_nol_info_lock); + + return 0; +} +EXPORT_SYMBOL(cnss_wlan_set_dfs_nol); + +int cnss_wlan_get_dfs_nol(void *info, u16 info_len) +{ + int len; + struct cnss_dfs_nol_info *dfs_info; + + mutex_lock(&dfs_nol_info_lock); + if (!info || !info_len) { + mutex_unlock(&dfs_nol_info_lock); + return -EINVAL; + } + + dfs_info = &dfs_nol_info; + + if (!dfs_info->dfs_nol_info || dfs_info->dfs_nol_info_len == 0) { + mutex_unlock(&dfs_nol_info_lock); + return -ENOENT; + } + + len = min(info_len, dfs_info->dfs_nol_info_len); + + memcpy(info, dfs_info->dfs_nol_info, len); + mutex_unlock(&dfs_nol_info_lock); + + return len; +} +EXPORT_SYMBOL(cnss_wlan_get_dfs_nol); + +void cnss_init_work(struct work_struct *work, work_func_t func) +{ + INIT_WORK(work, func); +} +EXPORT_SYMBOL(cnss_init_work); + +void cnss_flush_work(void *work) +{ + struct work_struct *cnss_work = work; + + cancel_work_sync(cnss_work); +} +EXPORT_SYMBOL(cnss_flush_work); + +void cnss_flush_delayed_work(void *dwork) +{ + struct delayed_work *cnss_dwork = dwork; + + cancel_delayed_work_sync(cnss_dwork); +} +EXPORT_SYMBOL(cnss_flush_delayed_work); + +void cnss_pm_wake_lock_init(struct wakeup_source **ws, const char *name) +{ + *ws = wakeup_source_register(NULL, name); +} +EXPORT_SYMBOL(cnss_pm_wake_lock_init); + +void cnss_pm_wake_lock(struct wakeup_source *ws) +{ + __pm_stay_awake(ws); +} +EXPORT_SYMBOL(cnss_pm_wake_lock); + +void cnss_pm_wake_lock_timeout(struct wakeup_source *ws, ulong msec) +{ + __pm_wakeup_event(ws, msec); +} +EXPORT_SYMBOL(cnss_pm_wake_lock_timeout); + +void cnss_pm_wake_lock_release(struct wakeup_source *ws) +{ + __pm_relax(ws); +} +EXPORT_SYMBOL(cnss_pm_wake_lock_release); + +void cnss_pm_wake_lock_destroy(struct wakeup_source *ws) +{ + wakeup_source_unregister(ws); +} +EXPORT_SYMBOL(cnss_pm_wake_lock_destroy); + +void cnss_get_monotonic_boottime(struct timespec64 *ts) +{ + ktime_get_boottime_ts64(ts); +} +EXPORT_SYMBOL(cnss_get_monotonic_boottime); + +void cnss_get_boottime(struct timespec *ts) +{ + ktime_get_ts(ts); +} +EXPORT_SYMBOL(cnss_get_boottime); + +void cnss_init_delayed_work(struct delayed_work *work, work_func_t func) +{ + INIT_DELAYED_WORK(work, func); +} +EXPORT_SYMBOL(cnss_init_delayed_work); + +int cnss_vendor_cmd_reply(struct sk_buff *skb) +{ + return cfg80211_vendor_cmd_reply(skb); +} +EXPORT_SYMBOL(cnss_vendor_cmd_reply); + +int cnss_set_cpus_allowed_ptr(struct task_struct *task, ulong cpu) +{ + return set_cpus_allowed_ptr(task, cpumask_of(cpu)); +} +EXPORT_SYMBOL(cnss_set_cpus_allowed_ptr); + +/* wlan prop driver cannot invoke show_stack + * function directly, so to invoke this function it + * call wcnss_dump_stack function + */ +void cnss_dump_stack(struct task_struct *task) +{ + show_stack(task, NULL); +} +EXPORT_SYMBOL(cnss_dump_stack); + +struct cnss_dev_platform_ops *cnss_get_platform_ops(struct device *dev) +{ + if (!dev) + return NULL; + else + return dev->platform_data; +} + +int cnss_common_request_bus_bandwidth(struct device *dev, int bandwidth) +{ + struct cnss_dev_platform_ops *pf_ops = cnss_get_platform_ops(dev); + + if (pf_ops && pf_ops->request_bus_bandwidth) + return pf_ops->request_bus_bandwidth(bandwidth); + else + return -EINVAL; +} +EXPORT_SYMBOL(cnss_common_request_bus_bandwidth); + +void *cnss_common_get_virt_ramdump_mem(struct device *dev, unsigned long *size) +{ + struct cnss_dev_platform_ops *pf_ops = cnss_get_platform_ops(dev); + + if (pf_ops && pf_ops->get_virt_ramdump_mem) + return pf_ops->get_virt_ramdump_mem(size); + else + return NULL; +} +EXPORT_SYMBOL(cnss_common_get_virt_ramdump_mem); + +void cnss_common_device_self_recovery(struct device *dev) +{ + struct cnss_dev_platform_ops *pf_ops = cnss_get_platform_ops(dev); + + if (pf_ops && pf_ops->device_self_recovery) + pf_ops->device_self_recovery(); +} +EXPORT_SYMBOL(cnss_common_device_self_recovery); + +void cnss_common_schedule_recovery_work(struct device *dev) +{ + struct cnss_dev_platform_ops *pf_ops = cnss_get_platform_ops(dev); + + if (pf_ops && pf_ops->schedule_recovery_work) + pf_ops->schedule_recovery_work(); +} +EXPORT_SYMBOL(cnss_common_schedule_recovery_work); + +void cnss_common_device_crashed(struct device *dev) +{ + struct cnss_dev_platform_ops *pf_ops = cnss_get_platform_ops(dev); + + if (pf_ops && pf_ops->device_crashed) + pf_ops->device_crashed(); +} +EXPORT_SYMBOL(cnss_common_device_crashed); + +u8 *cnss_common_get_wlan_mac_address(struct device *dev, u32 *num) +{ + struct cnss_dev_platform_ops *pf_ops = cnss_get_platform_ops(dev); + + if (pf_ops && pf_ops->get_wlan_mac_address) + return pf_ops->get_wlan_mac_address(num); + else + return NULL; +} +EXPORT_SYMBOL(cnss_common_get_wlan_mac_address); + +int cnss_common_set_wlan_mac_address(struct device *dev, const u8 *in, u32 len) +{ + struct cnss_dev_platform_ops *pf_ops = cnss_get_platform_ops(dev); + + if (pf_ops && pf_ops->set_wlan_mac_address) + return pf_ops->set_wlan_mac_address(in, len); + else + return -EINVAL; +} +EXPORT_SYMBOL(cnss_common_set_wlan_mac_address); + +int cnss_power_up(struct device *dev) +{ + struct cnss_dev_platform_ops *pf_ops = cnss_get_platform_ops(dev); + + if (pf_ops && pf_ops->power_up) + return pf_ops->power_up(dev); + else + return -EINVAL; +} +EXPORT_SYMBOL(cnss_power_up); + +int cnss_power_down(struct device *dev) +{ + struct cnss_dev_platform_ops *pf_ops = cnss_get_platform_ops(dev); + + if (pf_ops && pf_ops->power_down) + return pf_ops->power_down(dev); + else + return -EINVAL; +} +EXPORT_SYMBOL(cnss_power_down); + +void cnss_get_qca9377_fw_files(struct cnss_fw_files *pfw_files, + u32 size, u32 tufello_dual_fw) +{ + if (tufello_dual_fw) + memcpy(pfw_files, &FW_FILES_DEFAULT, sizeof(*pfw_files)); + else + memcpy(pfw_files, &FW_FILES_QCA6174_FW_3_0, sizeof(*pfw_files)); +} +EXPORT_SYMBOL(cnss_get_qca9377_fw_files); + +int cnss_get_fw_files_for_target(struct cnss_fw_files *pfw_files, + u32 target_type, u32 target_version) +{ + if (!pfw_files) + return -ENODEV; + + switch (target_version) { + case AR6320_REV1_VERSION: + case AR6320_REV1_1_VERSION: + memcpy(pfw_files, &FW_FILES_QCA6174_FW_1_1, sizeof(*pfw_files)); + break; + case AR6320_REV1_3_VERSION: + memcpy(pfw_files, &FW_FILES_QCA6174_FW_1_3, sizeof(*pfw_files)); + break; + case AR6320_REV2_1_VERSION: + memcpy(pfw_files, &FW_FILES_QCA6174_FW_2_0, sizeof(*pfw_files)); + break; + case AR6320_REV3_VERSION: + case AR6320_REV3_2_VERSION: + memcpy(pfw_files, &FW_FILES_QCA6174_FW_3_0, sizeof(*pfw_files)); + break; + default: + memcpy(pfw_files, &FW_FILES_DEFAULT, sizeof(*pfw_files)); + pr_err("%s default version 0x%X 0x%X\n", __func__, + target_type, target_version); + break; + } + return 0; +} +EXPORT_SYMBOL(cnss_get_fw_files_for_target); + +void cnss_set_cc_source(enum cnss_cc_src cc_source) +{ + cnss_cc_source = cc_source; +} +EXPORT_SYMBOL(cnss_set_cc_source); + +enum cnss_cc_src cnss_get_cc_source(void) +{ + return cnss_cc_source; +} +EXPORT_SYMBOL(cnss_get_cc_source); + +const char *cnss_wlan_get_evicted_data_file(void) +{ + return FW_FILES_QCA6174_FW_3_0.evicted_data; +} + +int cnss_common_register_tsf_captured_handler(struct device *dev, + irq_handler_t handler, void *ctx) +{ + struct cnss_dev_platform_ops *pf_ops = cnss_get_platform_ops(dev); + + if (pf_ops && pf_ops->register_tsf_captured_handler) + return pf_ops->register_tsf_captured_handler(handler, ctx); + else + return -EINVAL; +} +EXPORT_SYMBOL(cnss_common_register_tsf_captured_handler); + +int cnss_common_unregister_tsf_captured_handler(struct device *dev, + void *ctx) +{ + struct cnss_dev_platform_ops *pf_ops = cnss_get_platform_ops(dev); + + if (pf_ops && pf_ops->unregister_tsf_captured_handler) + return pf_ops->unregister_tsf_captured_handler(ctx); + else + return -EINVAL; +} +EXPORT_SYMBOL(cnss_common_unregister_tsf_captured_handler); diff --git a/drivers/net/wireless/cnss/cnss_common.h b/drivers/net/wireless/cnss/cnss_common.h new file mode 100644 index 000000000000..fa5fc71fb6b8 --- /dev/null +++ b/drivers/net/wireless/cnss/cnss_common.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ + +#ifndef _NET_CNSS_COMMON_H_ +#define _NET_CNSS_COMMON_H_ + +/* max 20mhz channel count */ +#define CNSS_MAX_CH_NUM 45 + +struct cnss_cap_tsf_info { + int irq_num; + void *context; + irq_handler_t irq_handler; +}; + +struct cnss_dev_platform_ops { + int (*request_bus_bandwidth)(int bandwidth); + void* (*get_virt_ramdump_mem)(unsigned long *size); + void (*device_self_recovery)(void); + void (*schedule_recovery_work)(void); + void (*device_crashed)(void); + u8 * (*get_wlan_mac_address)(u32 *num); + int (*set_wlan_mac_address)(const u8 *in, u32 len); + int (*power_up)(struct device *dev); + int (*power_down)(struct device *dev); + int (*register_tsf_captured_handler)(irq_handler_t handler, + void *adapter); + int (*unregister_tsf_captured_handler)(void *adapter); +}; + +int cnss_pci_request_bus_bandwidth(int bandwidth); +int cnss_sdio_request_bus_bandwidth(int bandwidth); + +void cnss_sdio_device_crashed(void); +void cnss_pci_device_crashed(void); + +void cnss_pci_device_self_recovery(void); +void cnss_sdio_device_self_recovery(void); + +void *cnss_pci_get_virt_ramdump_mem(unsigned long *size); +void *cnss_sdio_get_virt_ramdump_mem(unsigned long *size); + +void cnss_sdio_schedule_recovery_work(void); +void cnss_pci_schedule_recovery_work(void); + +int cnss_pcie_set_wlan_mac_address(const u8 *in, u32 len); +int cnss_sdio_set_wlan_mac_address(const u8 *in, u32 len); + +u8 *cnss_pci_get_wlan_mac_address(u32 *num); +u8 *cnss_sdio_get_wlan_mac_address(u32 *num); +int cnss_sdio_power_up(struct device *dev); +int cnss_sdio_power_down(struct device *dev); +int cnss_pcie_power_up(struct device *dev); +int cnss_pcie_power_down(struct device *dev); +const char *cnss_wlan_get_evicted_data_file(void); +#endif /* _NET_CNSS_COMMON_H_ */ diff --git a/drivers/net/wireless/cnss/cnss_pci.c b/drivers/net/wireless/cnss/cnss_pci.c new file mode 100644 index 000000000000..bef8ef6ee5b0 --- /dev/null +++ b/drivers/net/wireless/cnss/cnss_pci.c @@ -0,0 +1,3915 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2013-2021, The Linux Foundation. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cnss_common.h" + +#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC +#include +#endif + +#define subsys_to_drv(d) container_of(d, struct cnss_data, subsys_desc) + +#define WLAN_BOOTSTRAP_HIGH 1 +#define WLAN_BOOTSTRAP_LOW 0 +#define CNSS_DUMP_FORMAT_VER 0x11 +#define CNSS_DUMP_MAGIC_VER_V2 0x42445953 +#define CNSS_DUMP_NAME "CNSS_WLAN" + +#define QCA6174_VENDOR_ID (0x168C) +#define QCA6174_DEVICE_ID (0x003E) +#define BEELINER_DEVICE_ID (0x0040) +#define QCA6174_REV_ID_OFFSET (0x08) +#define QCA6174_FW_1_1 (0x11) +#define QCA6174_FW_1_3 (0x13) +#define QCA6174_FW_2_0 (0x20) +#define QCA6174_FW_3_0 (0x30) +#define QCA6174_FW_3_2 (0x32) +#define BEELINER_FW (0x00) + +#define QCA6180_VENDOR_ID (0x168C) +#define QCA6180_DEVICE_ID (0x0041) +#define QCA6180_REV_ID_OFFSET (0x08) + +#define WLAN_EN_VREG_NAME "vdd-wlan-en" +#define WLAN_VREG_NAME "vdd-wlan" +#define WLAN_VREG_IO_NAME "vdd-wlan-io" +#define WLAN_VREG_XTAL_NAME "vdd-wlan-xtal" +#define WLAN_VREG_XTAL_AON_NAME "vdd-wlan-xtal-aon" +#define WLAN_VREG_CORE_NAME "vdd-wlan-core" +#define WLAN_VREG_SP2T_NAME "vdd-wlan-sp2t" +#define WLAN_SWREG_NAME "wlan-soc-swreg" +#define WLAN_ANT_SWITCH_NAME "wlan-ant-switch" +#define WLAN_EN_GPIO_NAME "wlan-en-gpio" +#define WLAN_BOOTSTRAP_GPIO_NAME "wlan-bootstrap-gpio" +#define PM_OPTIONS 0 +#define PM_OPTIONS_SUSPEND_LINK_DOWN \ + (MSM_PCIE_CONFIG_NO_CFG_RESTORE | MSM_PCIE_CONFIG_LINKDOWN) +#define PM_OPTIONS_RESUME_LINK_DOWN \ + (MSM_PCIE_CONFIG_NO_CFG_RESTORE) + +#define SOC_SWREG_VOLT_MAX 1200000 +#define SOC_SWREG_VOLT_MIN 1200000 +#define WLAN_ANT_SWITCH_VOLT_MAX 2700000 +#define WLAN_ANT_SWITCH_VOLT_MIN 2700000 +#define WLAN_ANT_SWITCH_CURR 20000 +#define WLAN_VREG_IO_MAX 1800000 +#define WLAN_VREG_IO_MIN 1800000 +#define WLAN_VREG_XTAL_MAX 1800000 +#define WLAN_VREG_XTAL_MIN 1800000 +#define WLAN_VREG_CORE_MAX 1300000 +#define WLAN_VREG_CORE_MIN 1300000 +#define WLAN_VREG_SP2T_MAX 2700000 +#define WLAN_VREG_SP2T_MIN 2700000 + +#define POWER_ON_DELAY 2 +#define WLAN_VREG_IO_DELAY_MIN 100 +#define WLAN_VREG_IO_DELAY_MAX 1000 +#define WLAN_ENABLE_DELAY 10 +#define PCIE_SWITCH_DELAY 20 +#define WLAN_RECOVERY_DELAY 1 +#define PCIE_ENABLE_DELAY 100 +#define WLAN_BOOTSTRAP_DELAY 10 +#define EVICT_BIN_MAX_SIZE (512 * 1024) + +static DEFINE_SPINLOCK(pci_link_down_lock); + +#define FW_NAME_FIXED_LEN (6) +#define MAX_NUM_OF_SEGMENTS (16) +#define MAX_INDEX_FILE_SIZE (512) +#define FW_FILENAME_LENGTH (13) +#define TYPE_LENGTH (4) +#define PER_FILE_DATA (21) +#define MAX_IMAGE_SIZE (2 * 1024 * 1024) +#define FW_IMAGE_FTM (0x01) +#define FW_IMAGE_MISSION (0x02) +#define FW_IMAGE_BDATA (0x03) +#define FW_IMAGE_PRINT (0x04) + +#define SEG_METADATA (0x01) +#define SEG_NON_PAGED (0x02) +#define SEG_LOCKED_PAGE (0x03) +#define SEG_UNLOCKED_PAGE (0x04) +#define SEG_NON_SECURE_DATA (0x05) + +#define BMI_TEST_SETUP (0x09) + +struct cnss_wlan_gpio_info { + char *name; + u32 num; + bool state; + bool init; + bool prop; +}; + +struct cnss_wlan_vreg_info { + struct regulator *wlan_en_reg; + struct regulator *wlan_reg; + struct regulator *soc_swreg; + struct regulator *ant_switch; + struct regulator *wlan_reg_io; + struct regulator *wlan_reg_xtal; + struct regulator *wlan_reg_xtal_aon; + struct regulator *wlan_reg_core; + struct regulator *wlan_reg_sp2t; + bool state; +}; + +struct segment_memory { + dma_addr_t dma_region; + void *cpu_region; + u32 size; +}; + +/* FW image descriptor lists */ +struct image_desc_hdr { + u8 image_id; + u8 reserved[3]; + u32 segments_cnt; +}; + +struct segment_desc { + u8 segment_id; + u8 segment_idx; + u8 flags[2]; + u32 addr_count; + u32 addr_low; + u32 addr_high; +}; + +struct region_desc { + u32 addr_low; + u32 addr_high; + u32 size; + u32 reserved; +}; + +struct index_file { + u32 type; + u32 segment_idx; + u8 file_name[13]; +}; + +struct cnss_dual_wifi { + bool is_dual_wifi_enabled; +}; + +/** + * struct wlan_mac_addr - Structure to hold WLAN MAC Address + * @mac_addr: MAC address + */ +#define MAX_NO_OF_MAC_ADDR 4 +struct cnss_wlan_mac_addr { + u8 mac_addr[MAX_NO_OF_MAC_ADDR][ETH_ALEN]; + u32 no_of_mac_addr_set; +}; + +/* device_info is expected to be fully populated after cnss_config is invoked. + * The function pointer callbacks are expected to be non null as well. + */ +static struct cnss_data { + struct platform_device *pldev; + struct subsys_device *subsys; + struct subsys_desc subsysdesc; + struct cnss_wlan_mac_addr wlan_mac_addr; + bool is_wlan_mac_set; + bool ramdump_dynamic; + struct ramdump_device *ramdump_dev; + unsigned long ramdump_size; + void *ramdump_addr; + phys_addr_t ramdump_phys; + struct msm_dump_data dump_data; + struct cnss_wlan_driver *driver; + struct pci_dev *pdev; + const struct pci_device_id *id; + struct dma_iommu_mapping *smmu_mapping; + bool smmu_s1_bypass; + dma_addr_t smmu_iova_start; + size_t smmu_iova_len; + dma_addr_t smmu_iova_ipa_start; + size_t smmu_iova_ipa_len; + struct cnss_wlan_vreg_info vreg_info; + bool wlan_en_vreg_support; + struct cnss_wlan_gpio_info gpio_info; + bool pcie_link_state; + bool pcie_link_down_ind; + bool pci_register_again; + bool notify_modem_status; + struct pci_saved_state *saved_state; + u16 revision_id; + bool recovery_in_progress; + atomic_t fw_available; + struct codeswap_codeseg_info *cnss_seg_info; + /* Virtual Address of the DMA page */ + void *codeseg_cpuaddr[CODESWAP_MAX_CODESEGS]; + struct cnss_fw_files fw_files; + struct pm_qos_request qos_request; + void *modem_notify_handler; + int modem_current_status; + struct msm_bus_scale_pdata *bus_scale_table; + u32 bus_client; + int current_bandwidth_vote; + void *subsys_handle; + struct esoc_desc *esoc_desc; + struct cnss_platform_cap cap; + struct msm_pcie_register_event event_reg; + struct wakeup_source *ws; + u32 recovery_count; + enum cnss_driver_status driver_status; +#ifdef CONFIG_CNSS_SECURE_FW + void *fw_mem; +#endif + u32 device_id; + int fw_image_setup; + u32 bmi_test; + void *fw_cpu; + dma_addr_t fw_dma; + u32 fw_dma_size; + u32 fw_seg_count; + struct segment_memory fw_seg_mem[MAX_NUM_OF_SEGMENTS]; + /* Firmware setup complete lock */ + struct mutex fw_setup_stat_lock; + void *bdata_cpu; + dma_addr_t bdata_dma; + u32 bdata_dma_size; + u32 bdata_seg_count; + struct segment_memory bdata_seg_mem[MAX_NUM_OF_SEGMENTS]; + int wlan_bootstrap_gpio; + atomic_t auto_suspended; + bool monitor_wake_intr; + struct cnss_dual_wifi dual_wifi_info; + struct cnss_dev_platform_ops platform_ops; +} *penv; + +static unsigned int pcie_link_down_panic; +/* avoid using module_param for msm-5.4 and later, + * will implement this by debugfs after bring up. + * + * module_param(pcie_link_down_panic, uint, 0600); + * MODULE_PARM_DESC(pcie_link_down_panic, + * "Trigger kernel panic when PCIe link down is detected"); + */ + +static void cnss_put_wlan_enable_gpio(void) +{ + struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info; + struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; + + if (penv->wlan_en_vreg_support) + regulator_put(vreg_info->wlan_en_reg); + else + gpio_free(gpio_info->num); +} + +static int cnss_wlan_vreg_on(struct cnss_wlan_vreg_info *vreg_info) +{ + int ret; + + if (vreg_info->wlan_reg_core) { + ret = regulator_enable(vreg_info->wlan_reg_core); + if (ret) { + pr_err("%s: regulator enable failed for wlan_reg_core\n", + __func__); + goto error_enable_reg_core; + } + } + + if (vreg_info->wlan_reg_io) { + ret = regulator_enable(vreg_info->wlan_reg_io); + if (ret) { + pr_err("%s: regulator enable failed for wlan_reg_io\n", + __func__); + goto error_enable_reg_io; + } + + usleep_range(WLAN_VREG_IO_DELAY_MIN, WLAN_VREG_IO_DELAY_MAX); + } + + if (vreg_info->wlan_reg_xtal_aon) { + ret = regulator_enable(vreg_info->wlan_reg_xtal_aon); + if (ret) { + pr_err("%s: wlan_reg_xtal_aon enable failed\n", + __func__); + goto error_enable_reg_xtal_aon; + } + } + + if (vreg_info->wlan_reg_xtal) { + ret = regulator_enable(vreg_info->wlan_reg_xtal); + if (ret) { + pr_err("%s: regulator enable failed for wlan_reg_xtal\n", + __func__); + goto error_enable_reg_xtal; + } + } + + ret = regulator_enable(vreg_info->wlan_reg); + if (ret) { + pr_err("%s: regulator enable failed for WLAN power\n", + __func__); + goto error_enable; + } + + if (vreg_info->wlan_reg_sp2t) { + ret = regulator_enable(vreg_info->wlan_reg_sp2t); + if (ret) { + pr_err("%s: regulator enable failed for wlan_reg_sp2t\n", + __func__); + goto error_enable_reg_sp2t; + } + } + + if (vreg_info->ant_switch) { + ret = regulator_enable(vreg_info->ant_switch); + if (ret) { + pr_err("%s: regulator enable failed for ant_switch\n", + __func__); + goto error_enable_ant_switch; + } + } + + if (vreg_info->soc_swreg) { + ret = regulator_enable(vreg_info->soc_swreg); + if (ret) { + pr_err("%s: regulator enable failed for external soc-swreg\n", + __func__); + goto error_enable_soc_swreg; + } + } + + return ret; + +error_enable_soc_swreg: + if (vreg_info->ant_switch) + regulator_disable(vreg_info->ant_switch); +error_enable_ant_switch: + if (vreg_info->wlan_reg_sp2t) + regulator_disable(vreg_info->wlan_reg_sp2t); +error_enable_reg_sp2t: + regulator_disable(vreg_info->wlan_reg); +error_enable: + if (vreg_info->wlan_reg_xtal) + regulator_disable(vreg_info->wlan_reg_xtal); +error_enable_reg_xtal: + if (vreg_info->wlan_reg_xtal_aon) + regulator_disable(vreg_info->wlan_reg_xtal_aon); +error_enable_reg_xtal_aon: + if (vreg_info->wlan_reg_io) + regulator_disable(vreg_info->wlan_reg_io); +error_enable_reg_io: + if (vreg_info->wlan_reg_core) + regulator_disable(vreg_info->wlan_reg_core); +error_enable_reg_core: + return ret; +} + +static int cnss_wlan_vreg_off(struct cnss_wlan_vreg_info *vreg_info) +{ + int ret; + + if (vreg_info->soc_swreg) { + ret = regulator_disable(vreg_info->soc_swreg); + if (ret) { + pr_err("%s: regulator disable failed for external soc-swreg\n", + __func__); + goto error_disable; + } + } + + if (vreg_info->ant_switch) { + ret = regulator_disable(vreg_info->ant_switch); + if (ret) { + pr_err("%s: regulator disable failed for ant_switch\n", + __func__); + goto error_disable; + } + } + + if (vreg_info->wlan_reg_sp2t) { + ret = regulator_disable(vreg_info->wlan_reg_sp2t); + if (ret) { + pr_err("%s: regulator disable failed for wlan_reg_sp2t\n", + __func__); + goto error_disable; + } + } + + ret = regulator_disable(vreg_info->wlan_reg); + if (ret) { + pr_err("%s: regulator disable failed for WLAN power\n", + __func__); + goto error_disable; + } + + if (vreg_info->wlan_reg_xtal) { + ret = regulator_disable(vreg_info->wlan_reg_xtal); + if (ret) { + pr_err("%s: regulator disable failed for wlan_reg_xtal\n", + __func__); + goto error_disable; + } + } + + if (vreg_info->wlan_reg_xtal_aon) { + ret = regulator_disable(vreg_info->wlan_reg_xtal_aon); + if (ret) { + pr_err("%s: wlan_reg_xtal_aon disable failed\n", + __func__); + goto error_disable; + } + } + + if (vreg_info->wlan_reg_io) { + ret = regulator_disable(vreg_info->wlan_reg_io); + if (ret) { + pr_err("%s: regulator disable failed for wlan_reg_io\n", + __func__); + goto error_disable; + } + } + + if (vreg_info->wlan_reg_core) { + ret = regulator_disable(vreg_info->wlan_reg_core); + if (ret) { + pr_err("%s: regulator disable failed for wlan_reg_core\n", + __func__); + goto error_disable; + } + } + +error_disable: + return ret; +} + +static int cnss_wlan_vreg_set(struct cnss_wlan_vreg_info *vreg_info, bool state) +{ + int ret = 0; + + if (vreg_info->state == state) { + pr_debug("Already wlan vreg state is %s\n", + state ? "enabled" : "disabled"); + goto out; + } + + if (state) + ret = cnss_wlan_vreg_on(vreg_info); + else + ret = cnss_wlan_vreg_off(vreg_info); + + if (ret) + goto out; + + pr_debug("%s: wlan vreg is now %s\n", __func__, + state ? "enabled" : "disabled"); + vreg_info->state = state; + +out: + return ret; +} + +static int cnss_wlan_gpio_init(struct cnss_wlan_gpio_info *info) +{ + int ret = 0; + + ret = gpio_request(info->num, info->name); + + if (ret) { + pr_err("can't get gpio %s ret %d\n", info->name, ret); + goto err_gpio_req; + } + + ret = gpio_direction_output(info->num, info->init); + + if (ret) { + pr_err("can't set gpio direction %s ret %d\n", info->name, ret); + goto err_gpio_dir; + } + info->state = info->init; + + return ret; + +err_gpio_dir: + gpio_free(info->num); + +err_gpio_req: + + return ret; +} + +static int cnss_wlan_bootstrap_gpio_init(void) +{ + int ret = 0; + + ret = gpio_request(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_GPIO_NAME); + if (ret) { + pr_err("%s: Can't get GPIO %s, ret = %d\n", + __func__, WLAN_BOOTSTRAP_GPIO_NAME, ret); + goto out; + } + + ret = gpio_direction_output(penv->wlan_bootstrap_gpio, + WLAN_BOOTSTRAP_HIGH); + if (ret) { + pr_err("%s: Can't set GPIO %s direction, ret = %d\n", + __func__, WLAN_BOOTSTRAP_GPIO_NAME, ret); + gpio_free(penv->wlan_bootstrap_gpio); + goto out; + } + + msleep(WLAN_BOOTSTRAP_DELAY); +out: + return ret; +} + +static void cnss_wlan_gpio_set(struct cnss_wlan_gpio_info *info, bool state) +{ + if (!info->prop) + return; + + if (info->state == state) { + pr_debug("Already %s gpio is %s\n", + info->name, state ? "high" : "low"); + return; + } + + gpio_set_value(info->num, state); + info->state = state; + + pr_debug("%s: %s gpio is now %s\n", __func__, + info->name, info->state ? "enabled" : "disabled"); +} + +static int cnss_configure_wlan_en_gpio(bool state) +{ + int ret = 0; + struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info; + struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; + + if (penv->wlan_en_vreg_support) { + if (state) + ret = regulator_enable(vreg_info->wlan_en_reg); + else + ret = regulator_disable(vreg_info->wlan_en_reg); + } else { + cnss_wlan_gpio_set(gpio_info, state); + } + + msleep(WLAN_ENABLE_DELAY); + return ret; +} + +static void cnss_disable_xtal_ldo(struct platform_device *pdev) +{ + struct cnss_wlan_vreg_info *info = &penv->vreg_info; + + if (info->wlan_reg_xtal) { + regulator_disable(info->wlan_reg_xtal); + regulator_put(info->wlan_reg_xtal); + } + + if (info->wlan_reg_xtal_aon) { + regulator_disable(info->wlan_reg_xtal_aon); + regulator_put(info->wlan_reg_xtal_aon); + } +} + +static int cnss_enable_xtal_ldo(struct platform_device *pdev) +{ + int ret = 0; + struct cnss_wlan_vreg_info *info = &penv->vreg_info; + + if (!of_get_property(pdev->dev.of_node, + WLAN_VREG_XTAL_AON_NAME "-supply", NULL)) + goto enable_xtal; + + info->wlan_reg_xtal_aon = regulator_get(&pdev->dev, + WLAN_VREG_XTAL_AON_NAME); + if (IS_ERR(info->wlan_reg_xtal_aon)) { + ret = PTR_ERR(info->wlan_reg_xtal_aon); + pr_err("%s: XTAL AON Regulator get failed err:%d\n", __func__, + ret); + return ret; + } + + ret = regulator_enable(info->wlan_reg_xtal_aon); + if (ret) { + pr_err("%s: VREG_XTAL_ON enable failed\n", __func__); + goto end; + } + +enable_xtal: + + if (!of_get_property(pdev->dev.of_node, + WLAN_VREG_XTAL_NAME "-supply", NULL)) + goto out_disable_xtal_aon; + + info->wlan_reg_xtal = regulator_get(&pdev->dev, WLAN_VREG_XTAL_NAME); + + if (IS_ERR(info->wlan_reg_xtal)) { + ret = PTR_ERR(info->wlan_reg_xtal); + pr_err("%s XTAL Regulator get failed err:%d\n", __func__, ret); + goto out_disable_xtal_aon; + } + + ret = regulator_set_voltage(info->wlan_reg_xtal, WLAN_VREG_XTAL_MIN, + WLAN_VREG_XTAL_MAX); + if (ret) { + pr_err("%s: Set wlan_vreg_xtal failed\n", __func__); + goto out_put_xtal; + } + + ret = regulator_enable(info->wlan_reg_xtal); + if (ret) { + pr_err("%s: Enable wlan_vreg_xtal failed\n", __func__); + goto out_put_xtal; + } + + return 0; + +out_put_xtal: + if (info->wlan_reg_xtal) + regulator_put(info->wlan_reg_xtal); + +out_disable_xtal_aon: + if (info->wlan_reg_xtal_aon) + regulator_disable(info->wlan_reg_xtal_aon); + +end: + if (info->wlan_reg_xtal_aon) + regulator_put(info->wlan_reg_xtal_aon); + + return ret; +} + +static int cnss_get_wlan_enable_gpio(struct cnss_wlan_gpio_info *gpio_info, + struct platform_device *pdev) +{ + int ret = 0; + struct device *dev = &pdev->dev; + + if (!of_find_property(dev->of_node, gpio_info->name, NULL)) { + gpio_info->prop = false; + return -ENODEV; + } + + gpio_info->prop = true; + ret = of_get_named_gpio(dev->of_node, gpio_info->name, 0); + if (ret >= 0) { + gpio_info->num = ret; + } else { + if (ret == -EPROBE_DEFER) + pr_debug("get WLAN_EN GPIO probe defer\n"); + else + pr_err("can't get gpio %s ret %d\n", + gpio_info->name, ret); + } + + ret = cnss_wlan_gpio_init(gpio_info); + if (ret) + pr_err("gpio init failed\n"); + + return ret; +} + +static int cnss_get_wlan_bootstrap_gpio(struct platform_device *pdev) +{ + int ret = 0; + struct device_node *node = (&pdev->dev)->of_node; + + if (!of_find_property(node, WLAN_BOOTSTRAP_GPIO_NAME, NULL)) + return ret; + + penv->wlan_bootstrap_gpio = + of_get_named_gpio(node, WLAN_BOOTSTRAP_GPIO_NAME, 0); + if (penv->wlan_bootstrap_gpio > 0) { + ret = cnss_wlan_bootstrap_gpio_init(); + } else { + ret = penv->wlan_bootstrap_gpio; + pr_err("%s: Can't get GPIO %s, ret = %d\n", + __func__, WLAN_BOOTSTRAP_GPIO_NAME, ret); + } + + return ret; +} + +static int cnss_wlan_get_resources(struct platform_device *pdev) +{ + int ret = 0; + struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info; + struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; + struct device_node *node = pdev->dev.of_node; + + if (of_get_property(node, WLAN_VREG_CORE_NAME "-supply", NULL)) { + vreg_info->wlan_reg_core = regulator_get(&pdev->dev, + WLAN_VREG_CORE_NAME); + if (IS_ERR(vreg_info->wlan_reg_core)) { + ret = PTR_ERR(vreg_info->wlan_reg_core); + + if (ret == -EPROBE_DEFER) { + pr_err("%s: wlan_reg_core probe deferred\n", + __func__); + } else { + pr_err("%s: Get wlan_reg_core failed\n", + __func__); + } + goto err_reg_core_get; + } + + ret = regulator_set_voltage(vreg_info->wlan_reg_core, + WLAN_VREG_CORE_MIN, + WLAN_VREG_CORE_MAX); + if (ret) { + pr_err("%s: Set wlan_reg_core failed\n", __func__); + goto err_reg_core_set; + } + + ret = regulator_enable(vreg_info->wlan_reg_core); + if (ret) { + pr_err("%s: Enable wlan_reg_core failed\n", __func__); + goto err_reg_core_enable; + } + } + + if (of_get_property(node, WLAN_VREG_IO_NAME "-supply", NULL)) { + vreg_info->wlan_reg_io = regulator_get(&pdev->dev, + WLAN_VREG_IO_NAME); + if (!IS_ERR(vreg_info->wlan_reg_io)) { + ret = regulator_set_voltage(vreg_info->wlan_reg_io, + WLAN_VREG_IO_MIN, + WLAN_VREG_IO_MAX); + if (ret) { + pr_err("%s: Set wlan_vreg_io failed\n", + __func__); + goto err_reg_io_set; + } + + ret = regulator_enable(vreg_info->wlan_reg_io); + if (ret) { + pr_err("%s: Enable wlan_vreg_io failed\n", + __func__); + goto err_reg_io_enable; + } + + usleep_range(WLAN_VREG_IO_DELAY_MIN, + WLAN_VREG_IO_DELAY_MAX); + } + } + + if (cnss_enable_xtal_ldo(pdev)) + goto err_reg_xtal_enable; + + vreg_info->wlan_reg = regulator_get(&pdev->dev, WLAN_VREG_NAME); + + if (IS_ERR(vreg_info->wlan_reg)) { + if (PTR_ERR(vreg_info->wlan_reg) == -EPROBE_DEFER) + pr_err("%s: vreg probe defer\n", __func__); + else + pr_err("%s: vreg regulator get failed\n", __func__); + ret = PTR_ERR(vreg_info->wlan_reg); + goto err_reg_get; + } + + ret = regulator_enable(vreg_info->wlan_reg); + + if (ret) { + pr_err("%s: vreg initial vote failed\n", __func__); + goto err_reg_enable; + } + + if (of_get_property(node, WLAN_VREG_SP2T_NAME "-supply", NULL)) { + vreg_info->wlan_reg_sp2t = + regulator_get(&pdev->dev, WLAN_VREG_SP2T_NAME); + if (!IS_ERR(vreg_info->wlan_reg_sp2t)) { + ret = regulator_set_voltage(vreg_info->wlan_reg_sp2t, + WLAN_VREG_SP2T_MIN, + WLAN_VREG_SP2T_MAX); + if (ret) { + pr_err("%s: Set wlan_vreg_sp2t failed\n", + __func__); + goto err_reg_sp2t_set; + } + + ret = regulator_enable(vreg_info->wlan_reg_sp2t); + if (ret) { + pr_err("%s: Enable wlan_vreg_sp2t failed\n", + __func__); + goto err_reg_sp2t_enable; + } + } + } + + if (of_get_property(node, WLAN_ANT_SWITCH_NAME "-supply", NULL)) { + vreg_info->ant_switch = + regulator_get(&pdev->dev, WLAN_ANT_SWITCH_NAME); + if (!IS_ERR(vreg_info->ant_switch)) { + ret = regulator_set_voltage(vreg_info->ant_switch, + WLAN_ANT_SWITCH_VOLT_MIN, + WLAN_ANT_SWITCH_VOLT_MAX); + if (ret < 0) { + pr_err("%s: Set ant_switch voltage failed\n", + __func__); + goto err_ant_switch_set; + } + + ret = regulator_set_load(vreg_info->ant_switch, + WLAN_ANT_SWITCH_CURR); + if (ret < 0) { + pr_err("%s: Set ant_switch current failed\n", + __func__); + goto err_ant_switch_set; + } + + ret = regulator_enable(vreg_info->ant_switch); + if (ret < 0) { + pr_err("%s: Enable ant_switch failed\n", + __func__); + goto err_ant_switch_enable; + } + } + } + + if (of_find_property(node, "qcom,wlan-uart-access", NULL)) + penv->cap.cap_flag |= CNSS_HAS_UART_ACCESS; + + if (of_get_property(node, WLAN_SWREG_NAME "-supply", NULL)) { + vreg_info->soc_swreg = regulator_get(&pdev->dev, + WLAN_SWREG_NAME); + if (IS_ERR(vreg_info->soc_swreg)) { + pr_err("%s: soc-swreg node not found\n", + __func__); + goto err_reg_get2; + } + ret = regulator_set_voltage(vreg_info->soc_swreg, + SOC_SWREG_VOLT_MIN, + SOC_SWREG_VOLT_MAX); + if (ret) { + pr_err("%s: vreg initial voltage set failed on soc-swreg\n", + __func__); + goto err_reg_set; + } + ret = regulator_enable(vreg_info->soc_swreg); + if (ret) { + pr_err("%s: vreg initial vote failed\n", __func__); + goto err_reg_enable2; + } + penv->cap.cap_flag |= CNSS_HAS_EXTERNAL_SWREG; + } + + penv->wlan_en_vreg_support = + of_property_read_bool(node, "qcom,wlan-en-vreg-support"); + if (penv->wlan_en_vreg_support) { + vreg_info->wlan_en_reg = + regulator_get(&pdev->dev, WLAN_EN_VREG_NAME); + if (IS_ERR(vreg_info->wlan_en_reg)) { + pr_err("%s:wlan_en vreg get failed\n", __func__); + ret = PTR_ERR(vreg_info->wlan_en_reg); + goto err_wlan_en_reg_get; + } + } + + if (!penv->wlan_en_vreg_support) { + ret = cnss_get_wlan_enable_gpio(gpio_info, pdev); + if (ret) { + pr_err("%s:Failed to config the WLAN_EN gpio\n", + __func__); + goto err_gpio_wlan_en; + } + } + vreg_info->state = true; + + ret = cnss_get_wlan_bootstrap_gpio(pdev); + if (ret) { + pr_err("%s: Failed to enable wlan bootstrap gpio\n", __func__); + goto err_gpio_wlan_bootstrap; + } + + return ret; + +err_gpio_wlan_bootstrap: + cnss_put_wlan_enable_gpio(); +err_gpio_wlan_en: +err_wlan_en_reg_get: + vreg_info->wlan_en_reg = NULL; + if (vreg_info->soc_swreg) + regulator_disable(vreg_info->soc_swreg); + vreg_info->state = false; + +err_reg_enable2: +err_reg_set: + if (vreg_info->soc_swreg) + regulator_put(vreg_info->soc_swreg); + +err_reg_get2: + if (vreg_info->ant_switch) + regulator_disable(vreg_info->ant_switch); + +err_ant_switch_enable: +err_ant_switch_set: + if (vreg_info->ant_switch) + regulator_put(vreg_info->ant_switch); + if (vreg_info->wlan_reg_sp2t) + regulator_disable(vreg_info->wlan_reg_sp2t); + +err_reg_sp2t_enable: +err_reg_sp2t_set: + if (vreg_info->wlan_reg_sp2t) + regulator_put(vreg_info->wlan_reg_sp2t); + regulator_disable(vreg_info->wlan_reg); + +err_reg_enable: + regulator_put(vreg_info->wlan_reg); +err_reg_get: + cnss_disable_xtal_ldo(pdev); + +err_reg_xtal_enable: + if (vreg_info->wlan_reg_io) + regulator_disable(vreg_info->wlan_reg_io); + +err_reg_io_enable: +err_reg_io_set: + if (vreg_info->wlan_reg_io) + regulator_put(vreg_info->wlan_reg_io); + if (vreg_info->wlan_reg_core) + regulator_disable(vreg_info->wlan_reg_core); + +err_reg_core_enable: +err_reg_core_set: + if (vreg_info->wlan_reg_core) + regulator_put(vreg_info->wlan_reg_core); + +err_reg_core_get: + return ret; +} + +static void cnss_wlan_release_resources(void) +{ + struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info; + struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; + + if (penv->wlan_bootstrap_gpio > 0) + gpio_free(penv->wlan_bootstrap_gpio); + cnss_put_wlan_enable_gpio(); + gpio_info->state = false; + gpio_info->prop = false; + cnss_wlan_vreg_set(vreg_info, false); + if (vreg_info->soc_swreg) + regulator_put(vreg_info->soc_swreg); + if (vreg_info->ant_switch) + regulator_put(vreg_info->ant_switch); + if (vreg_info->wlan_reg_sp2t) + regulator_put(vreg_info->wlan_reg_sp2t); + regulator_put(vreg_info->wlan_reg); + if (vreg_info->wlan_reg_xtal) + regulator_put(vreg_info->wlan_reg_xtal); + if (vreg_info->wlan_reg_xtal_aon) + regulator_put(vreg_info->wlan_reg_xtal_aon); + if (vreg_info->wlan_reg_io) + regulator_put(vreg_info->wlan_reg_io); + if (vreg_info->wlan_reg_core) + regulator_put(vreg_info->wlan_reg_core); + vreg_info->state = false; +} + +static u8 cnss_get_pci_dev_bus_number(struct pci_dev *pdev) +{ + return pdev->bus->number; +} + +void cnss_setup_fw_files(u16 revision) +{ + switch (revision) { + case QCA6174_FW_1_1: + strlcpy(penv->fw_files.image_file, "qwlan11.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.board_data, "bdwlan11.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.otp_data, "otp11.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_file, "utf11.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_board_data, "utfbd11.bin", + CNSS_MAX_FILE_NAME); + break; + + case QCA6174_FW_1_3: + strlcpy(penv->fw_files.image_file, "qwlan13.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.board_data, "bdwlan13.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.otp_data, "otp13.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_file, "utf13.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_board_data, "utfbd13.bin", + CNSS_MAX_FILE_NAME); + break; + + case QCA6174_FW_2_0: + strlcpy(penv->fw_files.image_file, "qwlan20.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.board_data, "bdwlan20.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.otp_data, "otp20.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_file, "utf20.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_board_data, "utfbd20.bin", + CNSS_MAX_FILE_NAME); + break; + + case QCA6174_FW_3_0: + case QCA6174_FW_3_2: + strlcpy(penv->fw_files.image_file, "qwlan30.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.board_data, "bdwlan30.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.otp_data, "otp30.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_file, "utf30.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_board_data, "utfbd30.bin", + CNSS_MAX_FILE_NAME); + break; + + default: + strlcpy(penv->fw_files.image_file, "qwlan.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.board_data, "bdwlan.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.otp_data, "otp.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_file, "utf.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_board_data, "utfbd.bin", + CNSS_MAX_FILE_NAME); + break; + } +} + +int cnss_get_fw_files(struct cnss_fw_files *pfw_files) +{ + if (!penv || !pfw_files) + return -ENODEV; + + *pfw_files = penv->fw_files; + + return 0; +} +EXPORT_SYMBOL(cnss_get_fw_files); + +#ifdef CONFIG_CNSS_SECURE_FW +static void cnss_wlan_fw_mem_alloc(struct pci_dev *pdev) +{ + penv->fw_mem = devm_kzalloc(&pdev->dev, MAX_FIRMWARE_SIZE, GFP_KERNEL); +} +#else +static void cnss_wlan_fw_mem_alloc(struct pci_dev *pdev) +{ +} +#endif + +static int get_image_file(const u8 *index_info, u8 *file_name, + u32 *type, u32 *segment_idx) +{ + if (!file_name || !index_info || !type) + return -EINVAL; + + memcpy(type, index_info, TYPE_LENGTH); + memcpy(segment_idx, index_info + TYPE_LENGTH, TYPE_LENGTH); + memcpy(file_name, index_info + TYPE_LENGTH + TYPE_LENGTH, + FW_FILENAME_LENGTH); + + pr_debug("%u: %u: %s\n", *type, *segment_idx, file_name); + + return PER_FILE_DATA; +} + +static void print_allocated_image_table(void) +{ + u32 seg = 0, count = 0; + u8 *dump_addr; + struct segment_memory *pseg_mem = penv->fw_seg_mem; + struct segment_memory *p_bdata_seg_mem = penv->bdata_seg_mem; + u8 *tmp_buf; + + tmp_buf = kmalloc(MAX_IMAGE_SIZE, GFP_KERNEL); + if (!tmp_buf) + return; + + pr_debug("%s: Dumping FW IMAGE\n", __func__); + while (seg++ < penv->fw_seg_count) { + dump_addr = (u8 *)pseg_mem->cpu_region + + sizeof(struct region_desc); + hex_dump_to_buffer(dump_addr, pseg_mem->size - sizeof(struct region_desc), + 16, 1, tmp_buf, MAX_IMAGE_SIZE, false); + pr_debug("%s: %s\n", __func__, tmp_buf); + pseg_mem++; + } + + seg = 0; + pr_debug("%s: Dumping BOARD DATA\n", __func__); + while (seg++ < penv->bdata_seg_count) { + dump_addr = (u8 *)p_bdata_seg_mem->cpu_region + + sizeof(struct region_desc); + hex_dump_to_buffer(dump_addr, p_bdata_seg_mem->size - sizeof(struct region_desc), + 16, 1, tmp_buf, MAX_IMAGE_SIZE, false); + pr_debug("%s: %s\n", __func__, tmp_buf); + p_bdata_seg_mem++; + } + + kfree(tmp_buf); +} + +static void free_allocated_image_table(void) +{ + struct device *dev = &penv->pdev->dev; + struct segment_memory *pseg_mem; + u32 seg = 0; + + /* free fw memroy */ + pseg_mem = penv->fw_seg_mem; + while (seg++ < penv->fw_seg_count) { + dma_free_coherent(dev, pseg_mem->size, + pseg_mem->cpu_region, pseg_mem->dma_region); + pseg_mem++; + } + if (penv->fw_cpu) + dma_free_coherent(dev, + sizeof(struct segment_desc) * + MAX_NUM_OF_SEGMENTS, + penv->fw_cpu, penv->fw_dma); + penv->fw_seg_count = 0; + penv->fw_dma = 0; + penv->fw_cpu = NULL; + penv->fw_dma_size = 0; + + /* free bdata memory */ + seg = 0; + pseg_mem = penv->bdata_seg_mem; + while (seg++ < penv->bdata_seg_count) { + dma_free_coherent(dev, pseg_mem->size, + pseg_mem->cpu_region, + pseg_mem->dma_region); + pseg_mem++; + } + if (penv->bdata_cpu) + dma_free_coherent(dev, + sizeof(struct segment_desc) * + MAX_NUM_OF_SEGMENTS, + penv->bdata_cpu, penv->bdata_dma); + penv->bdata_seg_count = 0; + penv->bdata_dma = 0; + penv->bdata_cpu = NULL; + penv->bdata_dma_size = 0; +} + +static int cnss_setup_fw_image_table(int mode) +{ + struct image_desc_hdr *image_hdr; + struct segment_desc *pseg = NULL; + const struct firmware *fw_index, *fw_image; + struct device *dev = NULL; + char reserved[3] = ""; + u8 image_file[FW_FILENAME_LENGTH] = ""; + u8 index_file[FW_FILENAME_LENGTH] = ""; + u8 index_info[MAX_INDEX_FILE_SIZE] = ""; + size_t image_desc_size = 0, file_size = 0; + size_t index_pos = 0, image_pos = 0; + struct region_desc *reg_desc = NULL; + u32 type = 0; + u32 segment_idx = 0; + uintptr_t address; + int ret = 0; + dma_addr_t dma_addr; + void *vaddr = NULL; + dma_addr_t paddr; + struct segment_memory *pseg_mem; + u32 *pseg_count; + + if (!penv || !penv->pdev) { + pr_err("cnss: invalid penv or pdev or dev\n"); + ret = -EINVAL; + goto err; + } + dev = &penv->pdev->dev; + + /* meta data file has image details */ + switch (mode) { + case FW_IMAGE_FTM: + ret = scnprintf(index_file, FW_FILENAME_LENGTH, "qftm.bin"); + pseg_mem = penv->fw_seg_mem; + pseg_count = &penv->fw_seg_count; + break; + case FW_IMAGE_MISSION: + ret = scnprintf(index_file, FW_FILENAME_LENGTH, "qwlan.bin"); + pseg_mem = penv->fw_seg_mem; + pseg_count = &penv->fw_seg_count; + break; + case FW_IMAGE_BDATA: + ret = scnprintf(index_file, FW_FILENAME_LENGTH, "bdwlan.bin"); + pseg_mem = penv->bdata_seg_mem; + pseg_count = &penv->bdata_seg_count; + break; + default: + pr_err("%s: Unknown meta data file type 0x%x\n", + __func__, mode); + ret = -EINVAL; + } + if (ret < 0) + goto err; + + image_desc_size = sizeof(struct image_desc_hdr) + + sizeof(struct segment_desc) * MAX_NUM_OF_SEGMENTS; + + vaddr = dma_alloc_coherent(dev, image_desc_size, + &paddr, GFP_KERNEL); + if (!vaddr) { + ret = -ENOMEM; + goto err; + } + + image_hdr = (struct image_desc_hdr *)vaddr; + image_hdr->image_id = mode; + memcpy(image_hdr->reserved, reserved, 3); + + pr_err("cnss: request meta data file %s\n", index_file); + ret = request_firmware(&fw_index, index_file, dev); + if (ret || !fw_index || !fw_index->data || !fw_index->size) { + pr_err("cnss: meta data file open failure %s\n", index_file); + goto err_free; + } + + if (fw_index->size > MAX_INDEX_FILE_SIZE) { + pr_err("cnss: meta data file has invalid size %s: %zu\n", + index_file, fw_index->size); + release_firmware(fw_index); + goto err_free; + } + + memcpy(index_info, fw_index->data, fw_index->size); + file_size = fw_index->size; + release_firmware(fw_index); + + while (file_size >= PER_FILE_DATA && image_pos < image_desc_size && + image_hdr->segments_cnt < MAX_NUM_OF_SEGMENTS) { + ret = get_image_file(index_info + index_pos, + image_file, &type, &segment_idx); + if (ret == -EINVAL) + goto err_free; + + file_size -= ret; + index_pos += ret; + pseg = vaddr + image_pos + + sizeof(struct image_desc_hdr); + + switch (type) { + case SEG_METADATA: + case SEG_NON_PAGED: + case SEG_LOCKED_PAGE: + case SEG_UNLOCKED_PAGE: + case SEG_NON_SECURE_DATA: + + image_hdr->segments_cnt++; + pseg->segment_id = type; + pseg->segment_idx = (u8)(segment_idx & 0xff); + memcpy(pseg->flags, reserved, 2); + + ret = request_firmware(&fw_image, image_file, dev); + if (ret || !fw_image || !fw_image->data || + !fw_image->size) { + pr_err("cnss: image file read failed %s\n", + image_file); + goto err_free; + } + if (fw_image->size > MAX_IMAGE_SIZE) { + pr_err("cnss: %s: image file invalid size %zu\n", + image_file, fw_image->size); + release_firmware(fw_image); + ret = -EINVAL; + goto err_free; + } + reg_desc = + dma_alloc_coherent(dev, + sizeof(struct region_desc) + + fw_image->size, + &dma_addr, GFP_KERNEL); + if (!reg_desc) { + ret = -ENOMEM; + release_firmware(fw_image); + goto err_free; + } + address = (uintptr_t)dma_addr; + pseg->addr_low = address & 0xFFFFFFFF; + pseg->addr_high = 0x00; + /* one region for one image file */ + pseg->addr_count = 1; + memcpy((u8 *)reg_desc + sizeof(struct region_desc), + fw_image->data, fw_image->size); + address += sizeof(struct region_desc); + reg_desc->addr_low = address & 0xFFFFFFFF; + reg_desc->addr_high = 0x00; + reg_desc->reserved = 0; + reg_desc->size = fw_image->size; + + pseg_mem[*pseg_count].dma_region = dma_addr; + pseg_mem[*pseg_count].cpu_region = reg_desc; + pseg_mem[*pseg_count].size = + sizeof(struct region_desc) + fw_image->size; + + release_firmware(fw_image); + (*pseg_count)++; + break; + + default: + pr_err("cnss: Unknown segment %d\n", type); + ret = -EINVAL; + goto err_free; + } + image_pos += sizeof(struct segment_desc); + } + if (mode != FW_IMAGE_BDATA) { + penv->fw_cpu = vaddr; + penv->fw_dma = paddr; + penv->fw_dma_size = sizeof(struct image_desc_hdr) + + sizeof(struct segment_desc) * image_hdr->segments_cnt; + } else { + penv->bdata_cpu = vaddr; + penv->bdata_dma = paddr; + penv->bdata_dma_size = sizeof(struct image_desc_hdr) + + sizeof(struct segment_desc) * image_hdr->segments_cnt; + } + pr_info("%s: Mode %d: Image setup table built on host\n", __func__, mode); + + return file_size; +err_free: + free_allocated_image_table(); +err: + pr_err("cnss: image file setup failed %d\n", ret); + return ret; +} + +int cnss_get_fw_image(struct image_desc_info *image_desc_info) +{ + if (!image_desc_info || !penv || + !penv->fw_seg_count || !penv->bdata_seg_count) + return -EINVAL; + + mutex_lock(&penv->fw_setup_stat_lock); + image_desc_info->fw_addr = penv->fw_dma; + image_desc_info->fw_size = penv->fw_dma_size; + image_desc_info->bdata_addr = penv->bdata_dma; + image_desc_info->bdata_size = penv->bdata_dma_size; + mutex_unlock(&penv->fw_setup_stat_lock); + + return 0; +} +EXPORT_SYMBOL(cnss_get_fw_image); + +static ssize_t wlan_setup_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%u\n", penv->revision_id); +} + +static DEVICE_ATTR(wlan_setup, 0644, wlan_setup_show, NULL); + +static int cnss_wlan_is_codeswap_supported(u16 revision) +{ + switch (revision) { + case QCA6174_FW_3_0: + case QCA6174_FW_3_2: + return 0; + default: + return 1; + } +} + +static int cnss_smmu_init(struct device *dev) +{ +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + struct dma_iommu_mapping *mapping; + int atomic_ctx = 1; + int s1_bypass = 1; + int fast = 1; + int ret; + + mapping = arm_iommu_create_mapping(&platform_bus_type, + penv->smmu_iova_start, + penv->smmu_iova_len); + if (IS_ERR(mapping)) { + ret = PTR_ERR(mapping); + pr_err("%s: create mapping failed, err = %d\n", __func__, ret); + goto map_fail; + } + + if (penv->smmu_s1_bypass) { + ret = iommu_domain_set_attr(mapping->domain, + DOMAIN_ATTR_S1_BYPASS, + &s1_bypass); + if (ret) { + pr_err("%s: set s1 bypass attr failed, err = %d\n", + __func__, ret); + goto set_attr_fail; + } + } else { + ret = iommu_domain_set_attr(mapping->domain, + DOMAIN_ATTR_ATOMIC, + &atomic_ctx); + if (ret) { + pr_err("%s: set atomic_ctx attr failed, err = %d\n", + __func__, ret); + goto set_attr_fail; + } + + ret = iommu_domain_set_attr(mapping->domain, + DOMAIN_ATTR_FAST, + &fast); + if (ret) { + pr_err("%s: set fast map attr failed, err = %d\n", + __func__, ret); + goto set_attr_fail; + } + } + + ret = arm_iommu_attach_device(dev, mapping); + if (ret) { + pr_err("%s: attach device failed, err = %d\n", __func__, ret); + goto attach_fail; + } + + penv->smmu_mapping = mapping; + + return ret; + +attach_fail: +set_attr_fail: + arm_iommu_release_mapping(mapping); +map_fail: + return ret; +#endif + penv->smmu_mapping = NULL; + return 0; +} + +static void cnss_smmu_remove(struct device *dev) +{ +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + arm_iommu_detach_device(dev); + arm_iommu_release_mapping(penv->smmu_mapping); +#endif + penv->smmu_mapping = NULL; +} + +#ifdef CONFIG_PCI_MSM +struct pci_saved_state *cnss_pci_store_saved_state(struct pci_dev *dev) +{ + return pci_store_saved_state(dev); +} + +int cnss_msm_pcie_pm_control(enum msm_pcie_pm_opt pm_opt, u32 bus_num, + struct pci_dev *pdev, u32 options) +{ + return msm_pcie_pm_control(pm_opt, bus_num, pdev, NULL, options); +} + +int cnss_pci_load_and_free_saved_state(struct pci_dev *dev, + struct pci_saved_state **state) +{ + return pci_load_and_free_saved_state(dev, state); +} + +int cnss_msm_pcie_shadow_control(struct pci_dev *dev, bool enable) +{ + return msm_pcie_shadow_control(dev, enable); +} + +int cnss_msm_pcie_deregister_event(struct msm_pcie_register_event *reg) +{ + return msm_pcie_deregister_event(reg); +} + +int cnss_msm_pcie_recover_config(struct pci_dev *dev) +{ + return msm_pcie_recover_config(dev); +} + +int cnss_msm_pcie_register_event(struct msm_pcie_register_event *reg) +{ + return msm_pcie_register_event(reg); +} + +int cnss_msm_pcie_enumerate(u32 rc_idx) +{ + return msm_pcie_enumerate(rc_idx); +} +#else /* !defined CONFIG_PCI_MSM */ + +struct pci_saved_state *cnss_pci_store_saved_state(struct pci_dev *dev) +{ + return NULL; +} + +int cnss_msm_pcie_pm_control(enum msm_pcie_pm_opt pm_opt, u32 bus_num, + struct pci_dev *pdev, u32 options) +{ + return -ENODEV; +} + +int cnss_pci_load_and_free_saved_state(struct pci_dev *dev, + struct pci_saved_state **state) +{ + return 0; +} + +int cnss_msm_pcie_shadow_control(struct pci_dev *dev, bool enable) +{ + return -ENODEV; +} + +int cnss_msm_pcie_deregister_event(struct msm_pcie_register_event *reg) +{ + return -ENODEV; +} + +int cnss_msm_pcie_recover_config(struct pci_dev *dev) +{ + return -ENODEV; +} + +int cnss_msm_pcie_register_event(struct msm_pcie_register_event *reg) +{ + return -ENODEV; +} + +int cnss_msm_pcie_enumerate(u32 rc_idx) +{ + return -EPROBE_DEFER; +} +#endif + +static void cnss_pcie_set_platform_ops(struct device *dev) +{ + struct cnss_dev_platform_ops *pf_ops = &penv->platform_ops; + + pf_ops->request_bus_bandwidth = cnss_pci_request_bus_bandwidth; + pf_ops->get_virt_ramdump_mem = cnss_pci_get_virt_ramdump_mem; + pf_ops->device_self_recovery = cnss_pci_device_self_recovery; + pf_ops->schedule_recovery_work = cnss_pci_schedule_recovery_work; + pf_ops->device_crashed = cnss_pci_device_crashed; + pf_ops->get_wlan_mac_address = cnss_pci_get_wlan_mac_address; + pf_ops->set_wlan_mac_address = cnss_pcie_set_wlan_mac_address; + pf_ops->power_up = cnss_pcie_power_up; + pf_ops->power_down = cnss_pcie_power_down; + + dev->platform_data = pf_ops; +} + +static void cnss_pcie_reset_platform_ops(struct device *dev) +{ + struct cnss_dev_platform_ops *pf_ops = &penv->platform_ops; + + memset(pf_ops, 0, sizeof(struct cnss_dev_platform_ops)); + dev->platform_data = NULL; +} + +static int cnss_wlan_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int ret = 0; + struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; + void *cpu_addr; + dma_addr_t dma_handle; + struct codeswap_codeseg_info *cnss_seg_info = NULL; + struct device *dev = &pdev->dev; + + cnss_pcie_set_platform_ops(dev); + penv->pdev = pdev; + penv->id = id; + atomic_set(&penv->fw_available, 0); + penv->device_id = pdev->device; + + if (penv->smmu_iova_len) { + ret = cnss_smmu_init(&pdev->dev); + if (ret) { + pr_err("%s: SMMU init failed, err = %d\n", + __func__, ret); + } + } + + if (penv->pci_register_again) { + pr_debug("%s: PCI re-registration complete\n", __func__); + penv->pci_register_again = false; + return 0; + } + + switch (pdev->device) { + case QCA6180_DEVICE_ID: + pci_read_config_word(pdev, QCA6180_REV_ID_OFFSET, + &penv->revision_id); + break; + + case QCA6174_DEVICE_ID: + pci_read_config_word(pdev, QCA6174_REV_ID_OFFSET, + &penv->revision_id); + cnss_setup_fw_files(penv->revision_id); + break; + + default: + pr_err("cnss: unknown device found %d\n", pdev->device); + ret = -EPROBE_DEFER; + goto err_unknown; + } + + if (penv->pcie_link_state) { + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + + ret = cnss_msm_pcie_pm_control(MSM_PCIE_SUSPEND, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS); + if (ret) { + pr_err("Failed to shutdown PCIe link\n"); + goto err_pcie_suspend; + } + penv->pcie_link_state = false; + } + + cnss_configure_wlan_en_gpio(false); + ret = cnss_wlan_vreg_set(vreg_info, false); + + if (ret) { + pr_err("can't turn off wlan vreg\n"); + goto err_pcie_suspend; + } + + mutex_lock(&penv->fw_setup_stat_lock); + cnss_wlan_fw_mem_alloc(pdev); + mutex_unlock(&penv->fw_setup_stat_lock); + + ret = device_create_file(&penv->pldev->dev, &dev_attr_wlan_setup); + + if (ret) { + pr_err("Can't Create Device file\n"); + goto err_pcie_suspend; + } + + if (cnss_wlan_is_codeswap_supported(penv->revision_id)) { + pr_debug("Code-swap not enabled: %d\n", penv->revision_id); + goto err_pcie_suspend; + } + + cpu_addr = dma_alloc_coherent(dev, EVICT_BIN_MAX_SIZE, + &dma_handle, GFP_KERNEL); + if (!cpu_addr || !dma_handle) + goto err_pcie_suspend; + + cnss_seg_info = devm_kzalloc(dev, sizeof(*cnss_seg_info), + GFP_KERNEL); + if (!cnss_seg_info) + goto end_dma_alloc; + + memset(cnss_seg_info, 0, sizeof(*cnss_seg_info)); + cnss_seg_info->codeseg_busaddr[0] = (void *)dma_handle; + penv->codeseg_cpuaddr[0] = cpu_addr; + cnss_seg_info->codeseg_size = EVICT_BIN_MAX_SIZE; + cnss_seg_info->codeseg_total_bytes = EVICT_BIN_MAX_SIZE; + cnss_seg_info->num_codesegs = 1; + cnss_seg_info->codeseg_size_log2 = ilog2(EVICT_BIN_MAX_SIZE); + + penv->cnss_seg_info = cnss_seg_info; + pr_debug("%s: Successfully allocated memory for CODESWAP\n", __func__); + + return ret; + +end_dma_alloc: + dma_free_coherent(dev, EVICT_BIN_MAX_SIZE, cpu_addr, dma_handle); +err_unknown: +err_pcie_suspend: + cnss_pcie_reset_platform_ops(dev); + return ret; +} + +static void cnss_wlan_pci_remove(struct pci_dev *pdev) +{ + struct device *dev; + + if (!penv) + return; + + dev = &penv->pldev->dev; + cnss_pcie_reset_platform_ops(dev); + device_remove_file(dev, &dev_attr_wlan_setup); + + if (penv->smmu_mapping) + cnss_smmu_remove(&pdev->dev); +} + +static int cnss_wlan_pci_suspend(struct device *dev) +{ + int ret = 0; + struct cnss_wlan_driver *wdriver; + struct pci_dev *pdev = to_pci_dev(dev); + + pm_message_t state = { .event = PM_EVENT_SUSPEND }; + + if (!penv) + goto out; + + if (!penv->pcie_link_state) + goto out; + + wdriver = penv->driver; + if (!wdriver) + goto out; + + if (wdriver->suspend) { + ret = wdriver->suspend(pdev, state); + + if (penv->pcie_link_state) { + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + } + } + penv->monitor_wake_intr = false; + +out: + return ret; +} + +static int cnss_wlan_pci_resume(struct device *dev) +{ + int ret = 0; + struct cnss_wlan_driver *wdriver; + struct pci_dev *pdev = to_pci_dev(dev); + + if (!penv) + goto out; + + if (!penv->pcie_link_state) + goto out; + + wdriver = penv->driver; + if (!wdriver) + goto out; + + if (wdriver->resume && !penv->pcie_link_down_ind) { + if (penv->saved_state) + cnss_pci_load_and_free_saved_state(pdev, + &penv->saved_state); + pci_restore_state(pdev); + + ret = wdriver->resume(pdev); + } + +out: + return ret; +} + +static int cnss_wlan_runtime_suspend(struct device *dev) +{ + int ret = 0; + struct cnss_wlan_driver *wdrv; + + if (!penv) + return -EAGAIN; + + if (penv->pcie_link_down_ind) { + pr_debug("PCI link down recovery is in progress\n"); + return -EAGAIN; + } + + pr_debug("cnss: runtime suspend start\n"); + + wdrv = penv->driver; + + if (wdrv && wdrv->runtime_ops && wdrv->runtime_ops->runtime_suspend) + ret = wdrv->runtime_ops->runtime_suspend(to_pci_dev(dev)); + + pr_info("cnss: runtime suspend status: %d\n", ret); + + return ret; +} + +static int cnss_wlan_runtime_resume(struct device *dev) +{ + struct cnss_wlan_driver *wdrv; + int ret = 0; + + if (!penv) + return -EAGAIN; + + if (penv->pcie_link_down_ind) { + pr_debug("PCI link down recovery is in progress\n"); + return -EAGAIN; + } + + pr_debug("cnss: runtime resume start\n"); + + wdrv = penv->driver; + + if (wdrv && wdrv->runtime_ops && wdrv->runtime_ops->runtime_resume) + ret = wdrv->runtime_ops->runtime_resume(to_pci_dev(dev)); + + pr_info("cnss: runtime resume status: %d\n", ret); + + return ret; +} + +static int cnss_wlan_runtime_idle(struct device *dev) +{ + pr_debug("cnss: runtime idle\n"); + + pm_request_autosuspend(dev); + + return -EBUSY; +} + +static DECLARE_RWSEM(cnss_pm_sem); + +static int cnss_pm_notify(struct notifier_block *b, + unsigned long event, void *p) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + down_write(&cnss_pm_sem); + break; + + case PM_POST_SUSPEND: + up_write(&cnss_pm_sem); + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block cnss_pm_notifier = { + .notifier_call = cnss_pm_notify, +}; + +static const struct pci_device_id cnss_wlan_pci_id_table[] = { + { QCA6174_VENDOR_ID, QCA6174_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID }, + { QCA6174_VENDOR_ID, BEELINER_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID }, + { QCA6180_VENDOR_ID, QCA6180_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, cnss_wlan_pci_id_table); + +#ifdef CONFIG_PM +static const struct dev_pm_ops cnss_wlan_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cnss_wlan_pci_suspend, cnss_wlan_pci_resume) + SET_RUNTIME_PM_OPS(cnss_wlan_runtime_suspend, cnss_wlan_runtime_resume, + cnss_wlan_runtime_idle) +}; +#endif + +struct pci_driver cnss_wlan_pci_driver = { + .name = "cnss_wlan_pci", + .id_table = cnss_wlan_pci_id_table, + .probe = cnss_wlan_pci_probe, + .remove = cnss_wlan_pci_remove, +#ifdef CONFIG_PM + .driver = { + .pm = &cnss_wlan_pm_ops, + }, +#endif +}; + +static ssize_t fw_image_setup_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%u\n", penv->fw_image_setup); +} + +static ssize_t fw_image_setup_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int val; + int ret; + + if (!penv) + return -ENODEV; + + mutex_lock(&penv->fw_setup_stat_lock); + pr_info("%s: Firmware setup in progress\n", __func__); + + if (kstrtoint(buf, 0, &val)) { + mutex_unlock(&penv->fw_setup_stat_lock); + return -EINVAL; + } + + if (val == FW_IMAGE_FTM || val == FW_IMAGE_MISSION || + val == FW_IMAGE_BDATA) { + pr_info("%s: fw image setup triggered %d\n", __func__, val); + ret = cnss_setup_fw_image_table(val); + if (ret != 0) { + pr_err("%s: Invalid parsing of FW image files %d\n", + __func__, ret); + mutex_unlock(&penv->fw_setup_stat_lock); + return -EINVAL; + } + penv->fw_image_setup = val; + } else if (val == FW_IMAGE_PRINT) { + print_allocated_image_table(); + } else if (val == BMI_TEST_SETUP) { + penv->bmi_test = val; + } + + pr_info("%s: Firmware setup completed\n", __func__); + mutex_unlock(&penv->fw_setup_stat_lock); + return count; +} + +static DEVICE_ATTR(fw_image_setup, 0644, fw_image_setup_show, fw_image_setup_store); + +void cnss_pci_recovery_work_handler(struct work_struct *recovery) +{ + cnss_pci_device_self_recovery(); +} + +DECLARE_WORK(cnss_pci_recovery_work, cnss_pci_recovery_work_handler); + +void cnss_schedule_recovery_work(void) +{ + schedule_work(&cnss_pci_recovery_work); +} +EXPORT_SYMBOL(cnss_schedule_recovery_work); + +static inline void __cnss_disable_irq(void *data) +{ + struct pci_dev *pdev = data; + + disable_irq(pdev->irq); +} + +void cnss_pci_events_cb(struct msm_pcie_notify *notify) +{ + unsigned long flags; + + if (!notify) + return; + + switch (notify->event) { + case MSM_PCIE_EVENT_LINKDOWN: + if (pcie_link_down_panic) + panic("PCIe link is down\n"); + + spin_lock_irqsave(&pci_link_down_lock, flags); + if (penv->pcie_link_down_ind) { + pr_debug("PCI link down recovery is in progress, ignore\n"); + spin_unlock_irqrestore(&pci_link_down_lock, flags); + return; + } + penv->pcie_link_down_ind = true; + spin_unlock_irqrestore(&pci_link_down_lock, flags); + + pr_err("PCI link down, schedule recovery\n"); + __cnss_disable_irq(notify->user); + schedule_work(&cnss_pci_recovery_work); + break; + + case MSM_PCIE_EVENT_WAKEUP: + if (penv->monitor_wake_intr && + atomic_read(&penv->auto_suspended)) { + penv->monitor_wake_intr = false; + pm_request_resume(&penv->pdev->dev); + } + break; + + default: + pr_err("cnss: invalid event from PCIe callback %d\n", + notify->event); + } +} + +void cnss_wlan_pci_link_down(void) +{ + unsigned long flags; + + if (pcie_link_down_panic) + panic("PCIe link is down\n"); + + spin_lock_irqsave(&pci_link_down_lock, flags); + if (penv->pcie_link_down_ind) { + pr_debug("PCI link down recovery is in progress, ignore\n"); + spin_unlock_irqrestore(&pci_link_down_lock, flags); + return; + } + penv->pcie_link_down_ind = true; + spin_unlock_irqrestore(&pci_link_down_lock, flags); + + pr_err("PCI link down detected by host driver, schedule recovery\n"); + schedule_work(&cnss_pci_recovery_work); +} +EXPORT_SYMBOL(cnss_wlan_pci_link_down); + +int cnss_pcie_shadow_control(struct pci_dev *dev, bool enable) +{ + return cnss_msm_pcie_shadow_control(dev, enable); +} +EXPORT_SYMBOL(cnss_pcie_shadow_control); + +int cnss_get_codeswap_struct(struct codeswap_codeseg_info *swap_seg) +{ + struct codeswap_codeseg_info *cnss_seg_info = penv->cnss_seg_info; + + mutex_lock(&penv->fw_setup_stat_lock); + if (!cnss_seg_info) { + swap_seg = NULL; + mutex_unlock(&penv->fw_setup_stat_lock); + return -ENOENT; + } + + if (!atomic_read(&penv->fw_available)) { + pr_debug("%s: fw is not available\n", __func__); + mutex_unlock(&penv->fw_setup_stat_lock); + return -ENOENT; + } + + *swap_seg = *cnss_seg_info; + mutex_unlock(&penv->fw_setup_stat_lock); + + return 0; +} +EXPORT_SYMBOL(cnss_get_codeswap_struct); + +static void cnss_wlan_memory_expansion(void) +{ + struct device *dev; + const struct firmware *fw_entry; + const char *filename; + u32 fw_entry_size, size_left, dma_size_left, length; + char *fw_temp; + char *fw_data; + char *dma_virt_addr; + struct codeswap_codeseg_info *cnss_seg_info; + u32 total_length = 0; + struct pci_dev *pdev; + + mutex_lock(&penv->fw_setup_stat_lock); + filename = cnss_wlan_get_evicted_data_file(); + pdev = penv->pdev; + dev = &pdev->dev; + cnss_seg_info = penv->cnss_seg_info; + + if (!cnss_seg_info) { + pr_debug("cnss: cnss_seg_info is NULL\n"); + mutex_unlock(&penv->fw_setup_stat_lock); + goto end; + } + + if (atomic_read(&penv->fw_available)) { + pr_debug("cnss: fw code already copied to host memory\n"); + mutex_unlock(&penv->fw_setup_stat_lock); + goto end; + } + + if (request_firmware(&fw_entry, filename, dev) != 0) { + pr_debug("cnss: failed to get fw: %s\n", filename); + mutex_unlock(&penv->fw_setup_stat_lock); + goto end; + } + + if (!fw_entry || !fw_entry->data) { + pr_err("%s: INVALID FW entries\n", __func__); + mutex_unlock(&penv->fw_setup_stat_lock); + goto release_fw; + } + + dma_virt_addr = (char *)penv->codeseg_cpuaddr[0]; + fw_data = (u8 *)fw_entry->data; + fw_temp = fw_data; + fw_entry_size = fw_entry->size; + if (fw_entry_size > EVICT_BIN_MAX_SIZE) + fw_entry_size = EVICT_BIN_MAX_SIZE; + size_left = fw_entry_size; + dma_size_left = EVICT_BIN_MAX_SIZE; + while ((size_left && fw_temp) && (dma_size_left > 0)) { + fw_temp = fw_temp + 4; + size_left = size_left - 4; + length = *(int *)fw_temp; + if ((length > size_left || length <= 0) || + (dma_size_left <= 0 || length > dma_size_left)) { + pr_err("cnss: wrong length read:%d\n", + length); + break; + } + fw_temp = fw_temp + 4; + size_left = size_left - 4; + memcpy(dma_virt_addr, fw_temp, length); + dma_size_left = dma_size_left - length; + size_left = size_left - length; + fw_temp = fw_temp + length; + dma_virt_addr = dma_virt_addr + length; + total_length += length; + pr_debug("cnss: bytes_left to copy: fw:%d; dma_page:%d\n", + size_left, dma_size_left); + } + pr_debug("cnss: total_bytes copied: %d\n", total_length); + cnss_seg_info->codeseg_total_bytes = total_length; + + atomic_set(&penv->fw_available, 1); + mutex_unlock(&penv->fw_setup_stat_lock); + +release_fw: + release_firmware(fw_entry); +end: + return; +} + +/** + * cnss_get_wlan_mac_address() - API to return MAC addresses buffer + * @dev: struct device pointer + * @num: buffer for number of mac addresses supported + * + * API returns the pointer to the buffer filled with mac addresses and + * updates num with the number of mac addresses the buffer contains. + * + * Return: pointer to mac address buffer. + */ +u8 *cnss_pci_get_wlan_mac_address(u32 *num) +{ + struct cnss_wlan_mac_addr *addr = NULL; + + if (!penv) { + pr_err("%s: Invalid Platform Driver Context\n", __func__); + goto end; + } + + if (!penv->is_wlan_mac_set) { + pr_info("%s: Platform Driver doesn't have any mac address\n", + __func__); + goto end; + } + + addr = &penv->wlan_mac_addr; + *num = addr->no_of_mac_addr_set; + return &addr->mac_addr[0][0]; + +end: + *num = 0; + return NULL; +} + +/** + * cnss_get_wlan_mac_address() - API to return MAC addresses buffer + * @dev: struct device pointer + * @num: buffer for number of mac addresses supported + * + * API returns the pointer to the buffer filled with mac addresses and + * updates num with the number of mac addresses the buffer contains. + * + * Return: pointer to mac address buffer. + */ +u8 *cnss_get_wlan_mac_address(struct device *dev, u32 *num) +{ + struct cnss_wlan_mac_addr *addr = NULL; + + if (!penv) { + pr_err("%s: Invalid Platform Driver Context\n", __func__); + goto end; + } + + if (!penv->is_wlan_mac_set) { + pr_info("%s: Platform Driver doesn't have any mac address\n", + __func__); + goto end; + } + + addr = &penv->wlan_mac_addr; + *num = addr->no_of_mac_addr_set; + return &addr->mac_addr[0][0]; +end: + *num = 0; + return NULL; +} +EXPORT_SYMBOL(cnss_get_wlan_mac_address); + +/** + * cnss_pcie_set_wlan_mac_address() - API to get two wlan mac address + * @in: Input buffer with wlan mac addresses + * @len: Size of the buffer passed + * + * API to store wlan mac address passed by the caller. The stored mac + * addresses are used by the wlan functional driver to program wlan HW. + * + * Return: kernel error code. + */ +int cnss_pcie_set_wlan_mac_address(const u8 *in, u32 len) +{ + u32 no_of_mac_addr; + struct cnss_wlan_mac_addr *addr = NULL; + int iter = 0; + u8 *temp = NULL; + + if (len == 0 || (len % ETH_ALEN) != 0) { + pr_err("%s: Invalid Length:%d\n", __func__, len); + return -EINVAL; + } + + no_of_mac_addr = len / ETH_ALEN; + + if (no_of_mac_addr > MAX_NO_OF_MAC_ADDR) { + pr_err("%s: Num of supported MAC addresses are:%d given:%d\n", + __func__, MAX_NO_OF_MAC_ADDR, no_of_mac_addr); + return -EINVAL; + } + + if (!penv) { + pr_err("%s: Invalid CNSS Platform Context\n", __func__); + return -ENOENT; + } + + if (penv->is_wlan_mac_set) { + pr_info("%s: Already MAC address are configured\n", __func__); + return 0; + } + + penv->is_wlan_mac_set = true; + addr = &penv->wlan_mac_addr; + addr->no_of_mac_addr_set = no_of_mac_addr; + temp = &addr->mac_addr[0][0]; + + for (; iter < no_of_mac_addr; ++iter, temp += ETH_ALEN, in += + ETH_ALEN) { + ether_addr_copy(temp, in); + pr_debug("%s MAC_ADDR:%02x:%02x:%02x:%02x:%02x:%02x\n", + __func__, temp[0], temp[1], temp[2], temp[3], temp[4], + temp[5]); + } + return 0; +} + +int cnss_wlan_register_driver(struct cnss_wlan_driver *driver) +{ + int ret = 0; + int probe_again = 0; + struct cnss_wlan_driver *wdrv; + struct cnss_wlan_vreg_info *vreg_info; + struct cnss_wlan_gpio_info *gpio_info; + struct pci_dev *pdev; + + if (!penv) + return -ENODEV; + + vreg_info = &penv->vreg_info; + gpio_info = &penv->gpio_info; + pdev = penv->pdev; + + if (!penv->driver) { + penv->driver = driver; + wdrv = penv->driver; + } else { + pr_err("driver already registered\n"); + return -EEXIST; + } + +again: + ret = cnss_wlan_vreg_set(vreg_info, true); + if (ret) { + pr_err("wlan vreg ON failed\n"); + goto err_wlan_vreg_on; + } + + msleep(POWER_ON_DELAY); + + if (penv->wlan_bootstrap_gpio > 0) { + gpio_set_value(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_HIGH); + msleep(WLAN_BOOTSTRAP_DELAY); + } + + cnss_configure_wlan_en_gpio(true); + + if (!pdev) { + pr_debug("%s: invalid pdev. register pci device\n", __func__); + ret = pci_register_driver(&cnss_wlan_pci_driver); + + if (ret) { + pr_err("%s: pci registration failed\n", __func__); + goto err_pcie_reg; + } + pdev = penv->pdev; + if (!pdev) { + pr_err("%s: pdev is still invalid\n", __func__); + goto err_pcie_reg; + } + } + + penv->event_reg.events = MSM_PCIE_EVENT_LINKDOWN | + MSM_PCIE_EVENT_WAKEUP; + penv->event_reg.user = pdev; + penv->event_reg.mode = MSM_PCIE_TRIGGER_CALLBACK; + penv->event_reg.callback = cnss_pci_events_cb; + penv->event_reg.options = MSM_PCIE_CONFIG_NO_RECOVERY; + ret = cnss_msm_pcie_register_event(&penv->event_reg); + if (ret) + pr_err("%s: PCIe event register failed %d\n", __func__, ret); + + if (!penv->pcie_link_state && !penv->pcie_link_down_ind) { + ret = cnss_msm_pcie_pm_control(MSM_PCIE_RESUME, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS); + if (ret) { + pr_err("PCIe link bring-up failed\n"); + goto err_pcie_link_up; + } + penv->pcie_link_state = true; + } else if (!penv->pcie_link_state && penv->pcie_link_down_ind) { + ret = cnss_msm_pcie_pm_control(MSM_PCIE_RESUME, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS_RESUME_LINK_DOWN); + + if (ret) { + pr_err("PCIe link bring-up failed (link down option)\n"); + goto err_pcie_link_up; + } + penv->pcie_link_state = true; + + ret = cnss_msm_pcie_recover_config(pdev); + if (ret) { + pr_err("cnss: PCI link failed to recover\n"); + goto err_pcie_link_up; + } + penv->pcie_link_down_ind = false; + } + + if (!cnss_wlan_is_codeswap_supported(penv->revision_id)) + cnss_wlan_memory_expansion(); + + if (wdrv->probe) { + if (penv->saved_state) + cnss_pci_load_and_free_saved_state(pdev, + &penv->saved_state); + + pci_restore_state(pdev); + + ret = wdrv->probe(pdev, penv->id); + if (ret) { + wcnss_prealloc_check_memory_leak(); + wcnss_pre_alloc_reset(); + + if (probe_again > 3) { + pr_err("Failed to probe WLAN\n"); + goto err_wlan_probe; + } + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + cnss_msm_pcie_deregister_event(&penv->event_reg); + cnss_msm_pcie_pm_control(MSM_PCIE_SUSPEND, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS); + penv->pcie_link_state = false; + cnss_configure_wlan_en_gpio(false); + cnss_wlan_vreg_set(vreg_info, false); + msleep(POWER_ON_DELAY); + probe_again++; + goto again; + } + } + + if (penv->notify_modem_status && wdrv->modem_status) + wdrv->modem_status(pdev, penv->modem_current_status); + + return ret; + +err_wlan_probe: + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + +err_pcie_link_up: + cnss_msm_pcie_deregister_event(&penv->event_reg); + if (penv->pcie_link_state) { + cnss_msm_pcie_pm_control(MSM_PCIE_SUSPEND, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS); + penv->pcie_link_state = false; + } + +err_pcie_reg: + cnss_configure_wlan_en_gpio(false); + cnss_wlan_vreg_set(vreg_info, false); + if (penv->pdev) { + pr_err("%d: Unregistering PCI device\n", __LINE__); + pci_unregister_driver(&cnss_wlan_pci_driver); + penv->pdev = NULL; + penv->pci_register_again = true; + } + +err_wlan_vreg_on: + penv->driver = NULL; + + return ret; +} +EXPORT_SYMBOL(cnss_wlan_register_driver); + +void cnss_wlan_unregister_driver(struct cnss_wlan_driver *driver) +{ + struct cnss_wlan_driver *wdrv; + struct cnss_wlan_vreg_info *vreg_info; + struct cnss_wlan_gpio_info *gpio_info; + struct pci_dev *pdev; + + if (!penv) + return; + + wdrv = penv->driver; + vreg_info = &penv->vreg_info; + gpio_info = &penv->gpio_info; + pdev = penv->pdev; + + if (!wdrv) { + pr_err("driver not registered\n"); + return; + } +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + if (penv->bus_client) + legacy_bus_client_update_request(penv->bus_client, + CNSS_BUS_WIDTH_NONE); +#endif + if (!pdev) { + pr_err("%d: invalid pdev\n", __LINE__); + goto cut_power; + } + + if (wdrv->remove) + wdrv->remove(pdev); + + wcnss_prealloc_check_memory_leak(); + wcnss_pre_alloc_reset(); + + cnss_msm_pcie_deregister_event(&penv->event_reg); + + if (penv->pcie_link_state && !penv->pcie_link_down_ind) { + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + + if (cnss_msm_pcie_pm_control(MSM_PCIE_SUSPEND, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS)) { + pr_err("Failed to shutdown PCIe link\n"); + return; + } + } else if (penv->pcie_link_state && penv->pcie_link_down_ind) { + penv->saved_state = NULL; + + if (cnss_msm_pcie_pm_control(MSM_PCIE_SUSPEND, + cnss_get_pci_dev_bus_number(pdev), + pdev, + PM_OPTIONS_SUSPEND_LINK_DOWN)) { + pr_err("Failed to shutdown PCIe link (with linkdown option)\n"); + return; + } + } + penv->pcie_link_state = false; + penv->driver_status = CNSS_UNINITIALIZED; + penv->monitor_wake_intr = false; + atomic_set(&penv->auto_suspended, 0); + +cut_power: + penv->driver = NULL; + + cnss_configure_wlan_en_gpio(false); + if (cnss_wlan_vreg_set(vreg_info, false)) + pr_err("wlan vreg OFF failed\n"); +} +EXPORT_SYMBOL(cnss_wlan_unregister_driver); + +#ifdef CONFIG_PCI_MSM +int cnss_wlan_pm_control(bool vote) +{ + if (!penv || !penv->pdev) + return -ENODEV; + + return cnss_msm_pcie_pm_control(vote ? MSM_PCIE_DISABLE_PC : MSM_PCIE_ENABLE_PC, + cnss_get_pci_dev_bus_number(penv->pdev), + penv->pdev, PM_OPTIONS); +} +EXPORT_SYMBOL(cnss_wlan_pm_control); +#endif + +void cnss_lock_pm_sem(void) +{ + down_read(&cnss_pm_sem); +} +EXPORT_SYMBOL(cnss_lock_pm_sem); + +void cnss_release_pm_sem(void) +{ + up_read(&cnss_pm_sem); +} +EXPORT_SYMBOL(cnss_release_pm_sem); + +void cnss_pci_schedule_recovery_work(void) +{ + schedule_work(&cnss_pci_recovery_work); +} + +void *cnss_pci_get_virt_ramdump_mem(unsigned long *size) +{ + if (!penv || !penv->pldev) + return NULL; + + *size = penv->ramdump_size; + + return penv->ramdump_addr; +} + +void cnss_pci_device_crashed(void) +{ + if (penv && penv->subsys) { + subsys_set_crash_status(penv->subsys, true); + subsystem_restart_dev(penv->subsys); + } +} + +void *cnss_get_virt_ramdump_mem(unsigned long *size) +{ + if (!penv || !penv->pldev) + return NULL; + + *size = penv->ramdump_size; + + return penv->ramdump_addr; +} +EXPORT_SYMBOL(cnss_get_virt_ramdump_mem); + +void cnss_device_crashed(void) +{ + if (penv && penv->subsys) { + subsys_set_crash_status(penv->subsys, true); + subsystem_restart_dev(penv->subsys); + } +} +EXPORT_SYMBOL(cnss_device_crashed); + +static int cnss_shutdown(const struct subsys_desc *subsys, bool force_stop) +{ + struct cnss_wlan_driver *wdrv; + struct pci_dev *pdev; + struct cnss_wlan_vreg_info *vreg_info; + struct cnss_wlan_gpio_info *gpio_info; + int ret = 0; + + if (!penv) + return -ENODEV; + + penv->recovery_in_progress = true; + wdrv = penv->driver; + pdev = penv->pdev; + vreg_info = &penv->vreg_info; + gpio_info = &penv->gpio_info; + + if (!pdev) { + ret = -EINVAL; + goto cut_power; + } + + if (wdrv && wdrv->shutdown) + wdrv->shutdown(pdev); + + if (penv->pcie_link_state) { + if (cnss_msm_pcie_pm_control(MSM_PCIE_SUSPEND, + cnss_get_pci_dev_bus_number(pdev), + pdev, + PM_OPTIONS_SUSPEND_LINK_DOWN)) { + pr_debug("cnss: Failed to shutdown PCIe link\n"); + ret = -EFAULT; + } + penv->saved_state = NULL; + penv->pcie_link_state = false; + } + +cut_power: + cnss_configure_wlan_en_gpio(false); + if (cnss_wlan_vreg_set(vreg_info, false)) + pr_err("cnss: Failed to set WLAN false\n"); + + return ret; +} + +static int cnss_powerup(const struct subsys_desc *subsys) +{ + struct cnss_wlan_driver *wdrv; + struct pci_dev *pdev; + struct cnss_wlan_vreg_info *vreg_info; + struct cnss_wlan_gpio_info *gpio_info; + int ret = 0; + + if (!penv) + return -ENODEV; + + if (!penv->driver) + goto out; + + wdrv = penv->driver; + pdev = penv->pdev; + vreg_info = &penv->vreg_info; + gpio_info = &penv->gpio_info; + + ret = cnss_wlan_vreg_set(vreg_info, true); + if (ret) { + pr_err("cnss: Failed to set WLAN VREG_ON\n"); + goto err_wlan_vreg_on; + } + + msleep(POWER_ON_DELAY); + cnss_configure_wlan_en_gpio(true); + /** + * Some platforms have wifi and other PCIE card attached with PCIE + * switch on the same RC like P5459 board(ROME 3.2 PCIE card + Ethernet + * PCI), it will need extra time to stable the signals when do SSR, + * otherwise fail to create the PCIE link, so add PCIE_SWITCH_DELAY. + */ + msleep(PCIE_SWITCH_DELAY); + + if (!pdev) { + pr_err("%d: invalid pdev\n", __LINE__); + goto err_pcie_link_up; + } + + if (!penv->pcie_link_state) { + ret = cnss_msm_pcie_pm_control(MSM_PCIE_RESUME, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS_RESUME_LINK_DOWN); + + if (ret) { + pr_err("cnss: Failed to bring-up PCIe link\n"); + goto err_pcie_link_up; + } + penv->pcie_link_state = true; + ret = cnss_msm_pcie_recover_config(penv->pdev); + if (ret) { + pr_err("cnss: PCI link failed to recover\n"); + goto err_pcie_link_up; + } + penv->pcie_link_down_ind = false; + } + + if (wdrv && wdrv->reinit) { + if (penv->saved_state) + cnss_pci_load_and_free_saved_state(pdev, + &penv->saved_state); + + pci_restore_state(pdev); + + ret = wdrv->reinit(pdev, penv->id); + if (ret) { + pr_err("%d: Failed to do reinit\n", __LINE__); + goto err_wlan_reinit; + } + } else { + pr_err("%d: wdrv->reinit is invalid\n", __LINE__); + goto err_pcie_link_up; + } + + if (penv->notify_modem_status && wdrv->modem_status) + wdrv->modem_status(pdev, penv->modem_current_status); + +out: + penv->recovery_in_progress = false; + return ret; + +err_wlan_reinit: + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + cnss_msm_pcie_pm_control(MSM_PCIE_SUSPEND, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS); + penv->pcie_link_state = false; + +err_pcie_link_up: + cnss_configure_wlan_en_gpio(false); + cnss_wlan_vreg_set(vreg_info, false); + if (penv->pdev) { + if (wdrv && wdrv->update_status) + wdrv->update_status(penv->pdev, CNSS_SSR_FAIL); + if (!penv->recovery_in_progress) { + pr_err("%d: Unregistering pci device\n", __LINE__); + pci_unregister_driver(&cnss_wlan_pci_driver); + penv->pdev = NULL; + penv->pci_register_again = true; + } + } + +err_wlan_vreg_on: + return ret; +} + +void cnss_pci_device_self_recovery(void) +{ + if (!penv) + return; + + if (penv->recovery_in_progress) { + pr_err("cnss: Recovery already in progress\n"); + return; + } + + if (penv->driver_status == CNSS_LOAD_UNLOAD) { + pr_err("cnss: load unload in progress\n"); + return; + } + + penv->recovery_count++; + penv->recovery_in_progress = true; + cnss_pm_wake_lock(penv->ws); + cnss_shutdown(NULL, false); + msleep(WLAN_RECOVERY_DELAY); + cnss_powerup(NULL); + cnss_pm_wake_lock_release(penv->ws); + penv->recovery_in_progress = false; +} + +static int cnss_ramdump(int enable, const struct subsys_desc *subsys) +{ + struct ramdump_segment segment; + + if (!penv) + return -ENODEV; + + if (!penv->ramdump_size) + return -ENOENT; + + if (!enable) + return 0; + + memset(&segment, 0, sizeof(segment)); + segment.v_address = penv->ramdump_addr; + segment.size = penv->ramdump_size; + + return do_ramdump(penv->ramdump_dev, &segment, 1); +} + +static void cnss_crash_shutdown(const struct subsys_desc *subsys) +{ + struct cnss_wlan_driver *wdrv; + struct pci_dev *pdev; + + if (!penv) + return; + + wdrv = penv->driver; + pdev = penv->pdev; + + if (pdev && wdrv && wdrv->crash_shutdown) + wdrv->crash_shutdown(pdev); +} + +void cnss_device_self_recovery(void) +{ + if (!penv) + return; + + if (penv->recovery_in_progress) { + pr_err("cnss: Recovery already in progress\n"); + return; + } + if (penv->driver_status == CNSS_LOAD_UNLOAD) { + pr_err("cnss: load unload in progress\n"); + return; + } + penv->recovery_count++; + penv->recovery_in_progress = true; + cnss_pm_wake_lock(penv->ws); + cnss_shutdown(NULL, false); + msleep(WLAN_RECOVERY_DELAY); + cnss_powerup(NULL); + cnss_pm_wake_lock_release(penv->ws); + penv->recovery_in_progress = false; +} +EXPORT_SYMBOL(cnss_device_self_recovery); + +static int cnss_modem_notifier_nb(struct notifier_block *this, + unsigned long code, + void *ss_handle) +{ + struct cnss_wlan_driver *wdrv; + struct pci_dev *pdev; + + pr_debug("%s: Modem-Notify: event %lu\n", __func__, code); + + if (!penv) + return NOTIFY_DONE; + + if (code == SUBSYS_AFTER_POWERUP) + penv->modem_current_status = 1; + else if (code == SUBSYS_BEFORE_SHUTDOWN) + penv->modem_current_status = 0; + else + return NOTIFY_DONE; + + wdrv = penv->driver; + pdev = penv->pdev; + + if (!wdrv || !pdev || !wdrv->modem_status) + return NOTIFY_DONE; + + wdrv->modem_status(pdev, penv->modem_current_status); + + return NOTIFY_OK; +} + +static struct notifier_block mnb = { + .notifier_call = cnss_modem_notifier_nb, +}; + +static int cnss_init_dump_entry(void) +{ + struct msm_dump_entry dump_entry; + + if (!penv) + return -ENODEV; + + if (!penv->ramdump_dynamic) + return 0; + + penv->dump_data.addr = penv->ramdump_phys; + penv->dump_data.len = penv->ramdump_size; + penv->dump_data.version = CNSS_DUMP_FORMAT_VER; + penv->dump_data.magic = CNSS_DUMP_MAGIC_VER_V2; + strlcpy(penv->dump_data.name, CNSS_DUMP_NAME, + sizeof(penv->dump_data.name)); + dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN; + dump_entry.addr = virt_to_phys(&penv->dump_data); + + return msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); +} + +struct dma_iommu_mapping *cnss_smmu_get_mapping(void) +{ + if (!penv) { + pr_err("Invalid penv: data %pK\n", penv); + return NULL; + } + + return penv->smmu_mapping; +} +EXPORT_SYMBOL(cnss_smmu_get_mapping); + +int cnss_smmu_map(phys_addr_t paddr, uint32_t *iova_addr, size_t size) +{ + unsigned long iova; + size_t len; + int ret = 0; + + if (!iova_addr) { + pr_err("iova_addr is NULL, paddr %pa, size %zu\n", + &paddr, size); + return -EINVAL; + } + + len = roundup(size + paddr - rounddown(paddr, PAGE_SIZE), PAGE_SIZE); + iova = roundup(penv->smmu_iova_ipa_start, PAGE_SIZE); + + if (iova >= penv->smmu_iova_ipa_start + penv->smmu_iova_ipa_len) { + pr_err("No IOVA space to map, iova %lx, smmu_iova_ipa_start %pad, smmu_iova_ipa_len %zu\n", + iova, + &penv->smmu_iova_ipa_start, + penv->smmu_iova_ipa_len); + return -ENOMEM; + } + + ret = iommu_map(penv->smmu_mapping->domain, iova, + rounddown(paddr, PAGE_SIZE), len, + IOMMU_READ | IOMMU_WRITE); + if (ret) { + pr_err("PA to IOVA mapping failed, ret %d\n", ret); + return ret; + } + + penv->smmu_iova_ipa_start = iova + len; + *iova_addr = (uint32_t)(iova + paddr - rounddown(paddr, PAGE_SIZE)); + + return 0; +} +EXPORT_SYMBOL(cnss_smmu_map); + +static int cnss_probe(struct platform_device *pdev) +{ + int ret = 0; + struct esoc_desc *desc; + const char *client_desc; + struct device *dev = &pdev->dev; + u32 rc_num; + struct resource *res; + u32 ramdump_size = 0; + u32 smmu_iova_address[2]; + u32 smmu_iova_ipa[2]; + + if (penv) + return -ENODEV; + + penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL); + if (!penv) + return -ENOMEM; + + penv->pldev = pdev; + penv->esoc_desc = NULL; + + penv->gpio_info.name = WLAN_EN_GPIO_NAME; + penv->gpio_info.num = 0; + penv->gpio_info.state = false; + penv->gpio_info.init = false; + penv->gpio_info.prop = false; + penv->vreg_info.wlan_reg = NULL; + penv->vreg_info.state = false; + penv->pci_register_again = false; + mutex_init(&penv->fw_setup_stat_lock); + + ret = cnss_wlan_get_resources(pdev); + if (ret) + goto err_get_wlan_res; + + ret = cnss_configure_wlan_en_gpio(true); + if (ret) { + pr_err("%s: Failed to enable WLAN enable gpio\n", __func__); + goto err_get_rc; + } + + ret = of_property_read_u32(dev->of_node, "qcom,wlan-rc-num", &rc_num); + if (ret) { + pr_err("%s: Failed to find PCIe RC number\n", __func__); + goto err_get_rc; + } + + ret = cnss_msm_pcie_enumerate(rc_num); + if (ret) { + pr_err("%s: Failed to enable PCIe RC%x\n", __func__, rc_num); + goto err_pcie_enumerate; + } + + penv->pcie_link_state = true; + + penv->notify_modem_status = + of_property_read_bool(dev->of_node, + "qcom,notify-modem-status"); + + if (penv->notify_modem_status) { + ret = of_property_read_string_index(dev->of_node, "esoc-names", + 0, &client_desc); + if (ret) { + pr_debug("%s: esoc-names is not defined in DT, SKIP\n", + __func__); + } else { + desc = devm_register_esoc_client(dev, client_desc); + if (IS_ERR_OR_NULL(desc)) { + ret = PTR_RET(desc); + pr_err("%s: can't find esoc desc\n", __func__); + goto err_esoc_reg; + } + penv->esoc_desc = desc; + } + } + + penv->subsysdesc.name = "AR6320"; + penv->subsysdesc.owner = THIS_MODULE; + penv->subsysdesc.shutdown = cnss_shutdown; + penv->subsysdesc.powerup = cnss_powerup; + penv->subsysdesc.ramdump = cnss_ramdump; + penv->subsysdesc.crash_shutdown = cnss_crash_shutdown; + penv->subsysdesc.dev = &pdev->dev; + penv->subsys = subsys_register(&penv->subsysdesc); + if (IS_ERR(penv->subsys)) { + ret = PTR_ERR(penv->subsys); + goto err_subsys_reg; + } + + penv->subsys_handle = subsystem_get(penv->subsysdesc.name); + + if (of_property_read_bool(dev->of_node, "qcom,is-dual-wifi-enabled")) + penv->dual_wifi_info.is_dual_wifi_enabled = true; + + if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic", + &ramdump_size) == 0) { + penv->ramdump_addr = dma_alloc_coherent(&pdev->dev, + ramdump_size, + &penv->ramdump_phys, + GFP_KERNEL); + + if (penv->ramdump_addr) + penv->ramdump_size = ramdump_size; + penv->ramdump_dynamic = true; + } else { + res = platform_get_resource_byname(penv->pldev, + IORESOURCE_MEM, "ramdump"); + if (res) { + penv->ramdump_phys = res->start; + ramdump_size = resource_size(res); + penv->ramdump_addr = ioremap(penv->ramdump_phys, + ramdump_size); + + if (penv->ramdump_addr) + penv->ramdump_size = ramdump_size; + + penv->ramdump_dynamic = false; + } + } + + pr_debug("%s: ramdump addr: %p, phys: %pa\n", __func__, + penv->ramdump_addr, &penv->ramdump_phys); + + if (penv->ramdump_size == 0) { + pr_info("%s: CNSS ramdump will not be collected\n", __func__); + goto skip_ramdump; + } + + ret = cnss_init_dump_entry(); + if (ret) { + pr_err("%s: Dump table setup failed: %d\n", __func__, ret); + goto err_ramdump_create; + } + + penv->ramdump_dev = create_ramdump_device(penv->subsysdesc.name, + penv->subsysdesc.dev); + if (!penv->ramdump_dev) { + ret = -ENOMEM; + goto err_ramdump_create; + } + +skip_ramdump: + penv->modem_current_status = 0; + + if (penv->notify_modem_status) { + penv->modem_notify_handler = + subsys_notif_register_notifier(penv->esoc_desc ? + penv->esoc_desc->name : + "modem", &mnb); + if (IS_ERR(penv->modem_notify_handler)) { + ret = PTR_ERR(penv->modem_notify_handler); + pr_err("%s: Register notifier Failed\n", __func__); + goto err_notif_modem; + } + } + + if (of_property_read_u32_array(dev->of_node, + "qcom,wlan-smmu-iova-address", + smmu_iova_address, 2) == 0) { + penv->smmu_iova_start = smmu_iova_address[0]; + penv->smmu_iova_len = smmu_iova_address[1]; + } + + if (of_property_read_u32_array(dev->of_node, + "qcom,wlan-smmu-iova-ipa", + smmu_iova_ipa, 2) == 0) { + penv->smmu_iova_ipa_start = smmu_iova_ipa[0]; + penv->smmu_iova_ipa_len = smmu_iova_ipa[1]; + } + + if (of_property_read_bool(dev->of_node, + "qcom,smmu-s1-bypass")) + penv->smmu_s1_bypass = true; + + ret = pci_register_driver(&cnss_wlan_pci_driver); + if (ret) + goto err_pci_reg; + + penv->bus_scale_table = 0; +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + penv->bus_scale_table = msm_bus_cl_get_pdata(pdev); + + if (penv->bus_scale_table) { + penv->bus_client = + legacy_bus_register_client(penv->bus_scale_table); + + if (!penv->bus_client) { + pr_err("Failed to register with bus_scale client\n"); + goto err_bus_reg; + } + } +#endif + cnss_pm_wake_lock_init(&penv->ws, "cnss_wlock"); + + register_pm_notifier(&cnss_pm_notifier); + +#ifdef CONFIG_CNSS_MAC_BUG + /* 0-4K memory is reserved for QCA6174 to address a MAC HW bug. + * MAC would do an invalid pointer fetch based on the data + * that was read from 0 to 4K. So fill it with zero's (to an + * address for which PCIe RC honored the read without any errors). + */ + memset(phys_to_virt(0), 0, SZ_4K); +#endif + + ret = device_create_file(dev, &dev_attr_fw_image_setup); + if (ret) { + pr_err("cnss: fw_image_setup sys file creation failed\n"); + goto err_bus_reg; + } + pr_debug("cnss: Platform driver probed successfully.\n"); + return ret; + +err_bus_reg: +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + if (penv->bus_scale_table) + msm_bus_cl_clear_pdata(penv->bus_scale_table); +#endif + pci_unregister_driver(&cnss_wlan_pci_driver); + +err_pci_reg: + if (penv->notify_modem_status) + subsys_notif_unregister_notifier + (penv->modem_notify_handler, &mnb); + +err_notif_modem: + if (penv->ramdump_dev) + destroy_ramdump_device(penv->ramdump_dev); + +err_ramdump_create: + if (penv->ramdump_addr) { + if (penv->ramdump_dynamic) { + dma_free_coherent(&pdev->dev, penv->ramdump_size, + penv->ramdump_addr, + penv->ramdump_phys); + } else { + iounmap(penv->ramdump_addr); + } + } + + if (penv->subsys_handle) + subsystem_put(penv->subsys_handle); + + subsys_unregister(penv->subsys); + +err_subsys_reg: + if (penv->esoc_desc) + devm_unregister_esoc_client(&pdev->dev, penv->esoc_desc); + +err_esoc_reg: +err_pcie_enumerate: +err_get_rc: + cnss_configure_wlan_en_gpio(false); + cnss_wlan_release_resources(); + +err_get_wlan_res: + penv = NULL; + + return ret; +} + +static int cnss_remove(struct platform_device *pdev) +{ + unregister_pm_notifier(&cnss_pm_notifier); + device_remove_file(&pdev->dev, &dev_attr_fw_image_setup); + + cnss_pm_wake_lock_destroy(penv->ws); +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + if (penv->bus_client) + legacy_bus_unregister_client(penv->bus_client); + + if (penv->bus_scale_table) + msm_bus_cl_clear_pdata(penv->bus_scale_table); +#endif + if (penv->ramdump_addr) { + if (penv->ramdump_dynamic) { + dma_free_coherent(&pdev->dev, penv->ramdump_size, + penv->ramdump_addr, + penv->ramdump_phys); + } else { + iounmap(penv->ramdump_addr); + } + } + + cnss_configure_wlan_en_gpio(false); + if (penv->wlan_bootstrap_gpio > 0) + gpio_set_value(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_LOW); + cnss_wlan_release_resources(); + + return 0; +} + +static const struct of_device_id cnss_dt_match[] = { + {.compatible = "qcom,cnss"}, + {} +}; + +MODULE_DEVICE_TABLE(of, cnss_dt_match); + +static struct platform_driver cnss_driver = { + .probe = cnss_probe, + .remove = cnss_remove, + .driver = { + .name = "cnss", + .of_match_table = cnss_dt_match, +#ifdef CONFIG_CNSS_ASYNC + .probe_type = PROBE_PREFER_ASYNCHRONOUS, +#endif + }, +}; + +static int __init cnss_initialize(void) +{ + return platform_driver_register(&cnss_driver); +} + +static void __exit cnss_exit(void) +{ + struct platform_device *pdev = penv->pldev; + + if (penv->ramdump_dev) + destroy_ramdump_device(penv->ramdump_dev); + if (penv->notify_modem_status) + subsys_notif_unregister_notifier(penv->modem_notify_handler, + &mnb); + subsys_unregister(penv->subsys); + if (penv->esoc_desc) + devm_unregister_esoc_client(&pdev->dev, penv->esoc_desc); + platform_driver_unregister(&cnss_driver); +} + +void cnss_request_pm_qos_type(int latency_type, u32 qos_val) +{ + if (!penv) { + pr_err("%s: penv is NULL\n", __func__); + return; + } + + pm_qos_add_request(&penv->qos_request, latency_type, qos_val); +} +EXPORT_SYMBOL(cnss_request_pm_qos_type); + +void cnss_request_pm_qos(u32 qos_val) +{ + if (!penv) { + pr_err("%s: penv is NULL\n", __func__); + return; + } + + pm_qos_add_request(&penv->qos_request, PM_QOS_CPU_DMA_LATENCY, qos_val); +} +EXPORT_SYMBOL(cnss_request_pm_qos); + +void cnss_remove_pm_qos(void) +{ + if (!penv) { + pr_err("%s: penv is NULL\n", __func__); + return; + } + + pm_qos_remove_request(&penv->qos_request); +} +EXPORT_SYMBOL(cnss_remove_pm_qos); + +void cnss_pci_request_pm_qos_type(int latency_type, u32 qos_val) +{ + if (!penv) { + pr_err("%s: penv is NULL\n", __func__); + return; + } + + pm_qos_add_request(&penv->qos_request, latency_type, qos_val); +} +EXPORT_SYMBOL(cnss_pci_request_pm_qos_type); + +void cnss_pci_request_pm_qos(u32 qos_val) +{ + if (!penv) { + pr_err("%s: penv is NULL\n", __func__); + return; + } + + pm_qos_add_request(&penv->qos_request, PM_QOS_CPU_DMA_LATENCY, qos_val); +} +EXPORT_SYMBOL(cnss_pci_request_pm_qos); + +void cnss_pci_remove_pm_qos(void) +{ + if (!penv) { + pr_err("%s: penv is NULL\n", __func__); + return; + } + + pm_qos_remove_request(&penv->qos_request); +} +EXPORT_SYMBOL(cnss_pci_remove_pm_qos); + +int cnss_pci_request_bus_bandwidth(int bandwidth) +{ + int ret = 0; + + if (!penv) + return -ENODEV; + + if (!penv->bus_client) + return -EINVAL; +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + switch (bandwidth) { + case CNSS_BUS_WIDTH_NONE: + case CNSS_BUS_WIDTH_LOW: + case CNSS_BUS_WIDTH_MEDIUM: + case CNSS_BUS_WIDTH_HIGH: + ret = legacy_bus_client_update_request(penv->bus_client, + bandwidth); + if (!ret) { + penv->current_bandwidth_vote = bandwidth; + } else { + pr_err("%s: could not set bus bandwidth %d, ret = %d\n", + __func__, bandwidth, ret); + } + break; + + default: + pr_err("%s: Invalid request %d\n", __func__, bandwidth); + ret = -EINVAL; + } +#endif + return ret; +} + +int cnss_request_bus_bandwidth(int bandwidth) +{ + int ret = 0; + + if (!penv) + return -ENODEV; + + if (!penv->bus_client) + return -EINVAL; +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + switch (bandwidth) { + case CNSS_BUS_WIDTH_NONE: + case CNSS_BUS_WIDTH_LOW: + case CNSS_BUS_WIDTH_MEDIUM: + case CNSS_BUS_WIDTH_HIGH: + ret = legacy_bus_client_update_request(penv->bus_client, + bandwidth); + if (!ret) { + penv->current_bandwidth_vote = bandwidth; + } else { + pr_err("%s: could not set bus bandwidth %d, ret = %d\n", + __func__, bandwidth, ret); + } + break; + + default: + pr_err("%s: Invalid request %d\n", __func__, bandwidth); + ret = -EINVAL; + } +#endif + return ret; +} +EXPORT_SYMBOL(cnss_request_bus_bandwidth); + +int cnss_get_platform_cap(struct cnss_platform_cap *cap) +{ + if (!penv) + return -ENODEV; + + if (cap) + *cap = penv->cap; + + return 0; +} +EXPORT_SYMBOL(cnss_get_platform_cap); + +void cnss_set_driver_status(enum cnss_driver_status driver_status) +{ + penv->driver_status = driver_status; +} +EXPORT_SYMBOL(cnss_set_driver_status); + +int cnss_get_bmi_setup(void) +{ + if (!penv) + return -ENODEV; + + return penv->bmi_test; +} +EXPORT_SYMBOL(cnss_get_bmi_setup); + +#ifdef CONFIG_CNSS_SECURE_FW +int cnss_get_sha_hash(const u8 *data, u32 data_len, u8 *hash_idx, u8 *out) +{ + struct scatterlist sg; + struct hash_desc desc; + int ret = 0; + + if (!out) { + pr_err("memory for output buffer is not allocated\n"); + ret = -EINVAL; + goto end; + } + + desc.flags = CRYPTO_TFM_REQ_MAY_SLEEP; + desc.tfm = crypto_alloc_hash(hash_idx, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(desc.tfm)) { + pr_err("crypto_alloc_hash failed:%ld\n", PTR_ERR(desc.tfm)); + ret = PTR_ERR(desc.tfm); + goto end; + } + + sg_init_one(&sg, data, data_len); + ret = crypto_hash_digest(&desc, &sg, sg.length, out); + crypto_free_hash(desc.tfm); +end: + return ret; +} +EXPORT_SYMBOL(cnss_get_sha_hash); + +void *cnss_get_fw_ptr(void) +{ + if (!penv) + return NULL; + + return penv->fw_mem; +} +EXPORT_SYMBOL(cnss_get_fw_ptr); +#endif + +int cnss_auto_suspend(void) +{ + int ret = 0; + struct pci_dev *pdev; + + if (!penv || !penv->driver) + return -ENODEV; + + pdev = penv->pdev; + + if (penv->pcie_link_state) { + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + pci_disable_device(pdev); + ret = pci_set_power_state(pdev, PCI_D3hot); + if (ret) + pr_err("%s: Set D3Hot failed: %d\n", __func__, ret); + if (cnss_msm_pcie_pm_control(MSM_PCIE_SUSPEND, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS)) { + pr_err("%s: Failed to shutdown PCIe link\n", __func__); + ret = -EAGAIN; + goto out; + } + } + atomic_set(&penv->auto_suspended, 1); + penv->monitor_wake_intr = true; + penv->pcie_link_state = false; +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + legacy_bus_client_update_request(penv->bus_client, + CNSS_BUS_WIDTH_NONE); +#endif +out: + return ret; +} +EXPORT_SYMBOL(cnss_auto_suspend); + +int cnss_auto_resume(void) +{ + int ret = 0; + struct pci_dev *pdev; + + if (!penv || !penv->driver) + return -ENODEV; + + pdev = penv->pdev; + if (!penv->pcie_link_state) { + if (cnss_msm_pcie_pm_control(MSM_PCIE_RESUME, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS)) { + pr_err("%s: Failed to resume PCIe link\n", __func__); + ret = -EAGAIN; + goto out; + } + ret = pci_enable_device(pdev); + if (ret) + pr_err("%s: enable device failed: %d\n", __func__, ret); + penv->pcie_link_state = true; + } + + if (penv->saved_state) + cnss_pci_load_and_free_saved_state(pdev, &penv->saved_state); + + pci_restore_state(pdev); + pci_set_master(pdev); + + atomic_set(&penv->auto_suspended, 0); +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + legacy_bus_client_update_request(penv->bus_client, + penv->current_bandwidth_vote); +#endif +out: + return ret; +} +EXPORT_SYMBOL(cnss_auto_resume); + +int cnss_pm_runtime_request(struct device *dev, + enum cnss_runtime_request request) +{ + int ret = 0; + + switch (request) { + case CNSS_PM_RUNTIME_GET: + ret = pm_runtime_get(dev); + break; + case CNSS_PM_RUNTIME_PUT: + ret = pm_runtime_put(dev); + break; + case CNSS_PM_RUNTIME_MARK_LAST_BUSY: + pm_runtime_mark_last_busy(dev); + break; + case CNSS_PM_RUNTIME_RESUME: + ret = pm_runtime_resume(dev); + break; + case CNSS_PM_RUNTIME_PUT_AUTO: + ret = pm_runtime_put_autosuspend(dev); + break; + case CNSS_PM_RUNTIME_PUT_NOIDLE: + pm_runtime_put_noidle(dev); + break; + case CNSS_PM_REQUEST_RESUME: + ret = pm_request_resume(dev); + break; + case CNSS_PM_GET_NORESUME: + pm_runtime_get_noresume(dev); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL(cnss_pm_runtime_request); + +void cnss_runtime_init(struct device *dev, int auto_delay) +{ + pm_runtime_set_autosuspend_delay(dev, auto_delay); + pm_runtime_use_autosuspend(dev); + pm_runtime_allow(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_noidle(dev); + pm_suspend_ignore_children(dev, true); +} +EXPORT_SYMBOL(cnss_runtime_init); + +void cnss_runtime_exit(struct device *dev) +{ + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); +} +EXPORT_SYMBOL(cnss_runtime_exit); + +static void __cnss_set_pcie_monitor_intr(struct device *dev, bool val) +{ + penv->monitor_wake_intr = val; +} + +static void __cnss_set_auto_suspend(struct device *dev, int val) +{ + atomic_set(&penv->auto_suspended, val); +} + +static int __cnss_resume_link(struct device *dev, u32 flags) +{ + int ret; + struct pci_dev *pdev = to_pci_dev(dev); + u8 bus_num = cnss_get_pci_dev_bus_number(pdev); + + ret = cnss_msm_pcie_pm_control(MSM_PCIE_RESUME, bus_num, pdev, flags); + if (ret) + pr_err("%s: PCIe link resume failed with flags:%d bus_num:%d\n", + __func__, flags, bus_num); + + penv->pcie_link_state = true; + + return ret; +} + +static int __cnss_suspend_link(struct device *dev, u32 flags) +{ + struct pci_dev *pdev = to_pci_dev(dev); + u8 bus_num = cnss_get_pci_dev_bus_number(pdev); + int ret; + + if (!penv->pcie_link_state) + return 0; + + ret = cnss_msm_pcie_pm_control(MSM_PCIE_SUSPEND, bus_num, pdev, flags); + if (ret) { + pr_err("%s: Failed to suspend link\n", __func__); + return ret; + } + + penv->pcie_link_state = false; + + return ret; +} + +static int __cnss_pcie_recover_config(struct device *dev) +{ + int ret; + + ret = cnss_msm_pcie_recover_config(to_pci_dev(dev)); + if (ret) + pr_err("%s: PCIe Recover config failed\n", __func__); + + return ret; +} + +static int __cnss_event_reg(struct device *dev) +{ + int ret; + struct msm_pcie_register_event *event_reg; + + event_reg = &penv->event_reg; + + event_reg->events = MSM_PCIE_EVENT_LINKDOWN | + MSM_PCIE_EVENT_WAKEUP; + event_reg->user = to_pci_dev(dev); + event_reg->mode = MSM_PCIE_TRIGGER_CALLBACK; + event_reg->callback = cnss_pci_events_cb; + event_reg->options = MSM_PCIE_CONFIG_NO_RECOVERY; + + ret = cnss_msm_pcie_register_event(event_reg); + if (ret) + pr_err("%s: PCIe event register failed %d\n", __func__, ret); + + return ret; +} + +static void __cnss_event_dereg(struct device *dev) +{ + cnss_msm_pcie_deregister_event(&penv->event_reg); +} + +static struct pci_dev *__cnss_get_pcie_dev(struct device *dev) +{ + int ret; + struct pci_dev *pdev = penv->pdev; + + if (pdev) + return pdev; + + ret = pci_register_driver(&cnss_wlan_pci_driver); + if (ret) { + pr_err("%s: pci re-registration failed\n", __func__); + return NULL; + } + + pdev = penv->pdev; + + return pdev; +} + +static int __cnss_pcie_power_up(struct device *dev) +{ + struct cnss_wlan_vreg_info *vreg_info; + struct cnss_wlan_gpio_info *gpio_info; + int ret; + + vreg_info = &penv->vreg_info; + gpio_info = &penv->gpio_info; + + ret = cnss_wlan_vreg_set(vreg_info, true); + if (ret) { + pr_err("%s: WLAN VREG ON Failed\n", __func__); + return ret; + } + + msleep(POWER_ON_DELAY); + + if (penv->wlan_bootstrap_gpio > 0) { + gpio_set_value(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_HIGH); + msleep(WLAN_BOOTSTRAP_DELAY); + } + + cnss_configure_wlan_en_gpio(true); + return 0; +} + +static int __cnss_pcie_power_down(struct device *dev) +{ + struct cnss_wlan_vreg_info *vreg_info; + struct cnss_wlan_gpio_info *gpio_info; + int ret; + + vreg_info = &penv->vreg_info; + gpio_info = &penv->gpio_info; + + cnss_configure_wlan_en_gpio(false); + if (penv->wlan_bootstrap_gpio > 0) + gpio_set_value(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_LOW); + + ret = cnss_wlan_vreg_set(vreg_info, false); + if (ret) + pr_err("%s: Failed to turn off 3.3V regulator\n", __func__); + + return ret; +} + +static int __cnss_suspend_link_state(struct device *dev) +{ + int ret; + struct pci_dev *pdev = to_pci_dev(dev); + int link_ind; + + if (!penv->pcie_link_state) { + pr_debug("%s: Link is already suspended\n", __func__); + return 0; + } + + link_ind = penv->pcie_link_down_ind; + + if (!link_ind) + pci_save_state(pdev); + + penv->saved_state = link_ind ? NULL : cnss_pci_store_saved_state(pdev); + + ret = link_ind ? __cnss_suspend_link(dev, PM_OPTIONS_SUSPEND_LINK_DOWN) + : __cnss_suspend_link(dev, PM_OPTIONS); + if (ret) { + pr_err("%s: Link Suspend failed in state:%s\n", __func__, + link_ind ? "LINK_DOWN" : "LINK_ACTIVE"); + return ret; + } + + penv->pcie_link_state = false; + + return 0; +} + +static int __cnss_restore_pci_config_space(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + int ret = 0; + + if (penv->saved_state) + ret = cnss_pci_load_and_free_saved_state(pdev, + &penv->saved_state); + pci_restore_state(pdev); + + return ret; +} + +static int __cnss_resume_link_state(struct device *dev) +{ + int ret; + int link_ind; + + if (penv->pcie_link_state) { + pr_debug("%s: Link is already in active state\n", __func__); + return 0; + } + + link_ind = penv->pcie_link_down_ind; + + ret = link_ind ? __cnss_resume_link(dev, PM_OPTIONS_RESUME_LINK_DOWN) : + __cnss_resume_link(dev, PM_OPTIONS); + + if (ret) { + pr_err("%s: Resume Link failed in link state:%s\n", __func__, + link_ind ? "LINK_DOWN" : "LINK_ACTIVE"); + return ret; + } + + penv->pcie_link_state = true; + + ret = link_ind ? __cnss_pcie_recover_config(dev) : + __cnss_restore_pci_config_space(dev); + + if (ret) { + pr_err("%s: Link Recovery Config Failed link_state:%s\n", + __func__, link_ind ? "LINK_DOWN" : "LINK_ACTIVE"); + penv->pcie_link_state = false; + return ret; + } + + penv->pcie_link_down_ind = false; + return ret; +} + +int cnss_pcie_power_up(struct device *dev) +{ + int ret; + struct pci_dev *pdev; + + if (!penv) { + pr_err("%s: platform data is NULL\n", __func__); + return -ENODEV; + } + + ret = __cnss_pcie_power_up(dev); + if (ret) { + pr_err("%s: Power UP Failed\n", __func__); + return ret; + } + + pdev = __cnss_get_pcie_dev(dev); + if (!pdev) { + pr_err("%s: PCIe Dev is NULL\n", __func__); + goto power_down; + } + + ret = __cnss_event_reg(dev); + + if (ret) + pr_err("%s: PCIe event registration failed\n", __func__); + + ret = __cnss_resume_link_state(dev); + + if (ret) { + pr_err("%s: Link Bring Up Failed\n", __func__); + goto event_dereg; + } + + __cnss_set_pcie_monitor_intr(dev, true); + + return ret; + +event_dereg: + __cnss_event_dereg(dev); +power_down: + __cnss_pcie_power_down(dev); + pr_err("%s: Device Power Up Failed Fatal Error\n", __func__); + return ret; +} + +static void __cnss_vote_bus_width(struct device *dev, u32 option) +{ +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + if (penv->bus_client) + legacy_bus_client_update_request(penv->bus_client, option); +#endif +} + +int cnss_pcie_power_down(struct device *dev) +{ + int ret; + struct pci_dev *pdev = to_pci_dev(dev); + + if (!penv) { + pr_err("%s: Invalid Platform data\n", __func__); + return -ENODEV; + } + + if (!pdev) { + pr_err("%s: Invalid Pdev, Cut Power to device\n", __func__); + __cnss_pcie_power_down(dev); + return -ENODEV; + } + + __cnss_vote_bus_width(dev, CNSS_BUS_WIDTH_NONE); + __cnss_event_dereg(dev); + + ret = __cnss_suspend_link_state(dev); + + if (ret) { + pr_err("%s: Suspend Link failed\n", __func__); + return ret; + } + + __cnss_set_pcie_monitor_intr(dev, false); + __cnss_set_auto_suspend(dev, 0); + + ret = __cnss_pcie_power_down(dev); + if (ret) + pr_err("%s: Power Down Failed\n", __func__); + + return ret; +} + +module_init(cnss_initialize); +module_exit(cnss_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CNSS Driver"); diff --git a/drivers/net/wireless/cnss/cnss_sdio.c b/drivers/net/wireless/cnss/cnss_sdio.c new file mode 100644 index 000000000000..8b6179b6abab --- /dev/null +++ b/drivers/net/wireless/cnss/cnss_sdio.c @@ -0,0 +1,1598 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. */ + +#define pr_fmt(fmt) "cnss_sdio:%s:%d:: " fmt, __func__, __LINE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cnss_common.h" +#include +#include +#include + +#define WLAN_VREG_NAME "vdd-wlan" +#define WLAN_VREG_DSRC_NAME "vdd-wlan-dsrc" +#define WLAN_VREG_IO_NAME "vdd-wlan-io" +#define WLAN_VREG_XTAL_NAME "vdd-wlan-xtal" +#define WLAN_GPIO_CAPTSF_NAME "qcom,cap-tsf-gpio" + +#define WLAN_VREG_IO_MAX 1800000 +#define WLAN_VREG_IO_MIN 1800000 +#define WLAN_VREG_XTAL_MAX 3465000 +#define WLAN_VREG_XTAL_MIN 1620000 +#define WLAN_VREG_XTAL_TYP 1800000 +#define POWER_ON_DELAY 4 + +/* Values for Dynamic Ramdump Collection*/ +#define CNSS_DUMP_FORMAT_VER 0x11 +#define CNSS_DUMP_MAGIC_VER_V2 0x42445953 +#define CNSS_DUMP_NAME "CNSS_WLAN_SDIO" +#define CNSS_PINCTRL_SLEEP_STATE "sleep" +#define CNSS_PINCTRL_ACTIVE_STATE "active" + +struct cnss_sdio_regulator { + struct regulator *wlan_io; + struct regulator *wlan_xtal; + struct regulator *wlan_vreg; + struct regulator *wlan_vreg_dsrc; +}; + +struct cnss_sdio_info { + struct cnss_sdio_wlan_driver *wdrv; + struct sdio_func *func; + struct mmc_card *card; + struct mmc_host *host; + struct device *dev; + const struct sdio_device_id *id; + bool skip_wlan_en_toggle; + bool cnss_hw_state; + struct cnss_cap_tsf_info cap_tsf_info; +}; + +struct cnss_ssr_info { + struct subsys_device *subsys; + struct subsys_desc subsysdesc; + void *subsys_handle; + struct ramdump_device *ramdump_dev; + unsigned long ramdump_size; + void *ramdump_addr; + phys_addr_t ramdump_phys; + struct msm_dump_data dump_data; + bool ramdump_dynamic; + char subsys_name[10]; +}; + +struct cnss_wlan_pinctrl_info { + bool is_antenna_shared; + struct pinctrl *pinctrl; + struct pinctrl_state *sleep; + struct pinctrl_state *active; +}; + +struct cnss_sdio_bus_bandwidth { + struct msm_bus_scale_pdata *bus_scale_table; + u32 bus_client; + int current_bandwidth_vote; +}; + +static struct cnss_sdio_data { + struct cnss_sdio_regulator regulator; + struct platform_device *pdev; + struct cnss_sdio_info cnss_sdio_info; + struct cnss_ssr_info ssr_info; + struct pm_qos_request qos_request; + struct cnss_wlan_pinctrl_info pinctrl_info; + struct cnss_sdio_bus_bandwidth bus_bandwidth; + struct cnss_dev_platform_ops platform_ops; +} *cnss_pdata; + +#define WLAN_RECOVERY_DELAY 1 +/* cnss sdio subsytem device name, required property */ +#define CNSS_SUBSYS_NAME_KEY "subsys-name" + +/* SDIO manufacturer ID and Codes */ +#define MANUFACTURER_ID_AR6320_BASE 0x500 +#define MANUFACTURER_ID_QCA9377_BASE 0x700 +#define MANUFACTURER_ID_QCA9379_BASE 0x800 +#define MANUFACTURER_CODE 0x271 + +static const struct sdio_device_id ar6k_id_table[] = { + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x0))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x1))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x2))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x3))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x4))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x5))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x6))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x7))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x8))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x9))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xA))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xB))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xC))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xD))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xE))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xF))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x0))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x1))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x2))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x3))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x4))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x5))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x6))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x7))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x8))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x9))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xA))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xB))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xC))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xD))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xE))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xF))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x0))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x1))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x2))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x3))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x4))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x5))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x6))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x7))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x8))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x9))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0xA))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0xB))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0xC))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0xD))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0xE))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0xF))}, + {}, +}; +MODULE_DEVICE_TABLE(sdio, ar6k_id_table); + +void cnss_sdio_request_pm_qos_type(int latency_type, u32 qos_val) +{ + if (!cnss_pdata) + return; + + pr_debug("PM QoS value: %d\n", qos_val); + pm_qos_add_request(&cnss_pdata->qos_request, latency_type, qos_val); +} +EXPORT_SYMBOL(cnss_sdio_request_pm_qos_type); + +int cnss_sdio_request_bus_bandwidth(int bandwidth) +{ + int ret = 0; + struct cnss_sdio_bus_bandwidth *bus_bandwidth; + + if (!cnss_pdata) + return -ENODEV; + + bus_bandwidth = &cnss_pdata->bus_bandwidth; + if (!bus_bandwidth->bus_client) + return -EINVAL; +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + switch (bandwidth) { + case CNSS_BUS_WIDTH_NONE: + case CNSS_BUS_WIDTH_LOW: + case CNSS_BUS_WIDTH_MEDIUM: + case CNSS_BUS_WIDTH_HIGH: + ret = legacy_bus_client_update_request(bus_bandwidth->bus_client, + bandwidth); + if (!ret) { + bus_bandwidth->current_bandwidth_vote = bandwidth; + } else { + pr_debug("could not set bus bandwidth %d, ret = %d\n", + bandwidth, ret); + } + break; + default: + pr_debug("Invalid request %d\n", bandwidth); + ret = -EINVAL; + } +#endif + return ret; +} + +void cnss_sdio_request_pm_qos(u32 qos_val) +{ + if (!cnss_pdata) + return; + + pr_debug("PM QoS value: %d\n", qos_val); + pm_qos_add_request(&cnss_pdata->qos_request, + PM_QOS_CPU_DMA_LATENCY, qos_val); +} +EXPORT_SYMBOL(cnss_sdio_request_pm_qos); + +void cnss_sdio_remove_pm_qos(void) +{ + if (!cnss_pdata) + return; + + pm_qos_remove_request(&cnss_pdata->qos_request); + pr_debug("PM QoS removed\n"); +} +EXPORT_SYMBOL(cnss_sdio_remove_pm_qos); + +static int cnss_put_hw_resources(struct device *dev) +{ + int ret = -EINVAL; + struct cnss_sdio_info *info; + struct mmc_host *host; + + if (!cnss_pdata) + return ret; + + info = &cnss_pdata->cnss_sdio_info; + + if (info->skip_wlan_en_toggle) { + pr_debug("HW doesn't support wlan toggling\n"); + return 0; + } + + if (!info->cnss_hw_state) { + pr_debug("HW resources are already released\n"); + return 0; + } + + host = info->host; + + if (!host) { + pr_err("MMC host is invalid\n"); + return ret; + } +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + ret = mmc_power_save_host(host); + if (ret) { + pr_err("Failed to Power Save Host err:%d\n", + ret); + return ret; + } +#endif + + if (cnss_pdata->regulator.wlan_vreg) + regulator_disable(cnss_pdata->regulator.wlan_vreg); + else + pr_debug("wlan_vreg regulator is invalid\n"); + + info->cnss_hw_state = false; + + return ret; +} + +static int cnss_get_hw_resources(struct device *dev) +{ + int ret = 0; + struct mmc_host *host; + struct cnss_sdio_info *info; + + if (!cnss_pdata) + return ret; + + info = &cnss_pdata->cnss_sdio_info; + + if (info->skip_wlan_en_toggle) { + pr_debug("HW doesn't support wlan toggling\n"); + return 0; + } + + if (info->cnss_hw_state) { + pr_debug("HW resources are already active\n"); + return 0; + } + + host = info->host; + + if (!host) { + pr_err("MMC Host is Invalid; Enumeration Failed\n"); + return ret; + } + + if (cnss_pdata->regulator.wlan_vreg) { + ret = regulator_enable(cnss_pdata->regulator.wlan_vreg); + if (ret) { + pr_err("Failed to enable wlan vreg\n"); + return ret; + } + } else { + pr_debug("wlan_vreg regulator is invalid\n"); + } +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + ret = mmc_power_restore_host(host); + if (ret) { + pr_err("Failed to restore host power ret:%d\n", + ret); + if (cnss_pdata->regulator.wlan_vreg) + regulator_disable(cnss_pdata->regulator.wlan_vreg); + return ret; + } +#endif + info->cnss_hw_state = true; + return ret; +} + +static int cnss_sdio_shutdown(const struct subsys_desc *subsys, bool force_stop) +{ + struct cnss_sdio_info *cnss_info; + struct cnss_sdio_wlan_driver *wdrv; + int ret = 0; + + if (!cnss_pdata) + return -ENODEV; + + cnss_info = &cnss_pdata->cnss_sdio_info; + wdrv = cnss_info->wdrv; + if (!wdrv) + return 0; + if (!wdrv->shutdown) + return 0; + + wdrv->shutdown(cnss_info->func); + ret = cnss_put_hw_resources(cnss_info->dev); + + if (ret) + pr_err("Failed to put hw resources\n"); + + return ret; +} + +static int cnss_sdio_powerup(const struct subsys_desc *subsys) +{ + struct cnss_sdio_info *cnss_info; + struct cnss_sdio_wlan_driver *wdrv; + int ret = 0; + + if (!cnss_pdata) + return -ENODEV; + + cnss_info = &cnss_pdata->cnss_sdio_info; + wdrv = cnss_info->wdrv; + + if (!wdrv) + return 0; + + if (!wdrv->reinit) + return 0; + + ret = cnss_get_hw_resources(cnss_info->dev); + if (ret) { + pr_err("Failed to power up HW\n"); + return ret; + } + + ret = wdrv->reinit(cnss_info->func, cnss_info->id); + if (ret) + pr_err("wlan reinit error=%d\n", ret); + + return ret; +} + +static void cnss_sdio_crash_shutdown(const struct subsys_desc *subsys) +{ + struct cnss_sdio_info *cnss_info; + struct cnss_sdio_wlan_driver *wdrv; + + if (!cnss_pdata) + return; + + cnss_info = &cnss_pdata->cnss_sdio_info; + wdrv = cnss_info->wdrv; + if (wdrv && wdrv->crash_shutdown) + wdrv->crash_shutdown(cnss_info->func); +} + +static int cnss_sdio_ramdump(int enable, const struct subsys_desc *subsys) +{ + struct cnss_ssr_info *ssr_info; + struct ramdump_segment segment; + int ret; + + if (!cnss_pdata) + return -ENODEV; + + if (!cnss_pdata->ssr_info.ramdump_size) + return -ENOENT; + + if (!enable) + return 0; + + ssr_info = &cnss_pdata->ssr_info; + + memset(&segment, 0, sizeof(segment)); + segment.v_address = ssr_info->ramdump_addr; + segment.size = ssr_info->ramdump_size; + ret = do_ramdump(ssr_info->ramdump_dev, &segment, 1); + if (ret) + pr_err("do_ramdump failed error=%d\n", ret); + return ret; +} + +static int cnss_subsys_init(void) +{ + struct cnss_ssr_info *ssr_info; + int ret = 0; + + if (!cnss_pdata) + return -ENODEV; + + ssr_info = &cnss_pdata->ssr_info; + ssr_info->subsysdesc.name = ssr_info->subsys_name; + ssr_info->subsysdesc.owner = THIS_MODULE; + ssr_info->subsysdesc.shutdown = cnss_sdio_shutdown; + ssr_info->subsysdesc.powerup = cnss_sdio_powerup; + ssr_info->subsysdesc.ramdump = cnss_sdio_ramdump; + ssr_info->subsysdesc.crash_shutdown = cnss_sdio_crash_shutdown; + ssr_info->subsysdesc.dev = &cnss_pdata->pdev->dev; + ssr_info->subsys = subsys_register(&ssr_info->subsysdesc); + if (IS_ERR(ssr_info->subsys)) { + ret = PTR_ERR(ssr_info->subsys); + ssr_info->subsys = NULL; + dev_err(&cnss_pdata->pdev->dev, "Failed to subsys_register error=%d\n", + ret); + goto err_subsys_reg; + } + ssr_info->subsys_handle = subsystem_get(ssr_info->subsysdesc.name); + if (IS_ERR(ssr_info->subsys_handle)) { + ret = PTR_ERR(ssr_info->subsys_handle); + ssr_info->subsys_handle = NULL; + dev_err(&cnss_pdata->pdev->dev, "Failed to subsystem_get error=%d\n", + ret); + goto err_subsys_get; + } + return 0; +err_subsys_get: + subsys_unregister(ssr_info->subsys); + ssr_info->subsys = NULL; +err_subsys_reg: + return ret; +} + +static void cnss_subsys_exit(void) +{ + struct cnss_ssr_info *ssr_info; + + if (!cnss_pdata) + return; + + ssr_info = &cnss_pdata->ssr_info; + if (ssr_info->subsys_handle) + subsystem_put(ssr_info->subsys_handle); + ssr_info->subsys_handle = NULL; + if (ssr_info->subsys) + subsys_unregister(ssr_info->subsys); + ssr_info->subsys = NULL; +} + +static int cnss_configure_dump_table(struct cnss_ssr_info *ssr_info) +{ + struct msm_dump_entry dump_entry; + int ret; + + ssr_info->dump_data.addr = ssr_info->ramdump_phys; + ssr_info->dump_data.len = ssr_info->ramdump_size; + ssr_info->dump_data.version = CNSS_DUMP_FORMAT_VER; + ssr_info->dump_data.magic = CNSS_DUMP_MAGIC_VER_V2; + strlcpy(ssr_info->dump_data.name, CNSS_DUMP_NAME, + sizeof(ssr_info->dump_data.name)); + + dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN; + dump_entry.addr = virt_to_phys(&ssr_info->dump_data); + + ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); + if (ret) + pr_err("Dump table setup failed: %d\n", ret); + + return ret; +} + +static int cnss_configure_ramdump(void) +{ + struct cnss_ssr_info *ssr_info; + int ret = 0; + struct resource *res; + const char *name; + u32 ramdump_size = 0; + struct device *dev; + + if (!cnss_pdata) + return -ENODEV; + + dev = &cnss_pdata->pdev->dev; + + ssr_info = &cnss_pdata->ssr_info; + + ret = of_property_read_string(dev->of_node, CNSS_SUBSYS_NAME_KEY, + &name); + if (ret) { + pr_err("cnss missing DT key '%s'\n", + CNSS_SUBSYS_NAME_KEY); + ret = -ENODEV; + goto err_subsys_name_query; + } + + strlcpy(ssr_info->subsys_name, name, sizeof(ssr_info->subsys_name)); + + if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic", + &ramdump_size) == 0) { + ssr_info->ramdump_addr = + dma_alloc_coherent(dev, ramdump_size, + &ssr_info->ramdump_phys, + GFP_KERNEL); + if (ssr_info->ramdump_addr) + ssr_info->ramdump_size = ramdump_size; + ssr_info->ramdump_dynamic = true; + } else { + res = platform_get_resource_byname(cnss_pdata->pdev, + IORESOURCE_MEM, "ramdump"); + if (res) { + ssr_info->ramdump_phys = res->start; + ramdump_size = resource_size(res); + ssr_info->ramdump_addr = ioremap(ssr_info->ramdump_phys, + ramdump_size); + if (ssr_info->ramdump_addr) + ssr_info->ramdump_size = ramdump_size; + ssr_info->ramdump_dynamic = false; + } + } + + pr_info("ramdump addr: %p, phys: %pa subsys:'%s'\n", + ssr_info->ramdump_addr, &ssr_info->ramdump_phys, + ssr_info->subsys_name); + + if (ssr_info->ramdump_size == 0) { + pr_info("CNSS ramdump will not be collected\n"); + return 0; + } + + if (ssr_info->ramdump_dynamic) { + ret = cnss_configure_dump_table(ssr_info); + if (ret) + goto err_configure_dump_table; + } + + ssr_info->ramdump_dev = create_ramdump_device(ssr_info->subsys_name, + dev); + if (!ssr_info->ramdump_dev) { + ret = -ENOMEM; + pr_err("ramdump dev create failed: error=%d\n", + ret); + goto err_configure_dump_table; + } + + return 0; + +err_configure_dump_table: + if (ssr_info->ramdump_dynamic) + dma_free_coherent(dev, ssr_info->ramdump_size, + ssr_info->ramdump_addr, + ssr_info->ramdump_phys); + else + iounmap(ssr_info->ramdump_addr); + + ssr_info->ramdump_addr = NULL; + ssr_info->ramdump_size = 0; +err_subsys_name_query: + return ret; +} + +static void cnss_ramdump_cleanup(void) +{ + struct cnss_ssr_info *ssr_info; + struct device *dev; + + if (!cnss_pdata) + return; + + dev = &cnss_pdata->pdev->dev; + ssr_info = &cnss_pdata->ssr_info; + if (ssr_info->ramdump_addr) { + if (ssr_info->ramdump_dynamic) + dma_free_coherent(dev, ssr_info->ramdump_size, + ssr_info->ramdump_addr, + ssr_info->ramdump_phys); + else + iounmap(ssr_info->ramdump_addr); + } + + ssr_info->ramdump_addr = NULL; + if (ssr_info->ramdump_dev) + destroy_ramdump_device(ssr_info->ramdump_dev); + ssr_info->ramdump_dev = NULL; +} + +void *cnss_sdio_get_virt_ramdump_mem(unsigned long *size) +{ + if (!cnss_pdata || !cnss_pdata->pdev) + return NULL; + + *size = cnss_pdata->ssr_info.ramdump_size; + + return cnss_pdata->ssr_info.ramdump_addr; +} + +void cnss_sdio_device_self_recovery(void) +{ + cnss_sdio_shutdown(NULL, false); + msleep(WLAN_RECOVERY_DELAY); + cnss_sdio_powerup(NULL); +} + +void cnss_sdio_device_crashed(void) +{ + struct cnss_ssr_info *ssr_info; + + if (!cnss_pdata) + return; + ssr_info = &cnss_pdata->ssr_info; + if (ssr_info->subsys) { + subsys_set_crash_status(ssr_info->subsys, true); + subsystem_restart_dev(ssr_info->subsys); + } +} + +static void cnss_sdio_recovery_work_handler(struct work_struct *recovery) +{ + cnss_sdio_device_self_recovery(); +} + +DECLARE_WORK(cnss_sdio_recovery_work, cnss_sdio_recovery_work_handler); + +void cnss_sdio_schedule_recovery_work(void) +{ + schedule_work(&cnss_sdio_recovery_work); +} + +/** + * cnss_get_restart_level() - cnss get restart level API + * + * Wlan sdio function driver uses this API to get the current + * subsystem restart level. + * + * Return: CNSS_RESET_SOC - "SYSTEM", restart system + * CNSS_RESET_SUBSYS_COUPLED - "RELATED",restart subsystem + */ +int cnss_get_restart_level(void) +{ + struct cnss_ssr_info *ssr_info; + int level = 0; + + if (!cnss_pdata) + return CNSS_RESET_SOC; + ssr_info = &cnss_pdata->ssr_info; + if (!ssr_info->subsys) + return CNSS_RESET_SOC; +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + level = subsys_get_restart_level(ssr_info->subsys); +#endif + switch (level) { + case RESET_SOC: + return CNSS_RESET_SOC; + case RESET_SUBSYS_COUPLED: + return CNSS_RESET_SUBSYS_COUPLED; + default: + return CNSS_RESET_SOC; + } +} +EXPORT_SYMBOL(cnss_get_restart_level); + +static inline int cnss_get_tsf_cap_irq(struct device *dev) +{ + int irq = -EINVAL; + int gpio; + + if (!dev) + return -ENODEV; + + gpio = of_get_named_gpio(dev->of_node, WLAN_GPIO_CAPTSF_NAME, 0); + if (gpio >= 0) + irq = gpio_to_irq(gpio); + + return irq; +} + +static int cnss_sdio_register_tsf_captured_handler(irq_handler_t handler, + void *ctx) +{ + struct cnss_cap_tsf_info *tsf_info; + + if (!cnss_pdata) + return -ENODEV; + + tsf_info = &cnss_pdata->cnss_sdio_info.cap_tsf_info; + if (tsf_info->irq_num < 0) + return -EOPNOTSUPP; + + tsf_info->irq_handler = handler; + tsf_info->context = ctx; + return 0; +} + +static int cnss_sdio_unregister_tsf_captured_handler(void *ctx) +{ + struct cnss_cap_tsf_info *tsf_info; + + if (!cnss_pdata) + return -ENODEV; + + tsf_info = &cnss_pdata->cnss_sdio_info.cap_tsf_info; + if (tsf_info->irq_num < 0) + return -EOPNOTSUPP; + + if (ctx == tsf_info->context) { + tsf_info->irq_handler = NULL; + tsf_info->context = NULL; + } + return 0; +} + +static irqreturn_t cnss_sdio_tsf_captured_handler(int irq, void *ctx) +{ + struct cnss_cap_tsf_info *tsf_info; + + if (!cnss_pdata) + return IRQ_HANDLED; + + tsf_info = &cnss_pdata->cnss_sdio_info.cap_tsf_info; + if (tsf_info->irq_num < 0 || tsf_info->irq_num != irq || + !tsf_info->irq_handler || !tsf_info->context) + return IRQ_HANDLED; + + return tsf_info->irq_handler(irq, tsf_info->context); +} + +static void cnss_sdio_tsf_init(struct device *dev, + struct cnss_cap_tsf_info *tsf_info) +{ + int ret, irq; + + tsf_info->irq_num = -EINVAL; + tsf_info->irq_handler = NULL; + tsf_info->context = NULL; + + irq = cnss_get_tsf_cap_irq(dev); + if (irq < 0) { + dev_err(dev, "%s: fail to get irq: %d\n", __func__, irq); + return; + } + + ret = request_irq(irq, cnss_sdio_tsf_captured_handler, + IRQF_SHARED | IRQF_TRIGGER_RISING, dev_name(dev), + (void *)tsf_info); + dev_err(dev, "%s: request irq[%d] for dev: %s, result: %d\n", + __func__, irq, dev_name(dev), ret); + if (!ret) + tsf_info->irq_num = irq; +} + +static void cnss_sdio_tsf_deinit(struct cnss_cap_tsf_info *tsf_info) +{ + int irq = tsf_info->irq_num; + + if (irq < 0) + return; + + free_irq(irq, (void *)tsf_info); + + tsf_info->irq_num = -EINVAL; + tsf_info->irq_handler = NULL; + tsf_info->context = NULL; +} + +static void cnss_sdio_set_platform_ops(struct device *dev) +{ + struct cnss_dev_platform_ops *pf_ops = &cnss_pdata->platform_ops; + + pf_ops->power_up = cnss_sdio_power_up; + pf_ops->power_down = cnss_sdio_power_down; + pf_ops->device_crashed = cnss_sdio_device_crashed; + pf_ops->get_virt_ramdump_mem = cnss_sdio_get_virt_ramdump_mem; + pf_ops->device_self_recovery = cnss_sdio_device_self_recovery; + pf_ops->get_wlan_mac_address = cnss_sdio_get_wlan_mac_address; + pf_ops->set_wlan_mac_address = cnss_sdio_set_wlan_mac_address; + pf_ops->schedule_recovery_work = cnss_sdio_schedule_recovery_work; + pf_ops->request_bus_bandwidth = cnss_sdio_request_bus_bandwidth; + pf_ops->register_tsf_captured_handler = + cnss_sdio_register_tsf_captured_handler; + pf_ops->unregister_tsf_captured_handler = + cnss_sdio_unregister_tsf_captured_handler; + dev->platform_data = pf_ops; +} + +static int cnss_sdio_wlan_inserted(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct cnss_sdio_info *info; + + if (!cnss_pdata) + return -ENODEV; + + info = &cnss_pdata->cnss_sdio_info; + + info->func = func; + info->card = func->card; + info->host = func->card->host; + info->id = id; + info->dev = &func->dev; + cnss_sdio_set_platform_ops(info->dev); + + cnss_put_hw_resources(cnss_pdata->cnss_sdio_info.dev); + + pr_info("SDIO Device is Probed\n"); + return 0; +} + +static void cnss_sdio_wlan_removed(struct sdio_func *func) +{ + struct cnss_sdio_info *info; + + if (!cnss_pdata) + return; + + info = &cnss_pdata->cnss_sdio_info; + + info->host = NULL; + info->card = NULL; + info->func = NULL; + info->id = NULL; +} + +#if defined(CONFIG_PM) +static int cnss_sdio_wlan_suspend(struct device *dev) +{ + struct cnss_sdio_wlan_driver *wdrv; +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + struct cnss_sdio_bus_bandwidth *bus_bandwidth; +#endif + struct sdio_func *func; + + int error = 0; + + if (!cnss_pdata) + return -ENODEV; + +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + bus_bandwidth = &cnss_pdata->bus_bandwidth; + if (bus_bandwidth->bus_client) { + legacy_bus_client_update_request(bus_bandwidth->bus_client, + CNSS_BUS_WIDTH_NONE); + } +#endif + func = cnss_pdata->cnss_sdio_info.func; + wdrv = cnss_pdata->cnss_sdio_info.wdrv; + if (!wdrv) { + /* This can happen when no wlan driver loaded (no register to + * platform driver). + */ + sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + pr_debug("wlan driver not registered\n"); + return 0; + } + if (wdrv->suspend) { + error = wdrv->suspend(dev); + if (error) + pr_err("wlan suspend failed error=%d\n", error); + } + + return error; +} + +static int cnss_sdio_wlan_resume(struct device *dev) +{ + struct cnss_sdio_wlan_driver *wdrv; +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + struct cnss_sdio_bus_bandwidth *bus_bandwidth; +#endif + int error = 0; + + if (!cnss_pdata) + return -ENODEV; + +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + bus_bandwidth = &cnss_pdata->bus_bandwidth; + if (bus_bandwidth->bus_client) { + legacy_bus_client_update_request(bus_bandwidth->bus_client, + bus_bandwidth->current_bandwidth_vote); + } +#endif + + wdrv = cnss_pdata->cnss_sdio_info.wdrv; + if (!wdrv) { + /* This can happen when no wlan driver loaded (no register to + * platform driver). + */ + pr_debug("wlan driver not registered\n"); + return 0; + } + if (wdrv->resume) { + error = wdrv->resume(dev); + if (error) + pr_err("wlan resume failed error=%d\n", error); + } + return error; +} +#endif + +#if defined(CONFIG_PM) +static const struct dev_pm_ops cnss_ar6k_device_pm_ops = { + .suspend = cnss_sdio_wlan_suspend, + .resume = cnss_sdio_wlan_resume, +}; +#endif /* CONFIG_PM */ + +static struct sdio_driver cnss_ar6k_driver = { + .name = "cnss_ar6k_wlan", + .id_table = ar6k_id_table, + .probe = cnss_sdio_wlan_inserted, + .remove = cnss_sdio_wlan_removed, +#if defined(CONFIG_PM) + .drv = { + .pm = &cnss_ar6k_device_pm_ops, + } +#endif +}; + +static int cnss_set_pinctrl_state(struct cnss_sdio_data *pdata, bool state) +{ + struct cnss_wlan_pinctrl_info *info = &pdata->pinctrl_info; + + if (!info->is_antenna_shared) + return 0; + + if (!info->pinctrl) + return -EIO; + + return state ? pinctrl_select_state(info->pinctrl, info->active) : + pinctrl_select_state(info->pinctrl, info->sleep); +} + +int cnss_sdio_configure_spdt(bool state) +{ + if (!cnss_pdata) + return -ENODEV; + + return cnss_set_pinctrl_state(cnss_pdata, state); +} +EXPORT_SYMBOL(cnss_sdio_configure_spdt); + +/** + * cnss_sdio_wlan_register_driver() - cnss wlan register API + * @driver: sdio wlan driver interface from wlan driver. + * + * wlan sdio function driver uses this API to register callback + * functions to cnss_sido platform driver. The callback will + * be invoked by corresponding wrapper function of this cnss + * platform driver. + */ +int cnss_sdio_wlan_register_driver(struct cnss_sdio_wlan_driver *driver) +{ + struct cnss_sdio_info *cnss_info; + struct device *dev; + int error = -EINVAL; + + if (!cnss_pdata) + return -ENODEV; + + cnss_info = &cnss_pdata->cnss_sdio_info; + dev = cnss_info->dev; + + if (cnss_info->wdrv) { + pr_debug("wdrv already existed\n"); + return error; + } + + if (!driver) + return error; + + error = cnss_get_hw_resources(dev); + if (error) { + pr_err("Failed to restore power err:%d\n", error); + return error; + } + + error = cnss_set_pinctrl_state(cnss_pdata, PINCTRL_ACTIVE); + if (error) { + pr_err("Fail to set pinctrl to active state\n"); + cnss_put_hw_resources(dev); + goto put_hw; + } + + /* The HW resources are released in unregister logic if probe fails */ + error = driver->probe ? driver->probe(cnss_info->func, + cnss_info->id) : error; + if (error) { + pr_err("wlan probe failed error=%d\n", error); + /** + * Check memory leak in skb pre-alloc memory pool + * Reset the skb memory pool + */ + goto pinctrl_sleep; + } + + cnss_info->wdrv = driver; + + return error; + +pinctrl_sleep: + cnss_set_pinctrl_state(cnss_pdata, PINCTRL_SLEEP); +put_hw: + return error; +} +EXPORT_SYMBOL(cnss_sdio_wlan_register_driver); + +/** + * cnss_sdio_wlan_unregister_driver() - cnss wlan unregister API + * @driver: sdio wlan driver interface from wlan driver. + * + * wlan sdio function driver uses this API to detach it from cnss_sido + * platform driver. + */ +void +cnss_sdio_wlan_unregister_driver(struct cnss_sdio_wlan_driver *driver) +{ + struct cnss_sdio_info *cnss_info; +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + struct cnss_sdio_bus_bandwidth *bus_bandwidth; +#endif + + if (!cnss_pdata) + return; + +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + bus_bandwidth = &cnss_pdata->bus_bandwidth; + if (bus_bandwidth->bus_client) { + legacy_bus_client_update_request(bus_bandwidth->bus_client, + CNSS_BUS_WIDTH_NONE); + } +#endif + + cnss_info = &cnss_pdata->cnss_sdio_info; + if (!cnss_info->wdrv) { + pr_err("driver not registered\n"); + return; + } + + if (!driver) + return; + + if (!driver->remove) + return; + + driver->remove(cnss_info->func); + + cnss_info->wdrv = NULL; + cnss_set_pinctrl_state(cnss_pdata, PINCTRL_SLEEP); + cnss_put_hw_resources(cnss_info->dev); +} +EXPORT_SYMBOL(cnss_sdio_wlan_unregister_driver); + +/** + * cnss_wlan_query_oob_status() - cnss wlan query oob status API + * + * Wlan sdio function driver uses this API to check whether oob is + * supported in platform driver. + * + * Return: 0 means oob is supported, others means unsupported. + */ +int cnss_wlan_query_oob_status(void) +{ + return -EINVAL; +} +EXPORT_SYMBOL(cnss_wlan_query_oob_status); + +/** + * cnss_wlan_register_oob_irq_handler() - cnss wlan register oob callback API + * @handler: oob callback function pointer which registered to platform driver. + * @pm_oob : parameter which registered to platform driver. + * + * Wlan sdio function driver uses this API to register oob callback + * function to platform driver. + * + * Return: 0 means register successfully, others means failure. + */ +int cnss_wlan_register_oob_irq_handler(oob_irq_handler_t handler, void *pm_oob) +{ + return -EINVAL; +} +EXPORT_SYMBOL(cnss_wlan_register_oob_irq_handler); + +/** + * cnss_wlan_unregister_oob_irq_handler() - unregister oob callback API + * @pm_oob: parameter which unregistered from platform driver. + * + * Wlan sdio function driver uses this API to unregister oob callback + * function from platform driver. + * + * Return: 0 means unregister successfully, others means failure. + */ +int cnss_wlan_unregister_oob_irq_handler(void *pm_oob) +{ + return -EINVAL; +} +EXPORT_SYMBOL(cnss_wlan_unregister_oob_irq_handler); + +static void cnss_sdio_reset_platform_ops(void) +{ + struct cnss_dev_platform_ops *pf_ops = &cnss_pdata->platform_ops; + struct cnss_sdio_info *sdio_info = &cnss_pdata->cnss_sdio_info; + + memset(pf_ops, 0, sizeof(struct cnss_dev_platform_ops)); + if (sdio_info->dev) + sdio_info->dev->platform_data = NULL; +} + +static int cnss_sdio_wlan_init(void) +{ + int error = 0; + + error = sdio_register_driver(&cnss_ar6k_driver); + if (error) { + cnss_sdio_reset_platform_ops(); + pr_err("registered fail error=%d\n", error); + } else { + pr_debug("registered success\n"); + } + + return error; +} + +static void cnss_sdio_wlan_exit(void) +{ + if (!cnss_pdata) + return; + + cnss_sdio_reset_platform_ops(); + sdio_unregister_driver(&cnss_ar6k_driver); +} + +static void cnss_sdio_deinit_bus_bandwidth(void) +{ +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + struct cnss_sdio_bus_bandwidth *bus_bandwidth; + + bus_bandwidth = &cnss_pdata->bus_bandwidth; + if (bus_bandwidth->bus_client) { + legacy_bus_client_update_request(bus_bandwidth->bus_client, + CNSS_BUS_WIDTH_NONE); + legacy_bus_unregister_client(bus_bandwidth->bus_client); + } +#endif +} + +static int cnss_sdio_configure_wlan_enable_regulator(void) +{ + int error; + struct device *dev = &cnss_pdata->pdev->dev; + + if (of_get_property(cnss_pdata->pdev->dev.of_node, + WLAN_VREG_NAME "-supply", NULL)) { + cnss_pdata->regulator.wlan_vreg = + regulator_get(&cnss_pdata->pdev->dev, WLAN_VREG_NAME); + if (IS_ERR(cnss_pdata->regulator.wlan_vreg)) { + error = PTR_ERR(cnss_pdata->regulator.wlan_vreg); + dev_err(dev, "VDD-VREG get failed error=%d\n", error); + return error; + } + + error = regulator_enable(cnss_pdata->regulator.wlan_vreg); + if (error) { + dev_err(dev, "VDD-VREG enable failed error=%d\n", + error); + goto err_vdd_vreg_regulator; + } + } + + return 0; + +err_vdd_vreg_regulator: + regulator_put(cnss_pdata->regulator.wlan_vreg); + + return error; +} + +static int cnss_sdio_configure_wlan_enable_dsrc_regulator(void) +{ + int error; + struct device *dev = &cnss_pdata->pdev->dev; + + if (of_get_property(cnss_pdata->pdev->dev.of_node, + WLAN_VREG_DSRC_NAME "-supply", NULL)) { + cnss_pdata->regulator.wlan_vreg_dsrc = + regulator_get(&cnss_pdata->pdev->dev, WLAN_VREG_DSRC_NAME); + if (IS_ERR(cnss_pdata->regulator.wlan_vreg_dsrc)) { + error = PTR_ERR(cnss_pdata->regulator.wlan_vreg_dsrc); + dev_err(dev, "VDD-VREG-DSRC get failed error=%d\n", + error); + return error; + } + + error = regulator_enable(cnss_pdata->regulator.wlan_vreg_dsrc); + if (error) { + dev_err(dev, "VDD-VREG-DSRC enable failed error=%d\n", + error); + goto err_vdd_vreg_dsrc_regulator; + } + } + + return 0; + +err_vdd_vreg_dsrc_regulator: + regulator_put(cnss_pdata->regulator.wlan_vreg_dsrc); + + return error; +} + +static int cnss_sdio_configure_regulator(void) +{ + int error; + struct device *dev = &cnss_pdata->pdev->dev; + u32 vdd_xtal_min; + u32 vdd_xtal_max; + + if (of_get_property(cnss_pdata->pdev->dev.of_node, + WLAN_VREG_IO_NAME "-supply", NULL)) { + cnss_pdata->regulator.wlan_io = + regulator_get(&cnss_pdata->pdev->dev, WLAN_VREG_IO_NAME); + if (IS_ERR(cnss_pdata->regulator.wlan_io)) { + error = PTR_ERR(cnss_pdata->regulator.wlan_io); + dev_err(dev, "VDD-IO get failed error=%d\n", error); + return error; + } + + error = regulator_set_voltage(cnss_pdata->regulator.wlan_io, + WLAN_VREG_IO_MIN, + WLAN_VREG_IO_MAX); + if (error) { + dev_err(dev, "VDD-IO set failed error=%d\n", error); + goto err_vdd_io_regulator; + } else { + error = regulator_enable(cnss_pdata->regulator.wlan_io); + if (error) { + dev_err(dev, "VDD-IO enable failed error=%d\n", + error); + goto err_vdd_io_regulator; + } + } + } + + if (of_get_property(cnss_pdata->pdev->dev.of_node, + WLAN_VREG_XTAL_NAME "-supply", NULL)) { + cnss_pdata->regulator.wlan_xtal = + regulator_get(&cnss_pdata->pdev->dev, WLAN_VREG_XTAL_NAME); + if (IS_ERR(cnss_pdata->regulator.wlan_xtal)) { + error = PTR_ERR(cnss_pdata->regulator.wlan_xtal); + dev_err(dev, "VDD-XTAL get failed error=%d\n", error); + goto err_vdd_xtal_regulator; + } + + if (!of_property_read_u32(cnss_pdata->pdev->dev.of_node, + WLAN_VREG_XTAL_NAME "-min", + &vdd_xtal_min)) { + if (vdd_xtal_min < WLAN_VREG_XTAL_MIN || + vdd_xtal_min > WLAN_VREG_XTAL_MAX) + vdd_xtal_min = WLAN_VREG_XTAL_TYP; + } else { + vdd_xtal_min = WLAN_VREG_XTAL_TYP; + } + + if (!of_property_read_u32(cnss_pdata->pdev->dev.of_node, + WLAN_VREG_XTAL_NAME "-max", + &vdd_xtal_max)) { + if (vdd_xtal_max < WLAN_VREG_XTAL_MIN || + vdd_xtal_max > WLAN_VREG_XTAL_MAX) + vdd_xtal_max = WLAN_VREG_XTAL_TYP; + } else { + vdd_xtal_max = WLAN_VREG_XTAL_TYP; + } + + if (vdd_xtal_min > vdd_xtal_max) + vdd_xtal_min = vdd_xtal_max; + + error = regulator_set_voltage(cnss_pdata->regulator.wlan_xtal, + vdd_xtal_min, vdd_xtal_max); + if (error) { + dev_err(dev, "VDD-XTAL set failed error=%d\n", error); + goto err_vdd_xtal_regulator; + } else { + error = + regulator_enable(cnss_pdata->regulator.wlan_xtal); + if (error) { + dev_err(dev, "VDD-XTAL enable failed err=%d\n", + error); + goto err_vdd_xtal_regulator; + } + } + } + + return 0; + +err_vdd_xtal_regulator: + regulator_put(cnss_pdata->regulator.wlan_xtal); +err_vdd_io_regulator: + regulator_put(cnss_pdata->regulator.wlan_io); + return error; +} + +static void cnss_sdio_release_resource(void) +{ + if (cnss_pdata->regulator.wlan_xtal) + regulator_put(cnss_pdata->regulator.wlan_xtal); + if (cnss_pdata->regulator.wlan_vreg) + regulator_put(cnss_pdata->regulator.wlan_vreg); + if (cnss_pdata->regulator.wlan_io) + regulator_put(cnss_pdata->regulator.wlan_io); + if (cnss_pdata->regulator.wlan_vreg_dsrc) + regulator_put(cnss_pdata->regulator.wlan_vreg_dsrc); +} + +static int cnss_sdio_pinctrl_init(struct cnss_sdio_data *pdata, + struct platform_device *pdev) +{ + int ret = 0; + struct device *dev = &pdev->dev; + struct cnss_wlan_pinctrl_info *info = &pdata->pinctrl_info; + + if (!of_find_property(dev->of_node, "qcom,is-antenna-shared", NULL)) + return 0; + + info->is_antenna_shared = true; + info->pinctrl = devm_pinctrl_get(dev); + if ((IS_ERR_OR_NULL(info->pinctrl))) { + dev_err(dev, "%s: Failed to get pinctrl\n", __func__); + return PTR_ERR(info->pinctrl); + } + + info->sleep = pinctrl_lookup_state(info->pinctrl, + CNSS_PINCTRL_SLEEP_STATE); + if (IS_ERR_OR_NULL(info->sleep)) { + dev_err(dev, "%s: Fail to get sleep state for pin\n", __func__); + ret = PTR_ERR(info->sleep); + goto release_pinctrl; + } + + info->active = pinctrl_lookup_state(info->pinctrl, + CNSS_PINCTRL_ACTIVE_STATE); + if (IS_ERR_OR_NULL(info->active)) { + dev_err(dev, "%s: Fail to get active state for pin\n", + __func__); + ret = PTR_ERR(info->active); + goto release_pinctrl; + } + + ret = cnss_set_pinctrl_state(pdata, PINCTRL_SLEEP); + + if (ret) { + dev_err(dev, "%s: Fail to set pin in sleep state\n", __func__); + goto release_pinctrl; + } + + return ret; + +release_pinctrl: + devm_pinctrl_put(info->pinctrl); + info->is_antenna_shared = false; + return ret; +} + +static int cnss_sdio_init_bus_bandwidth(void) +{ + int ret = 0; +#ifdef CNSS_COMPLIE_ISSUE_FIX_LATER_IFNEEDED + struct cnss_sdio_bus_bandwidth *bus_bandwidth; + struct device *dev = &cnss_pdata->pdev->dev; + + bus_bandwidth = &cnss_pdata->bus_bandwidth; + bus_bandwidth->bus_scale_table = msm_bus_cl_get_pdata(cnss_pdata->pdev); + if (!bus_bandwidth->bus_scale_table) { + dev_err(dev, "Failed to get the bus scale platform data\n"); + ret = -EINVAL; + } + + bus_bandwidth->bus_client = + legacy_bus_register_client(bus_bandwidth->bus_scale_table); + if (!bus_bandwidth->bus_client) { + dev_err(dev, "Failed to register with bus_scale client\n"); + ret = -EINVAL; + } +#endif + return ret; +} + +static int cnss_sdio_probe(struct platform_device *pdev) +{ + int error; + struct device *dev = &pdev->dev; + struct cnss_sdio_info *info; + + if (pdev->dev.of_node) { + cnss_pdata = devm_kzalloc(&pdev->dev, + sizeof(*cnss_pdata), GFP_KERNEL); + if (!cnss_pdata) + return -ENOMEM; + } else { + cnss_pdata = pdev->dev.platform_data; + } + + if (!cnss_pdata) + return -EINVAL; + + cnss_pdata->pdev = pdev; + info = &cnss_pdata->cnss_sdio_info; + + error = cnss_sdio_pinctrl_init(cnss_pdata, pdev); + if (error) { + dev_err(&pdev->dev, "Fail to configure pinctrl err:%d\n", + error); + return error; + } + + error = cnss_sdio_configure_regulator(); + if (error) { + dev_err(&pdev->dev, "Failed to configure voltage regulator error=%d\n", + error); + return error; + } + + if (of_get_property(cnss_pdata->pdev->dev.of_node, + WLAN_VREG_NAME "-supply", NULL)) { + error = cnss_sdio_configure_wlan_enable_regulator(); + if (error) { + dev_err(&pdev->dev, + "Failed to enable wlan enable regulator error=%d\n", + error); + goto err_wlan_enable_regulator; + } + } + + if (of_get_property(cnss_pdata->pdev->dev.of_node, + WLAN_VREG_DSRC_NAME "-supply", NULL)) { + error = cnss_sdio_configure_wlan_enable_dsrc_regulator(); + if (error) { + dev_err(&pdev->dev, + "Failed to enable wlan dsrc enable regulator\n"); + goto err_wlan_dsrc_enable_regulator; + } + } + + info->skip_wlan_en_toggle = of_property_read_bool(dev->of_node, + "qcom,skip-wlan-en-toggle"); + info->cnss_hw_state = true; + + cnss_sdio_tsf_init(dev, &info->cap_tsf_info); + + error = cnss_sdio_wlan_init(); + if (error) { + dev_err(&pdev->dev, "cnss wlan init failed error=%d\n", error); + goto err_wlan_dsrc_enable_regulator; + } + + error = cnss_configure_ramdump(); + if (error) { + dev_err(&pdev->dev, "Failed to configure ramdump error=%d\n", + error); + goto err_ramdump_create; + } + + error = cnss_subsys_init(); + if (error) { + dev_err(&pdev->dev, "Failed to cnss_subsys_init error=%d\n", + error); + goto err_subsys_init; + } + + if (of_property_read_bool(pdev->dev.of_node, + "qcom,cnss-enable-bus-bandwidth")) { + error = cnss_sdio_init_bus_bandwidth(); + if (error) { + dev_err(&pdev->dev, "Failed to init bus bandwidth\n"); + goto err_bus_bandwidth_init; + } + } + + dev_info(&pdev->dev, "CNSS SDIO Driver registered\n"); + return 0; + +err_bus_bandwidth_init: + cnss_subsys_exit(); +err_subsys_init: + cnss_ramdump_cleanup(); +err_ramdump_create: + cnss_sdio_wlan_exit(); +err_wlan_dsrc_enable_regulator: + info->cnss_hw_state = false; + regulator_put(cnss_pdata->regulator.wlan_vreg_dsrc); +err_wlan_enable_regulator: + regulator_put(cnss_pdata->regulator.wlan_xtal); + regulator_put(cnss_pdata->regulator.wlan_io); + cnss_pdata = NULL; + return error; +} + +static int cnss_sdio_remove(struct platform_device *pdev) +{ + struct cnss_sdio_info *info; + struct cnss_cap_tsf_info *tsf_info; + + if (!cnss_pdata) + return -ENODEV; + + info = &cnss_pdata->cnss_sdio_info; + tsf_info = &info->cap_tsf_info; + + cnss_sdio_tsf_deinit(tsf_info); + cnss_sdio_deinit_bus_bandwidth(); + cnss_sdio_wlan_exit(); + cnss_subsys_exit(); + cnss_ramdump_cleanup(); + cnss_put_hw_resources(info->dev); + cnss_sdio_release_resource(); + cnss_pdata = NULL; + return 0; +} + +int cnss_sdio_set_wlan_mac_address(const u8 *in, u32 len) +{ + return 0; +} + +u8 *cnss_sdio_get_wlan_mac_address(u32 *num) +{ + *num = 0; + return NULL; +} + +int cnss_sdio_power_down(struct device *dev) +{ + return 0; +} + +int cnss_sdio_power_up(struct device *dev) +{ + return 0; +} + +static const struct of_device_id cnss_sdio_dt_match[] = { + {.compatible = "qcom,cnss_sdio"}, + {} +}; +MODULE_DEVICE_TABLE(of, cnss_sdio_dt_match); + +static struct platform_driver cnss_sdio_driver = { + .probe = cnss_sdio_probe, + .remove = cnss_sdio_remove, + .driver = { + .name = "cnss_sdio", + .of_match_table = cnss_sdio_dt_match, + }, +}; + +static int __init cnss_sdio_init(void) +{ + return platform_driver_register(&cnss_sdio_driver); +} + +static void __exit cnss_sdio_exit(void) +{ + platform_driver_unregister(&cnss_sdio_driver); +} + +module_init(cnss_sdio_init); +module_exit(cnss_sdio_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wireless/cnss/logger/Kconfig b/drivers/net/wireless/cnss/logger/Kconfig new file mode 100644 index 000000000000..ea8e91a36578 --- /dev/null +++ b/drivers/net/wireless/cnss/logger/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# cnss logger configuration +# + +config CNSS_LOGGER + tristate "CNSS Logging Service Driver" + help + This module adds support for the CNSS Logging Service for CLD + driver, including the netlink socket service registration, transmit, + event receive. diff --git a/drivers/net/wireless/cnss/logger/Makefile b/drivers/net/wireless/cnss/logger/Makefile new file mode 100644 index 000000000000..f23b0c0b9acb --- /dev/null +++ b/drivers/net/wireless/cnss/logger/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for cnss logger +# + +obj-$(CONFIG_CNSS_LOGGER) += logger.o + +logger-y += main.o \ + nl_service.o + +logger-$(CONFIG_DEBUG_FS) += debugfs.o diff --git a/drivers/net/wireless/cnss/logger/debugfs.c b/drivers/net/wireless/cnss/logger/debugfs.c new file mode 100644 index 000000000000..de762ff5f527 --- /dev/null +++ b/drivers/net/wireless/cnss/logger/debugfs.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ + +#include +#include + +#include "logger.h" + +#define CNSS_LOGGER_STATE_DUMP_BUFFER (2 * 1024) /* 2KB */ + +static int logger_state_dump_device(struct logger_device *dev, char *buf, + int buf_len) +{ + int len = 0; + struct logger_event_handler *cur; + + len += scnprintf(buf + len, buf_len - len, + "==============================================\n"); + + len += scnprintf(buf + len, buf_len - len, + "driver [%s] is registered with radio index: %d\n", + dev->name, dev->radio_idx); + + if (list_empty(&dev->event_list)) { + len += scnprintf(buf + len, buf_len - len, + "No event registered\n"); + return len; + } + + list_for_each_entry(cur, &dev->event_list, list) { + len += scnprintf(buf + len, buf_len - len, + "\t event %d\n", cur->event); + } + len += scnprintf(buf + len, buf_len - len, "\n"); + + return len; +} + +static int logger_state_dump(struct logger_context *ctx, char *buf, int buf_len) +{ + int len = 0; + struct logger_device *cur; + + if (list_empty(&ctx->dev_list)) { + len += scnprintf(buf + len, buf_len - len, + "=======================\n"); + len += scnprintf(buf + len, buf_len - len, + "No driver registered\n"); + return 0; + } + + list_for_each_entry(cur, &ctx->dev_list, list) + len += logger_state_dump_device(cur, (buf + len), buf_len); + + return 0; +} + +static int logger_state_open(struct inode *inode, struct file *file) +{ + struct logger_context *ctx = inode->i_private; + void *buf; + int ret; + + mutex_lock(&ctx->con_mutex); + + buf = kmalloc(CNSS_LOGGER_STATE_DUMP_BUFFER, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto error_unlock; + } + + ret = logger_state_dump(ctx, buf, CNSS_LOGGER_STATE_DUMP_BUFFER); + if (ret) + goto error_free; + + file->private_data = buf; + mutex_unlock(&ctx->con_mutex); + return 0; + +error_free: + kfree(buf); + +error_unlock: + mutex_unlock(&ctx->con_mutex); + + return ret; +} + +static int logger_state_release(struct inode *inode, struct file *file) +{ + kfree(file->private_data); + return 0; +} + +static ssize_t logger_state_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + const char *buf = file->private_data; + unsigned int len = strlen(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_logger_state = { + .open = logger_state_open, + .release = logger_state_release, + .read = logger_state_read, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +void logger_debugfs_init(struct logger_context *ctx) +{ + if (!ctx->debugfs_entry) + ctx->debugfs_entry = debugfs_create_dir("cnss_logger", NULL); + + debugfs_create_file("state", 0400, ctx->debugfs_entry, ctx, + &fops_logger_state); +} + +void logger_debugfs_remove(struct logger_context *ctx) +{ + debugfs_remove(ctx->debugfs_entry); +} + diff --git a/drivers/net/wireless/cnss/logger/logger.h b/drivers/net/wireless/cnss/logger/logger.h new file mode 100644 index 000000000000..861cf613624d --- /dev/null +++ b/drivers/net/wireless/cnss/logger/logger.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ + +#ifndef _LOGGER_H_ +#define _LOGGER_H_ + +#include +#include +#include +#include +#include + +#define CNSS_LOGGER_NL_MCAST_GRP_ID 0x01 +#define CNSS_LOGGER_NL_MAX_PAYLOAD 256 +#define CNSS_LOGGER_BROADCAST_ID 255 + +/** + * struct aninlmsg - the wireless service message header + * @nlh: the netlink message header + * @radio: the radio index of this message + * @wmsg: the pointer to the wireless message data + */ +struct aninlmsg { + struct nlmsghdr *nlh; + int radio; + void *wmsg; +}; + +/** + * struct logger_event_handler - the logger event handler structure + * @list: the event list associated to the same device + * @event: the event number + * @radio_idx: the callback handler + */ +struct logger_event_handler { + struct list_head list; + + int event; + int (*cb)(struct sk_buff *skb); +}; + +/** + * struct logger_device - the logger device structure + * @list: the device list registered to logger module + * @event_list: the event list registered to this device + * @ctx: the pointer to the logger context + * @wiphy: the wiphy that associated to the device + * @name: the name of the device driver module + * @radio_idx: the radio index assigned to this device + */ +struct logger_device { + struct list_head list; + struct list_head event_list; + + struct logger_context *ctx; + struct wiphy *wiphy; + char name[MODULE_NAME_LEN]; + int radio_idx; +}; + +/** + * struct logger_context - the main context block for logger module + * @dev_list: this is the list to maintain the devices that registered + * to use the logger module feature + * @nl_sock: the netlink socket to share accros the module + * @con_mutex: the mutex to protect concurrent access + * @data_lock: the lock to protect shared data + * @radio_mask: this mask would maintain the radio index assign and release + */ +struct logger_context { + struct list_head dev_list; + + struct sock *nl_sock; + struct mutex con_mutex; /* concurrent access mutex */ + spinlock_t data_lock; + unsigned long radio_mask; /* support up to 4 drivers registration? */ +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_entry; +#endif +}; + +int logger_netlink_init(struct logger_context *ctx); +int logger_netlink_deinit(struct logger_context *ctx); +struct logger_context *logger_get_ctx(void); + +#ifdef CONFIG_DEBUG_FS +void logger_debugfs_init(struct logger_context *ctx); +void logger_debugfs_remove(struct logger_context *ctx); +#else +static inline void logger_debugfs_init(struct logger_context *ctx) {} +static inline void logger_debugfs_remove(struct logger_context *ctx) {} +#endif + +#endif /* _LOGGER_H_ */ diff --git a/drivers/net/wireless/cnss/logger/main.c b/drivers/net/wireless/cnss/logger/main.c new file mode 100644 index 000000000000..202ab2e748ab --- /dev/null +++ b/drivers/net/wireless/cnss/logger/main.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ + +#include +#include +#include +#include +#include +#include "logger.h" + +static struct logger_context *ctx; + +struct logger_context *logger_get_ctx(void) +{ + return ctx; +} + +static int __init logger_module_init(void) +{ + int ret; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ret = logger_netlink_init(ctx); + + mutex_init(&ctx->con_mutex); + spin_lock_init(&ctx->data_lock); + logger_debugfs_init(ctx); + + return ret; +} + +static void __exit logger_module_exit(void) +{ + logger_debugfs_remove(ctx); + logger_netlink_deinit(ctx); + + kfree(ctx); +} + +module_init(logger_module_init); +module_exit(logger_module_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CNSS Logging Service Driver"); diff --git a/drivers/net/wireless/cnss/logger/nl_service.c b/drivers/net/wireless/cnss/logger/nl_service.c new file mode 100644 index 000000000000..0d88fb66bfa5 --- /dev/null +++ b/drivers/net/wireless/cnss/logger/nl_service.c @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ + +#define pr_fmt(fmt) "cnss_logger: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include "logger.h" + +static DEFINE_MUTEX(logger_sem); + +/** + * logger_get_radio_idx() - to get the radio index + * @ctx: the logger context pointer + * + * Return: the first available radio index, otherwise failure code + */ +static int logger_get_radio_idx(struct logger_context *ctx) +{ + int i; + + for (i = 0; i < sizeof(ctx->radio_mask); i++) { + if (!test_and_set_bit(i, &ctx->radio_mask)) + return i; + } + return -EINVAL; +} + +/** + * logger_put_radio_idx() - to release the radio index + * @radio: the radio index to release + * + * Return: None + */ +static void logger_put_radio_idx(struct logger_context *ctx, int radio) +{ + clear_bit(radio, &ctx->radio_mask); +} + +/** + * logger_get_device() - to get the logger device per radio index + * @radio: the radio index + * + * Return: the logger_device pointer, otherwise return NULL. + */ +static struct logger_device *logger_get_device(int radio) +{ + struct logger_device *dev; + struct logger_context *ctx; + + ctx = logger_get_ctx(); + if (!ctx) + return NULL; + + list_for_each_entry(dev, &ctx->dev_list, list) { + if (dev->radio_idx == radio) + return dev; + } + return NULL; +} + +/** + * logger_device_is_registered() - to check if device has been registered + * @dev: pointer to logger device + * @wiphy: the wiphy pointer of the device to register + * + * This helper function is to check if this device has been registered. + * + * Return: NULL if it has not, otherwise return the logger_device pointer. + */ +static +struct logger_device *logger_device_is_registered(struct logger_context *ctx, + struct wiphy *wiphy) +{ + struct logger_device *dev; + + list_for_each_entry(dev, &ctx->dev_list, list) { + if (dev->wiphy == wiphy) + return dev; + } + return NULL; +} + +/** + * logger_dispatch_skb() - to dispatch the skb to devices + * @skb: the socket buffer received and to dispatch + * + * The function will look up the header of the skb, and dispatch the skb + * to the associated event and device that registered. + * + * Return: 0 if successfully dispatch, otherwise failure code + */ +static int logger_dispatch_skb(struct sk_buff *skb) +{ + struct nlmsghdr *nlh; + struct logger_context *ctx; + struct logger_device *cur; + struct logger_event_handler *evt; + int handled = 0; + + ctx = logger_get_ctx(); + if (!ctx) + return -ENOENT; + + pr_info("skb_len: %d, skb_data_len: %d\n", + skb->len, skb->data_len); + + if (skb->len < sizeof(struct nlmsghdr)) + return 0; + + nlh = (struct nlmsghdr *)skb->data; + list_for_each_entry(cur, &ctx->dev_list, list) { + list_for_each_entry(evt, &cur->event_list, list) { + if (nlh->nlmsg_type == evt->event) { + if (evt->cb) { + handled = 1; + evt->cb(skb); + } + /* Break inside loop, next dev */ + break; + } + } + } + + if (!handled) + pr_info("Not handled msg type: %d\n", nlh->nlmsg_type); + + return 0; +} + +/** + * logger_flush_event_handle() - to flush the event handle associate to device + * @dev: pointer to logger device + * + * The function will clean up all the event handle's resource, take it out + * from the list, and free the memory allocated. + * + * Return: None + */ +static void logger_flush_event_handle(struct logger_device *dev) +{ + struct list_head *pos, *temp; + struct logger_event_handler *cur; + + list_for_each_safe(pos, temp, &dev->event_list) { + cur = container_of(pos, struct logger_event_handler, list); + pr_info("radio: %d, event: %d unregistered\n", + dev->radio_idx, cur->event); + list_del(&cur->list); + kfree(cur); + } +} + +/** + * logger_flush_devices() - to flush the devices infomration + * @dev: pointer to logger device + * + * The helper function to flush the device information, all the device clean + * up prcoess should be starting from here. + * + * Return: None + */ +static void logger_flush_devices(struct logger_device *dev) +{ + pr_info("driver: [%s] and radio-%d is unregistered\n", + dev->name, dev->radio_idx); + logger_flush_event_handle(dev); + logger_put_radio_idx(dev->ctx, dev->radio_idx); + list_del(&dev->list); + kfree(dev); +} + +/** + * logger_register_device_event() - register the evet to device + * @dev: pointer to logger device + * @event: the event to register + * @cb: the callback associated to the device and event + * + * Return: 0 if register successfully, otherwise the failure code + */ +static int logger_register_device_event(struct logger_device *dev, int event, + int (*cb)(struct sk_buff *skb)) +{ + struct logger_event_handler *cur; + + list_for_each_entry(cur, &dev->event_list, list) { + if (cur->event == event) { + pr_info("event %d, is already added\n", event); + return 0; + } + } + + cur = kmalloc(sizeof(*cur), GFP_KERNEL); + if (!cur) + return -ENOMEM; + + cur->event = event; + cur->cb = cb; + + pr_info("radio: %d, event: %d\n", dev->radio_idx, cur->event); + list_add_tail(&cur->list, &dev->event_list); + + return 0; +} + +/** + * logger_unregister_device_event() - unregister the evet from device + * @dev: pointer to logger device + * @event: the event to unregister + * @cb: the callback associated to the device and event + * + * Return: 0 if unregister successfully, otherwise the failure code + */ +static int logger_unregister_device_event(struct logger_device *dev, int event, + int (*cb)(struct sk_buff *skb)) +{ + struct list_head *pos, *temp; + struct logger_event_handler *cur; + + list_for_each_safe(pos, temp, &dev->event_list) { + cur = container_of(pos, struct logger_event_handler, list); + if (cur->event == event && cur->cb == cb) { + pr_info("radio: %d, event: %d\n", + dev->radio_idx, cur->event); + list_del(&cur->list); + kfree(cur); + return 0; + } + } + return -ENOENT; +} + +/** + * logger_skb_input() - the callback to receive the skb + * @skb: the receive socket buffer + * + * Return: None + */ +static void logger_skb_input(struct sk_buff *skb) +{ + mutex_lock(&logger_sem); + logger_dispatch_skb(skb); + mutex_unlock(&logger_sem); +} + +/** + * cnss_logger_event_register() - register the event + * @radio: the radio index to register + * @event: the event to register + * @cb: the callback + * + * This function is used to register event associated to the radio index. + * + * Return: 0 if register success, otherwise failure code + */ +int cnss_logger_event_register(int radio, int event, + int (*cb)(struct sk_buff *skb)) +{ + int ret = -ENOENT; + struct logger_device *dev; + + dev = logger_get_device(radio); + if (dev) + ret = logger_register_device_event(dev, event, cb); + + return ret; +} +EXPORT_SYMBOL(cnss_logger_event_register); + +/** + * cnss_logger_event_unregister() - unregister the event + * @radio: the radio index to unregister + * @event: the event to unregister + * @cb: the callback + * + * This function is used to unregister the event from cnss logger module. + * + * Return: 0 if unregister success, otherwise failure code + */ +int cnss_logger_event_unregister(int radio, int event, + int (*cb)(struct sk_buff *skb)) +{ + int ret = -ENOENT; + struct logger_device *dev; + + dev = logger_get_device(radio); + if (dev) + ret = logger_unregister_device_event(dev, event, cb); + + return ret; +} +EXPORT_SYMBOL(cnss_logger_event_unregister); + +/** + * cnss_logger_device_register() - register the driver + * @wiphy: the wiphy device to unregister + * @name: the module name of the driver + * + * This function is used to register the driver to cnss logger module, + * this will indicate the existence of the driver, and also assign the + * radio index for further operation. + * + * Return: the radio index if register successful, otherwise failure code + */ +int cnss_logger_device_register(struct wiphy *wiphy, const char *name) +{ + int radio; + struct logger_context *ctx; + struct logger_device *new; + + ctx = logger_get_ctx(); + if (!ctx) + return -ENOENT; + + /* sanity check, already registered? */ + new = logger_device_is_registered(ctx, wiphy); + if (new) + return new->radio_idx; + + radio = logger_get_radio_idx(ctx); + if (radio < 0) { + pr_err("driver registration is full\n"); + return -ENOMEM; + } + + new = kmalloc(sizeof(*new), GFP_KERNEL); + if (!new) { + logger_put_radio_idx(ctx, radio); + return -ENOMEM; + } + + new->radio_idx = radio; + new->wiphy = wiphy; + new->ctx = ctx; + strlcpy(new->name, name, sizeof(new->name)); + INIT_LIST_HEAD(&new->event_list); + + list_add(&new->list, &ctx->dev_list); + + pr_info("driver: [%s] is registered as radio-%d\n", + new->name, new->radio_idx); + + return new->radio_idx; +} +EXPORT_SYMBOL(cnss_logger_device_register); + +/** + * cnss_logger_device_unregister() - unregister the driver + * @radio: the radio to unregister + * @wiphy: the wiphy device to unregister + * + * This function is used to unregister the driver from cnss logger module. + * This will disable the driver to access the interface in cnss logger, + * and also all the related events that registered will be reset. + * + * Return: 0 if success, otherwise failure code + */ +int cnss_logger_device_unregister(int radio, struct wiphy *wiphy) +{ + struct logger_context *ctx; + struct logger_device *cur; + struct list_head *pos, *temp; + + ctx = logger_get_ctx(); + if (!ctx) + return -ENOENT; + + list_for_each_safe(pos, temp, &ctx->dev_list) { + cur = list_entry(pos, struct logger_device, list); + if (cur->radio_idx == radio && cur->wiphy == wiphy) { + logger_flush_devices(cur); + break; + } + } + return 0; +} +EXPORT_SYMBOL(cnss_logger_device_unregister); + +/** + * cnss_logger_nl_ucast() - nl interface to unicast the buffer + * @skb: the socket buffer to transmit + * @portid: netlink portid of the destination socket + * @flag: the flag to indicate if this is a nonblock call + * + * Return: 0 if success, otherwise failure code + */ +int cnss_logger_nl_ucast(struct sk_buff *skb, int portid, int flag) +{ + struct logger_context *ctx; + + ctx = logger_get_ctx(); + if (!ctx) { + dev_kfree_skb(skb); + return -ENOENT; + } + + return netlink_unicast(ctx->nl_sock, skb, portid, flag); +} +EXPORT_SYMBOL(cnss_logger_nl_ucast); + +/** + * cnss_logger_nl_bcast() - nl interface to broadcast the buffer + * @skb: the socket buffer to transmit + * @portid: netlink portid of the destination socket + * @flag: the gfp_t flag + * + * Return: 0 if success, otherwise failure code + */ +int cnss_logger_nl_bcast(struct sk_buff *skb, int portid, int flag) +{ + struct logger_context *ctx; + + ctx = logger_get_ctx(); + if (!ctx) { + dev_kfree_skb(skb); + return -ENOENT; + } + + return netlink_broadcast(ctx->nl_sock, skb, 0, portid, flag); +} +EXPORT_SYMBOL(cnss_logger_nl_bcast); + +/** + * logger_netlink_init() - initialize the netlink socket + * @ctx: the cnss logger context pointer + * + * Return: the netlink handle if success, otherwise failure code + */ +int logger_netlink_init(struct logger_context *ctx) +{ + struct netlink_kernel_cfg cfg = { + .groups = CNSS_LOGGER_NL_MCAST_GRP_ID, + .input = logger_skb_input, + }; + + ctx->nl_sock = netlink_kernel_create(&init_net, NETLINK_USERSOCK, &cfg); + if (!ctx->nl_sock) { + pr_err("cnss_logger: Cannot create netlink socket\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&ctx->dev_list); + + return 0; +} + +/** + * logger_netlink_deinit() - release the netlink socket and other resource + * @ctx: the cnss logger context pointer + * + * Return: 0 if success, otherwise failure code + */ +int logger_netlink_deinit(struct logger_context *ctx) +{ + struct list_head *pos, *temp; + struct logger_device *dev; + + netlink_kernel_release(ctx->nl_sock); + list_for_each_safe(pos, temp, &ctx->dev_list) { + dev = container_of(pos, struct logger_device, list); + logger_flush_devices(dev); + } + return 0; +} diff --git a/drivers/net/wireless/cnss_crypto/Makefile b/drivers/net/wireless/cnss_crypto/Makefile new file mode 100644 index 000000000000..6318e8416429 --- /dev/null +++ b/drivers/net/wireless/cnss_crypto/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for cnss crypto +# + +obj-$(CONFIG_CNSS_CRYPTO) += cnss_secif.o diff --git a/drivers/net/wireless/cnss_crypto/cnss_secif.c b/drivers/net/wireless/cnss_crypto/cnss_secif.c new file mode 100644 index 000000000000..a2acc0ab9f32 --- /dev/null +++ b/drivers/net/wireless/cnss_crypto/cnss_secif.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2011-2013, 2015, 2018-2021, The Linux Foundation. */ + +#include +#include +#include + +/* APIs for calling crypto routines from kernel + */ +struct crypto_ahash *wcnss_wlan_crypto_alloc_ahash(const char *alg_name, + u32 type, u32 mask) +{ + return crypto_alloc_ahash(alg_name, type, mask); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_alloc_ahash); + +int wcnss_wlan_crypto_ahash_digest(struct ahash_request *req) +{ + return crypto_ahash_digest(req); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_ahash_digest); + +void wcnss_wlan_crypto_free_ahash(struct crypto_ahash *tfm) +{ + crypto_free_ahash(tfm); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_free_ahash); + +int wcnss_wlan_crypto_ahash_setkey(struct crypto_ahash *tfm, const u8 *key, + unsigned int keylen) +{ + return crypto_ahash_setkey(tfm, key, keylen); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_ahash_setkey); + +void wcnss_wlan_ablkcipher_request_free(struct ablkcipher_request *req) +{ + ablkcipher_request_free(req); +} +EXPORT_SYMBOL(wcnss_wlan_ablkcipher_request_free); + +void wcnss_wlan_crypto_free_ablkcipher(struct crypto_ablkcipher *tfm) +{ + crypto_free_ablkcipher(tfm); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_free_ablkcipher); + +void wcnss_wlan_crypto_free_cipher(struct crypto_cipher *tfm) +{ + crypto_free_cipher(tfm); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_free_cipher); + +struct crypto_cipher * +wcnss_wlan_crypto_alloc_cipher(const char *alg_name, u32 type, u32 mask) +{ + return crypto_alloc_cipher(alg_name, type, mask); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_alloc_cipher); + +static inline void xor_128(const u8 *a, const u8 *b, u8 *out) +{ + u8 i; + + for (i = 0; i < AES_BLOCK_SIZE; i++) + out[i] = a[i] ^ b[i]; +} + +static inline void leftshift_onebit(const u8 *input, u8 *output) +{ + int i, overflow = 0; + + for (i = (AES_BLOCK_SIZE - 1); i >= 0; i--) { + output[i] = input[i] << 1; + output[i] |= overflow; + overflow = (input[i] & 0x80) ? 1 : 0; + } +} + +static void generate_subkey(struct crypto_cipher *tfm, u8 *k1, u8 *k2) +{ + u8 l[AES_BLOCK_SIZE], tmp[AES_BLOCK_SIZE]; + u8 const_rb[AES_BLOCK_SIZE] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87}; + u8 const_zero[AES_BLOCK_SIZE] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + crypto_cipher_encrypt_one(tfm, l, const_zero); + + if ((l[0] & 0x80) == 0) { /* If MSB(l) = 0, then k1 = l << 1 */ + leftshift_onebit(l, k1); + } else { /* Else k1 = ( l << 1 ) (+) Rb */ + leftshift_onebit(l, tmp); + xor_128(tmp, const_rb, k1); + } + + if ((k1[0] & 0x80) == 0) { + leftshift_onebit(k1, k2); + } else { + leftshift_onebit(k1, tmp); + xor_128(tmp, const_rb, k2); + } +} + +static inline void padding(u8 *lastb, u8 *pad, u16 length) +{ + u8 j; + + /* original last block */ + for (j = 0; j < AES_BLOCK_SIZE; j++) { + if (j < length) + pad[j] = lastb[j]; + else if (j == length) + pad[j] = 0x80; + else + pad[j] = 0x00; + } +} + +void wcnss_wlan_cmac_calc_mic(struct crypto_cipher *tfm, u8 *m, + u16 length, u8 *mac) +{ + u8 x[AES_BLOCK_SIZE], y[AES_BLOCK_SIZE]; + u8 m_last[AES_BLOCK_SIZE], padded[AES_BLOCK_SIZE]; + u8 k1[AES_KEYSIZE_128], k2[AES_KEYSIZE_128]; + int cmpBlk; + int i, nblocks = (length + 15) / AES_BLOCK_SIZE; + + generate_subkey(tfm, k1, k2); + + if (nblocks == 0) { + nblocks = 1; + cmpBlk = 0; + } else { + cmpBlk = ((length % AES_BLOCK_SIZE) == 0) ? 1 : 0; + } + + if (cmpBlk) { /* Last block is complete block */ + xor_128(&m[AES_BLOCK_SIZE * (nblocks - 1)], k1, m_last); + } else { /* Last block is not complete block */ + padding(&m[AES_BLOCK_SIZE * (nblocks - 1)], padded, + length % AES_BLOCK_SIZE); + xor_128(padded, k2, m_last); + } + + for (i = 0; i < AES_BLOCK_SIZE; i++) + x[i] = 0; + + for (i = 0; i < (nblocks - 1); i++) { + xor_128(x, &m[AES_BLOCK_SIZE * i], y); /* y = Mi (+) x */ + crypto_cipher_encrypt_one(tfm, x, y); /* x = AES-128(KEY, y) */ + } + + xor_128(x, m_last, y); + crypto_cipher_encrypt_one(tfm, x, y); + + memcpy(mac, x, CMAC_TLEN); +} +EXPORT_SYMBOL(wcnss_wlan_cmac_calc_mic); diff --git a/include/linux/esoc_client.h b/include/linux/esoc_client.h new file mode 100644 index 000000000000..64789df23943 --- /dev/null +++ b/include/linux/esoc_client.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2014, 2017-2021, The Linux Foundation. All rights reserved. */ + +#ifndef __ESOC_CLIENT_H_ +#define __ESOC_CLIENT_H_ + +#include +#include +#include + +enum esoc_client_hook_prio { + ESOC_MHI_HOOK, + ESOC_CNSS_HOOK, + ESOC_MAX_HOOKS +}; + +struct esoc_link_data { + enum esoc_client_hook_prio prio; + __u64 link_id; +}; + +/* Flag values used with the power_on and power_off hooks */ +#define ESOC_HOOK_MDM_CRASH 0x0001 /* In crash handling path */ +#define ESOC_HOOK_MDM_DOWN 0x0002 /* MDM about to go down */ + +struct esoc_client_hook { + char *name; + void *priv; + enum esoc_client_hook_prio prio; + int (*esoc_link_power_on)(void *priv, unsigned int flags); + void (*esoc_link_power_off)(void *priv, unsigned int flags); + u64 (*esoc_link_get_id)(void *priv); + void (*esoc_link_mdm_crash)(void *priv); +}; + +/* + * struct esoc_desc: Describes an external soc + * @name: external soc name + * @priv: private data for external soc + */ +struct esoc_desc { + const char *name; + const char *link; + const char *link_info; + void *priv; +}; + +#ifdef CONFIG_ESOC_CLIENT +/* Can return probe deferral */ +struct esoc_desc *devm_register_esoc_client(struct device *dev, const char *name); +void devm_unregister_esoc_client(struct device *dev, struct esoc_desc *esoc_desc); +int esoc_register_client_notifier(struct notifier_block *nb); +int esoc_register_client_hook(struct esoc_desc *desc, + struct esoc_client_hook *client_hook); +int esoc_unregister_client_hook(struct esoc_desc *desc, + struct esoc_client_hook *client_hook); +#else +static inline struct esoc_desc *devm_register_esoc_client(struct device *dev, + const char *name) +{ + return NULL; +} + +static inline void devm_unregister_esoc_client(struct device *dev, + struct esoc_desc *esoc_desc) +{ +} + +static inline int esoc_register_client_notifier(struct notifier_block *nb) +{ + return -EIO; +} + +static inline int esoc_register_client_hook(struct esoc_desc *desc, + struct esoc_client_hook *client_hook) +{ + return -EIO; +} + +static inline int esoc_unregister_client_hook(struct esoc_desc *desc, + struct esoc_client_hook *client_hook) +{ + return -EIO; +} +#endif +#endif diff --git a/include/linux/qcomwlan_secif.h b/include/linux/qcomwlan_secif.h new file mode 100644 index 000000000000..82b560bb3925 --- /dev/null +++ b/include/linux/qcomwlan_secif.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2011-2013, 2019-2021,The Linux Foundation. All rights reserved. */ + +#ifndef __QCOM_WLAN_SECIF_H__ +#define __QCOM_WLAN_SECIF_H__ + +#include + +#define CMAC_TLEN 8 /* CMAC TLen = 64 bits (8 octets) */ + +/* + * Prototypes for WLAN Security Interface Functions + */ + +struct crypto_ahash * +wcnss_wlan_crypto_alloc_ahash(const char *alg_name, u32 type, u32 mask); + +int wcnss_wlan_crypto_ahash_digest(struct ahash_request *req); +void wcnss_wlan_crypto_free_ahash(struct crypto_ahash *tfm); +int wcnss_wlan_crypto_ahash_setkey(struct crypto_ahash *tfm, + const u8 *key, unsigned int keylen); +struct crypto_ablkcipher * +wcnss_wlan_crypto_alloc_ablkcipher(const char *alg_name, u32 type, u32 mask); +void wcnss_wlan_ablkcipher_request_free(struct ablkcipher_request *req); +void wcnss_wlan_crypto_free_cipher(struct crypto_cipher *tfm); +void wcnss_wlan_crypto_free_ablkcipher(struct crypto_ablkcipher *tfm); +struct crypto_cipher * +wcnss_wlan_crypto_alloc_cipher(const char *alg_name, u32 type, u32 mask); +void wcnss_wlan_cmac_calc_mic(struct crypto_cipher *tfm, u8 *m, + u16 length, u8 *mac); + +#endif /* __QCOM_WLAN_SECIF_H__ */ diff --git a/include/net/cnss.h b/include/net/cnss.h new file mode 100644 index 000000000000..9c99bdc2032d --- /dev/null +++ b/include/net/cnss.h @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2013-2021, The Linux Foundation. All rights reserved. */ + +#ifndef _NET_CNSS_H_ +#define _NET_CNSS_H_ + +#include +#include +#include +#include +#include + +#ifdef CONFIG_CNSS +#define MAX_FIRMWARE_SIZE (1 * 1024 * 1024) +#define CNSS_MAX_FILE_NAME 20 +#define PINCTRL_SLEEP 0 +#define PINCTRL_ACTIVE 1 + +enum cnss_bus_width_type { + CNSS_BUS_WIDTH_NONE, + CNSS_BUS_WIDTH_LOW, + CNSS_BUS_WIDTH_MEDIUM, + CNSS_BUS_WIDTH_HIGH +}; + +enum cnss_cc_src { + CNSS_SOURCE_CORE, + CNSS_SOURCE_11D, + CNSS_SOURCE_USER +}; + +/* FW image files */ +struct cnss_fw_files { + char image_file[CNSS_MAX_FILE_NAME]; + char board_data[CNSS_MAX_FILE_NAME]; + char otp_data[CNSS_MAX_FILE_NAME]; + char utf_file[CNSS_MAX_FILE_NAME]; + char utf_board_data[CNSS_MAX_FILE_NAME]; + char epping_file[CNSS_MAX_FILE_NAME]; + char evicted_data[CNSS_MAX_FILE_NAME]; +}; + +struct cnss_wlan_runtime_ops { + int (*runtime_suspend)(struct pci_dev *pdev); + int (*runtime_resume)(struct pci_dev *pdev); +}; + +struct cnss_wlan_driver { + char *name; + int (*probe)(struct pci_dev *pdev, const struct pci_device_id *id); + void (*remove)(struct pci_dev *pdev); + int (*reinit)(struct pci_dev *pdev, const struct pci_device_id *id); + void (*shutdown)(struct pci_dev *pdev); + void (*crash_shutdown)(struct pci_dev *pdev); + int (*suspend)(struct pci_dev *pdev, pm_message_t state); + int (*resume)(struct pci_dev *pdev); + void (*modem_status)(struct pci_dev *pdev, int state); + void (*update_status)(struct pci_dev *pdev, uint32_t status); + struct cnss_wlan_runtime_ops *runtime_ops; + const struct pci_device_id *id_table; +}; + +/* + * codeseg_total_bytes: Total bytes across all the codesegment blocks + * num_codesegs: No of Pages used + * codeseg_size: Size of each segment. Should be power of 2 and multiple of 4K + * codeseg_size_log2: log2(codeseg_size) + * codeseg_busaddr: Physical address of the DMAble memory;4K aligned + */ + +#define CODESWAP_MAX_CODESEGS 16 +struct codeswap_codeseg_info { + u32 codeseg_total_bytes; + u32 num_codesegs; + u32 codeseg_size; + u32 codeseg_size_log2; + void *codeseg_busaddr[CODESWAP_MAX_CODESEGS]; +}; + +struct image_desc_info { + dma_addr_t fw_addr; + u32 fw_size; + dma_addr_t bdata_addr; + u32 bdata_size; +}; + +/* platform capabilities */ +enum cnss_platform_cap_flag { + CNSS_HAS_EXTERNAL_SWREG = 0x01, + CNSS_HAS_UART_ACCESS = 0x02, +}; + +struct cnss_platform_cap { + u32 cap_flag; +}; + +/* WLAN driver status, keep it aligned with cnss2 */ +enum cnss_driver_status { + CNSS_UNINITIALIZED, + CNSS_INITIALIZED, + CNSS_LOAD_UNLOAD, + CNSS_RECOVERY, + CNSS_FW_DOWN, + CNSS_SSR_FAIL, +}; + +enum cnss_runtime_request { + CNSS_PM_RUNTIME_GET, + CNSS_PM_RUNTIME_PUT, + CNSS_PM_RUNTIME_MARK_LAST_BUSY, + CNSS_PM_RUNTIME_RESUME, + CNSS_PM_RUNTIME_PUT_NOIDLE, + CNSS_PM_REQUEST_RESUME, + CNSS_PM_RUNTIME_PUT_AUTO, + CNSS_PM_GET_NORESUME, +}; + +struct dma_iommu_mapping *cnss_smmu_get_mapping(void); +int cnss_smmu_map(phys_addr_t paddr, uint32_t *iova_addr, size_t size); +int cnss_get_fw_image(struct image_desc_info *image_desc_info); +void cnss_runtime_init(struct device *dev, int auto_delay); +void cnss_runtime_exit(struct device *dev); +void cnss_wlan_pci_link_down(void); +int cnss_pcie_shadow_control(struct pci_dev *dev, bool enable); +int cnss_wlan_register_driver(struct cnss_wlan_driver *driver); +void cnss_wlan_unregister_driver(struct cnss_wlan_driver *driver); +int cnss_get_fw_files(struct cnss_fw_files *pfw_files); +int cnss_get_fw_files_for_target(struct cnss_fw_files *pfw_files, + u32 target_type, u32 target_version); +void cnss_get_qca9377_fw_files(struct cnss_fw_files *pfw_files, + u32 size, u32 tufello_dual_fw); + +int cnss_request_bus_bandwidth(int bandwidth); + +#ifdef CONFIG_CNSS_SECURE_FW +int cnss_get_sha_hash(const u8 *data, u32 data_len, + u8 *hash_idx, u8 *out); +void *cnss_get_fw_ptr(void); +#endif + +int cnss_get_codeswap_struct(struct codeswap_codeseg_info *swap_seg); +int cnss_get_bmi_setup(void); + +#ifdef CONFIG_PCI_MSM +int cnss_wlan_pm_control(bool vote); +#endif +void cnss_lock_pm_sem(void); +void cnss_release_pm_sem(void); + +void cnss_request_pm_qos_type(int latency_type, u32 qos_val); +void cnss_request_pm_qos(u32 qos_val); +void cnss_remove_pm_qos(void); + +void cnss_pci_request_pm_qos_type(int latency_type, u32 qos_val); +void cnss_pci_request_pm_qos(u32 qos_val); +void cnss_pci_remove_pm_qos(void); + +void cnss_sdio_request_pm_qos_type(int latency_type, u32 qos_val); +void cnss_sdio_request_pm_qos(u32 qos_val); +void cnss_sdio_remove_pm_qos(void); + +int cnss_get_platform_cap(struct cnss_platform_cap *cap); +void cnss_set_driver_status(enum cnss_driver_status driver_status); + +#ifndef CONFIG_WCNSS_MEM_PRE_ALLOC +static inline int wcnss_pre_alloc_reset(void) { return 0; } +#endif + +int msm_pcie_enumerate(u32 rc_idx); +int cnss_auto_suspend(void); +int cnss_auto_resume(void); +int cnss_prevent_auto_suspend(const char *caller_func); +int cnss_allow_auto_suspend(const char *caller_func); +int cnss_is_auto_suspend_allowed(const char *caller_func); + +int cnss_pm_runtime_request(struct device *dev, enum + cnss_runtime_request request); +void cnss_set_cc_source(enum cnss_cc_src cc_source); +enum cnss_cc_src cnss_get_cc_source(void); +#endif + +void cnss_pm_wake_lock_init(struct wakeup_source **ws, const char *name); +void cnss_pm_wake_lock(struct wakeup_source *ws); + +void cnss_device_crashed(void); +void cnss_device_self_recovery(void); +void *cnss_get_virt_ramdump_mem(unsigned long *size); + +void cnss_schedule_recovery_work(void); +int cnss_pcie_set_wlan_mac_address(const u8 *in, uint32_t len); +u8 *cnss_get_wlan_mac_address(struct device *dev, uint32_t *num); +int cnss_sdio_set_wlan_mac_address(const u8 *in, uint32_t len); + +enum { + CNSS_RESET_SOC = 0, + CNSS_RESET_SUBSYS_COUPLED, + CNSS_RESET_LEVEL_MAX +}; + +int cnss_get_restart_level(void); + +struct cnss_sdio_wlan_driver { + const char *name; + const struct sdio_device_id *id_table; + int (*probe)(struct sdio_func *func, const struct sdio_device_id *id); + void (*remove)(struct sdio_func *func); + int (*reinit)(struct sdio_func *func, const struct sdio_device_id *id); + void (*shutdown)(struct sdio_func *func); + void (*crash_shutdown)(struct sdio_func *func); + int (*suspend)(struct device *dev); + int (*resume)(struct device *dev); +}; + +int cnss_sdio_wlan_register_driver(struct cnss_sdio_wlan_driver *driver); +void cnss_sdio_wlan_unregister_driver(struct cnss_sdio_wlan_driver *driver); + +typedef void (*oob_irq_handler_t)(void *dev_para); +int cnss_wlan_query_oob_status(void); +int cnss_wlan_register_oob_irq_handler(oob_irq_handler_t handler, void *pm_oob); +int cnss_wlan_unregister_oob_irq_handler(void *pm_oob); + +void cnss_dump_stack(struct task_struct *task); +u8 *cnss_common_get_wlan_mac_address(struct device *dev, uint32_t *num); +void cnss_init_work(struct work_struct *work, work_func_t func); +void cnss_flush_delayed_work(void *dwork); +void cnss_flush_work(void *work); +void cnss_pm_wake_lock_timeout(struct wakeup_source *ws, ulong msec); +void cnss_pm_wake_lock_release(struct wakeup_source *ws); +void cnss_pm_wake_lock_destroy(struct wakeup_source *ws); +void cnss_get_monotonic_boottime(struct timespec64 *ts); +void cnss_get_boottime(struct timespec *ts); +void cnss_init_delayed_work(struct delayed_work *work, work_func_t func); +int cnss_vendor_cmd_reply(struct sk_buff *skb); +int cnss_set_cpus_allowed_ptr(struct task_struct *task, ulong cpu); +int cnss_set_wlan_unsafe_channel(u16 *unsafe_ch_list, u16 ch_count); +int cnss_get_wlan_unsafe_channel(u16 *unsafe_ch_list, u16 *ch_count, u16 buf_len); +int cnss_wlan_set_dfs_nol(const void *info, u16 info_len); +int cnss_wlan_get_dfs_nol(void *info, u16 info_len); +int cnss_common_request_bus_bandwidth(struct device *dev, int bandwidth); +void cnss_common_device_crashed(struct device *dev); +void cnss_common_device_self_recovery(struct device *dev); +void *cnss_common_get_virt_ramdump_mem(struct device *dev, unsigned long *size); +void cnss_common_schedule_recovery_work(struct device *dev); +int cnss_common_set_wlan_mac_address(struct device *dev, const u8 *in, uint32_t len); +u8 *cnss_common_get_wlan_mac_address(struct device *dev, uint32_t *num); +int cnss_power_up(struct device *dev); +int cnss_power_down(struct device *dev); +int cnss_sdio_configure_spdt(bool state); + +int cnss_common_register_tsf_captured_handler(struct device *dev, irq_handler_t handler, + void *ctx); +int cnss_common_unregister_tsf_captured_handler(struct device *dev, void *ctx); +#endif /* _NET_CNSS_H_ */ diff --git a/include/net/cnss_logger.h b/include/net/cnss_logger.h new file mode 100644 index 000000000000..dcdb7fa18f2b --- /dev/null +++ b/include/net/cnss_logger.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2016,2021, The Linux Foundation. All rights reserved. */ + +#ifndef _NET_CNSS_LOGGER_H_ +#define _NET_CNSS_LOGGER_H_ + +struct sk_buff; +struct wiphy; + +#if IS_ENABLED(CONFIG_CNSS_LOGGER) +int cnss_logger_event_register(int radio, int event, + int (*cb)(struct sk_buff *skb)); +int cnss_logger_event_unregister(int radio, int event, + int (*cb)(struct sk_buff *skb)); +int cnss_logger_device_register(struct wiphy *wiphy, const char *name); +int cnss_logger_device_unregister(int radio, struct wiphy *wiphy); +int cnss_logger_nl_ucast(struct sk_buff *skb, int portid, int flag); +int cnss_logger_nl_bcast(struct sk_buff *skb, int portid, int flag); +#else +static inline int cnss_logger_event_register(int radio, int event, + int (*cb)(struct sk_buff *skb)) +{ + return 0; +} + +static inline int cnss_logger_event_unregister(int radio, int event, + int (*cb)(struct sk_buff *skb)) +{ + return 0; +} + +static inline int cnss_logger_device_register(struct wiphy *wiphy, + const char *name) +{ + return 0; +} + +static inline int cnss_logger_device_unregister(int radio, struct wiphy *wiphy) +{ + return 0; +} + +static inline int cnss_logger_nl_ucast(struct sk_buff *skb, int portid, + int flag) +{ + return 0; +} + +static inline int cnss_logger_nl_bcast(struct sk_buff *skb, int portid, + int flag) +{ + return 0; +} +#endif /* CONFIG_CNSS_LOGGER */ +#endif /* _NET_CNSS_LOGGER_H_ */ + diff --git a/include/uapi/linux/esoc_ctrl.h b/include/uapi/linux/esoc_ctrl.h new file mode 100644 index 000000000000..891b4c6ead25 --- /dev/null +++ b/include/uapi/linux/esoc_ctrl.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __UAPI_ESOC_CTRL_H__ +#define __UAPI_ESOC_CTRL_H__ + +#include + +#define ESOC_CODE 0xCC + +#define ESOC_CMD_EXE _IOW(ESOC_CODE, 1, unsigned int) +#define ESOC_WAIT_FOR_REQ _IOR(ESOC_CODE, 2, unsigned int) +#define ESOC_NOTIFY _IOW(ESOC_CODE, 3, unsigned int) +#define ESOC_GET_STATUS _IOR(ESOC_CODE, 4, unsigned int) +#define ESOC_GET_ERR_FATAL _IOR(ESOC_CODE, 5, unsigned int) +#define ESOC_WAIT_FOR_CRASH _IOR(ESOC_CODE, 6, unsigned int) +#define ESOC_REG_REQ_ENG _IO(ESOC_CODE, 7) +#define ESOC_REG_CMD_ENG _IO(ESOC_CODE, 8) +#define ESOC_GET_LINK_ID _IOWR(ESOC_CODE, 9, __u64) +#define ESOC_SET_BOOT_FAIL_ACT _IOW(ESOC_CODE, 10, unsigned int) +#define ESOC_SET_N_PON_TRIES _IOW(ESOC_CODE, 11, unsigned int) + +#define ESOC_REQ_SEND_SHUTDOWN ESOC_REQ_SEND_SHUTDOWN +#define ESOC_REQ_CRASH_SHUTDOWN ESOC_REQ_CRASH_SHUTDOWN +#define ESOC_PON_RETRY ESOC_PON_RETRY +#define ESOC_BOOT_FAIL_ACTION +#define ESOC_LINK_ID + +enum esoc_boot_fail_action { + BOOT_FAIL_ACTION_RETRY, + BOOT_FAIL_ACTION_COLD_RESET, + BOOT_FAIL_ACTION_SHUTDOWN, + BOOT_FAIL_ACTION_PANIC, + BOOT_FAIL_ACTION_NOP, + BOOT_FAIL_ACTION_S3_RESET, + BOOT_FAIL_ACTION_LAST, +}; + +enum esoc_evt { + ESOC_RUN_STATE = 0x1, + ESOC_UNEXPECTED_RESET, + ESOC_ERR_FATAL, + ESOC_IN_DEBUG, + ESOC_REQ_ENG_ON, + ESOC_REQ_ENG_OFF, + ESOC_CMD_ENG_ON, + ESOC_CMD_ENG_OFF, + ESOC_INVALID_STATE, + ESOC_RETRY_PON_EVT, + ESOC_BOOT_STATE, +}; + +enum esoc_cmd { + ESOC_PWR_ON = 1, + ESOC_PWR_OFF, + ESOC_FORCE_PWR_OFF, + ESOC_RESET, + ESOC_PREPARE_DEBUG, + ESOC_EXE_DEBUG, + ESOC_EXIT_DEBUG, +}; + +enum esoc_notify { + ESOC_IMG_XFER_DONE = 1, + ESOC_BOOT_DONE, + ESOC_BOOT_FAIL, + ESOC_IMG_XFER_RETRY, + ESOC_IMG_XFER_FAIL, + ESOC_UPGRADE_AVAILABLE, + ESOC_DEBUG_DONE, + ESOC_DEBUG_FAIL, + ESOC_PRIMARY_CRASH, + ESOC_PRIMARY_REBOOT, + ESOC_PON_RETRY, +}; + +enum esoc_req { + ESOC_REQ_IMG = 1, + ESOC_REQ_DEBUG, + ESOC_REQ_SHUTDOWN, + ESOC_REQ_SEND_SHUTDOWN, + ESOC_REQ_CRASH_SHUTDOWN, +}; + +#endif