3123 lines
74 KiB
C
3123 lines
74 KiB
C
|
/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*/
|
||
|
#include <asm/dma-iommu.h>
|
||
|
#include <linux/iommu.h>
|
||
|
#include <linux/export.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/regulator/consumer.h>
|
||
|
#include <linux/gpio.h>
|
||
|
#include <linux/of_gpio.h>
|
||
|
#include <linux/pm.h>
|
||
|
#include <linux/pm_wakeup.h>
|
||
|
#include <linux/sched.h>
|
||
|
#include <linux/pm_qos.h>
|
||
|
#include <linux/pm_runtime.h>
|
||
|
#include <linux/esoc_client.h>
|
||
|
#include <linux/pinctrl/consumer.h>
|
||
|
#include <linux/firmware.h>
|
||
|
#include <linux/dma-mapping.h>
|
||
|
#include <linux/msm-bus.h>
|
||
|
#include <linux/msm-bus-board.h>
|
||
|
#include <linux/spinlock.h>
|
||
|
#include <linux/suspend.h>
|
||
|
#include <linux/rwsem.h>
|
||
|
#include <linux/crypto.h>
|
||
|
#include <linux/scatterlist.h>
|
||
|
#include <linux/log2.h>
|
||
|
#ifdef CONFIG_PCI_MSM
|
||
|
#include <linux/msm_pcie.h>
|
||
|
#else
|
||
|
#include <mach/msm_pcie.h>
|
||
|
#endif
|
||
|
#include <soc/qcom/subsystem_restart.h>
|
||
|
#include <soc/qcom/subsystem_notif.h>
|
||
|
#include <soc/qcom/ramdump.h>
|
||
|
#include <net/cfg80211.h>
|
||
|
#include <soc/qcom/memory_dump.h>
|
||
|
#include <net/cnss.h>
|
||
|
|
||
|
#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
|
||
|
#include <net/cnss_prealloc.h>
|
||
|
#endif
|
||
|
|
||
|
#define subsys_to_drv(d) container_of(d, struct cnss_data, subsys_desc)
|
||
|
|
||
|
#define VREG_ON 1
|
||
|
#define VREG_OFF 0
|
||
|
#define WLAN_EN_HIGH 1
|
||
|
#define WLAN_EN_LOW 0
|
||
|
#define PCIE_LINK_UP 1
|
||
|
#define PCIE_LINK_DOWN 0
|
||
|
#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 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
|
||
|
|
||
|
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"};
|
||
|
|
||
|
#define QCA6180_VENDOR_ID (0x168C)
|
||
|
#define QCA6180_DEVICE_ID (0x0041)
|
||
|
#define QCA6180_REV_ID_OFFSET (0x08)
|
||
|
|
||
|
#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_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_ENABLE_DELAY 10
|
||
|
#define WLAN_RECOVERY_DELAY 1
|
||
|
#define PCIE_ENABLE_DELAY 100
|
||
|
#define WLAN_BOOTSTRAP_DELAY 10
|
||
|
#define EVICT_BIN_MAX_SIZE (512*1024)
|
||
|
#define CNSS_PINCTRL_STATE_ACTIVE "default"
|
||
|
|
||
|
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 pinctrl *pinctrl;
|
||
|
struct pinctrl_state *gpio_state_default;
|
||
|
};
|
||
|
|
||
|
struct cnss_wlan_vreg_info {
|
||
|
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_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];
|
||
|
};
|
||
|
|
||
|
/* 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;
|
||
|
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;
|
||
|
u16 unsafe_ch_count;
|
||
|
u16 unsafe_ch_list[CNSS_MAX_CH_NUM];
|
||
|
struct cnss_wlan_driver *driver;
|
||
|
struct pci_dev *pdev;
|
||
|
const struct pci_device_id *id;
|
||
|
struct dma_iommu_mapping *smmu_mapping;
|
||
|
dma_addr_t smmu_iova_start;
|
||
|
size_t smmu_iova_len;
|
||
|
struct cnss_wlan_vreg_info vreg_info;
|
||
|
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;
|
||
|
u16 dfs_nol_info_len;
|
||
|
bool recovery_in_progress;
|
||
|
bool 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;
|
||
|
uint32_t 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;
|
||
|
uint32_t recovery_count;
|
||
|
enum cnss_driver_status driver_status;
|
||
|
void *dfs_nol_info;
|
||
|
#ifdef CONFIG_CNSS_SECURE_FW
|
||
|
void *fw_mem;
|
||
|
#endif
|
||
|
u32 device_id;
|
||
|
int fw_image_setup;
|
||
|
uint32_t 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];
|
||
|
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;
|
||
|
} *penv;
|
||
|
|
||
|
static unsigned int pcie_link_down_panic;
|
||
|
module_param(pcie_link_down_panic, uint, S_IRUSR | S_IWUSR);
|
||
|
MODULE_PARM_DESC(pcie_link_down_panic,
|
||
|
"Trigger kernel panic when PCIe link down is detected");
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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_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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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:
|
||
|
if (vreg_info->wlan_reg_xtal)
|
||
|
regulator_disable(vreg_info->wlan_reg_xtal);
|
||
|
error_enable_reg_xtal:
|
||
|
if (vreg_info->wlan_reg_io)
|
||
|
regulator_disable(vreg_info->wlan_reg_io);
|
||
|
error_enable_reg_io:
|
||
|
regulator_disable(vreg_info->wlan_reg);
|
||
|
error_enable:
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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_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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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_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_pinctrl_init(struct cnss_wlan_gpio_info *gpio_info,
|
||
|
struct platform_device *pdev)
|
||
|
{
|
||
|
int ret;
|
||
|
gpio_info->pinctrl = devm_pinctrl_get(&pdev->dev);
|
||
|
if (IS_ERR_OR_NULL(gpio_info->pinctrl)) {
|
||
|
pr_err("%s: Failed to get pinctrl!\n", __func__);
|
||
|
return PTR_ERR(gpio_info->pinctrl);
|
||
|
}
|
||
|
|
||
|
gpio_info->gpio_state_default = pinctrl_lookup_state(gpio_info->pinctrl,
|
||
|
CNSS_PINCTRL_STATE_ACTIVE);
|
||
|
if (IS_ERR_OR_NULL(gpio_info->gpio_state_default)) {
|
||
|
pr_err("%s: Can not get active pin state!\n", __func__);
|
||
|
return PTR_ERR(gpio_info->gpio_state_default);
|
||
|
}
|
||
|
|
||
|
ret = pinctrl_select_state(gpio_info->pinctrl,
|
||
|
gpio_info->gpio_state_default);
|
||
|
|
||
|
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;
|
||
|
|
||
|
if (of_get_property(pdev->dev.of_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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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(pdev->dev.of_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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (of_get_property(pdev->dev.of_node,
|
||
|
WLAN_VREG_XTAL_NAME"-supply", NULL)) {
|
||
|
vreg_info->wlan_reg_xtal =
|
||
|
regulator_get(&pdev->dev, WLAN_VREG_XTAL_NAME);
|
||
|
if (!IS_ERR(vreg_info->wlan_reg_xtal)) {
|
||
|
ret = regulator_set_voltage(vreg_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 err_reg_xtal_set;
|
||
|
}
|
||
|
|
||
|
ret = regulator_enable(vreg_info->wlan_reg_xtal);
|
||
|
if (ret) {
|
||
|
pr_err("%s: Enable wlan_vreg_xtal failed!\n",
|
||
|
__func__);
|
||
|
goto err_reg_xtal_enable;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (of_get_property(pdev->dev.of_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(pdev->dev.of_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_optimum_mode(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((&pdev->dev)->of_node,
|
||
|
"qcom,wlan-uart-access", NULL))
|
||
|
penv->cap.cap_flag |= CNSS_HAS_UART_ACCESS;
|
||
|
|
||
|
if (of_get_property(pdev->dev.of_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;
|
||
|
}
|
||
|
|
||
|
vreg_info->state = VREG_ON;
|
||
|
|
||
|
if (!of_find_property((&pdev->dev)->of_node, gpio_info->name, NULL)) {
|
||
|
gpio_info->prop = false;
|
||
|
goto end;
|
||
|
}
|
||
|
|
||
|
gpio_info->prop = true;
|
||
|
ret = of_get_named_gpio((&pdev->dev)->of_node,
|
||
|
gpio_info->name, 0);
|
||
|
|
||
|
if (ret >= 0) {
|
||
|
gpio_info->num = ret;
|
||
|
ret = 0;
|
||
|
} else {
|
||
|
if (ret == -EPROBE_DEFER)
|
||
|
pr_debug("get WLAN_EN GPIO probe defer\n");
|
||
|
else
|
||
|
pr_err("can't get gpio %s ret %d",
|
||
|
gpio_info->name, ret);
|
||
|
goto err_get_gpio;
|
||
|
}
|
||
|
|
||
|
ret = cnss_pinctrl_init(gpio_info, pdev);
|
||
|
if (ret) {
|
||
|
pr_err("%s: pinctrl init failed!\n", __func__);
|
||
|
goto err_pinctrl_init;
|
||
|
}
|
||
|
|
||
|
ret = cnss_wlan_gpio_init(gpio_info);
|
||
|
if (ret) {
|
||
|
pr_err("gpio init failed\n");
|
||
|
goto err_gpio_init;
|
||
|
}
|
||
|
|
||
|
if (of_find_property((&pdev->dev)->of_node,
|
||
|
WLAN_BOOTSTRAP_GPIO_NAME, NULL)) {
|
||
|
penv->wlan_bootstrap_gpio =
|
||
|
of_get_named_gpio((&pdev->dev)->of_node,
|
||
|
WLAN_BOOTSTRAP_GPIO_NAME, 0);
|
||
|
if (penv->wlan_bootstrap_gpio > 0) {
|
||
|
ret = cnss_wlan_bootstrap_gpio_init();
|
||
|
if (ret)
|
||
|
goto err_gpio_init;
|
||
|
} else {
|
||
|
if (ret == -EPROBE_DEFER) {
|
||
|
pr_debug("%s: Get GPIO %s probe defer\n",
|
||
|
__func__, WLAN_BOOTSTRAP_GPIO_NAME);
|
||
|
} else {
|
||
|
pr_err("%s: Can't get GPIO %s, ret = %d",
|
||
|
__func__, WLAN_BOOTSTRAP_GPIO_NAME,
|
||
|
ret);
|
||
|
}
|
||
|
goto err_gpio_init;
|
||
|
}
|
||
|
}
|
||
|
end:
|
||
|
return ret;
|
||
|
|
||
|
err_gpio_init:
|
||
|
err_pinctrl_init:
|
||
|
err_get_gpio:
|
||
|
if (vreg_info->soc_swreg)
|
||
|
regulator_disable(vreg_info->soc_swreg);
|
||
|
vreg_info->state = VREG_OFF;
|
||
|
|
||
|
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);
|
||
|
if (vreg_info->wlan_reg_xtal)
|
||
|
regulator_disable(vreg_info->wlan_reg_xtal);
|
||
|
|
||
|
err_reg_xtal_enable:
|
||
|
err_reg_xtal_set:
|
||
|
if (vreg_info->wlan_reg_xtal)
|
||
|
regulator_put(vreg_info->wlan_reg_xtal);
|
||
|
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);
|
||
|
regulator_disable(vreg_info->wlan_reg);
|
||
|
|
||
|
err_reg_enable:
|
||
|
regulator_put(vreg_info->wlan_reg);
|
||
|
|
||
|
err_reg_get:
|
||
|
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);
|
||
|
gpio_free(gpio_info->num);
|
||
|
gpio_info->state = WLAN_EN_LOW;
|
||
|
gpio_info->prop = false;
|
||
|
cnss_wlan_vreg_set(vreg_info, VREG_OFF);
|
||
|
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);
|
||
|
if (vreg_info->wlan_reg_xtal)
|
||
|
regulator_put(vreg_info->wlan_reg_xtal);
|
||
|
if (vreg_info->wlan_reg_io)
|
||
|
regulator_put(vreg_info->wlan_reg_io);
|
||
|
regulator_put(vreg_info->wlan_reg);
|
||
|
if (vreg_info->wlan_reg_core)
|
||
|
regulator_put(vreg_info->wlan_reg_core);
|
||
|
vreg_info->state = VREG_OFF;
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
|
||
|
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 version mismatch 0x%X 0x%X",
|
||
|
__func__, target_type, target_version);
|
||
|
break;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(cnss_get_fw_files_for_target);
|
||
|
|
||
|
#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);
|
||
|
|
||
|
if (!penv->fw_mem)
|
||
|
pr_debug("Memory not available for Secure FW\n");
|
||
|
}
|
||
|
#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", *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;
|
||
|
|
||
|
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);
|
||
|
for (count = 0; count < pseg_mem->size -
|
||
|
sizeof(struct region_desc); count++)
|
||
|
pr_debug("%02x", dump_addr[count]);
|
||
|
|
||
|
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);
|
||
|
for (count = 0; count < p_bdata_seg_mem->size -
|
||
|
sizeof(struct region_desc); count++)
|
||
|
pr_debug("%02x ", dump_addr[count]);
|
||
|
|
||
|
p_bdata_seg_mem++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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) {
|
||
|
pr_err("cnss: image desc allocation failure\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
memset(vaddr, 0, image_desc_size);
|
||
|
|
||
|
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",
|
||
|
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) {
|
||
|
pr_err("cnss: region allocation failure\n");
|
||
|
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", 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", __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;
|
||
|
|
||
|
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;
|
||
|
|
||
|
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, S_IRUSR,
|
||
|
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)
|
||
|
{
|
||
|
struct dma_iommu_mapping *mapping;
|
||
|
int disable_htw = 1;
|
||
|
int atomic_ctx = 1;
|
||
|
int ret;
|
||
|
|
||
|
mapping = arm_iommu_create_mapping(&platform_bus_type,
|
||
|
penv->smmu_iova_start,
|
||
|
penv->smmu_iova_len);
|
||
|
if (IS_ERR(mapping)) {
|
||
|
pr_err("%s: create mapping failed, err = %d\n", __func__, ret);
|
||
|
ret = PTR_ERR(mapping);
|
||
|
goto map_fail;
|
||
|
}
|
||
|
|
||
|
ret = iommu_domain_set_attr(mapping->domain,
|
||
|
DOMAIN_ATTR_COHERENT_HTW_DISABLE,
|
||
|
&disable_htw);
|
||
|
if (ret) {
|
||
|
pr_err("%s: set disable_htw attribute failed, err = %d\n",
|
||
|
__func__, ret);
|
||
|
goto set_attr_fail;
|
||
|
}
|
||
|
|
||
|
ret = iommu_domain_set_attr(mapping->domain,
|
||
|
DOMAIN_ATTR_ATOMIC,
|
||
|
&atomic_ctx);
|
||
|
if (ret) {
|
||
|
pr_err("%s: set atomic_ctx attribute 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;
|
||
|
}
|
||
|
|
||
|
static void cnss_smmu_remove(struct device *dev)
|
||
|
{
|
||
|
arm_iommu_detach_device(dev);
|
||
|
arm_iommu_release_mapping(penv->smmu_mapping);
|
||
|
|
||
|
penv->smmu_mapping = 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;
|
||
|
struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info;
|
||
|
void *cpu_addr;
|
||
|
dma_addr_t dma_handle;
|
||
|
struct codeswap_codeseg_info *cnss_seg_info = NULL;
|
||
|
struct device *dev = &pdev->dev;
|
||
|
|
||
|
penv->pdev = pdev;
|
||
|
penv->id = id;
|
||
|
penv->fw_available = false;
|
||
|
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);
|
||
|
goto smmu_init_fail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 = pci_store_saved_state(pdev);
|
||
|
|
||
|
ret = msm_pcie_pm_control(MSM_PCIE_SUSPEND,
|
||
|
cnss_get_pci_dev_bus_number(pdev),
|
||
|
pdev, NULL, PM_OPTIONS);
|
||
|
if (ret) {
|
||
|
pr_err("Failed to shutdown PCIe link\n");
|
||
|
goto err_pcie_suspend;
|
||
|
}
|
||
|
penv->pcie_link_state = PCIE_LINK_DOWN;
|
||
|
}
|
||
|
|
||
|
cnss_wlan_gpio_set(gpio_info, WLAN_EN_LOW);
|
||
|
ret = cnss_wlan_vreg_set(vreg_info, VREG_OFF);
|
||
|
|
||
|
if (ret) {
|
||
|
pr_err("can't turn off wlan vreg\n");
|
||
|
goto err_pcie_suspend;
|
||
|
}
|
||
|
|
||
|
cnss_wlan_fw_mem_alloc(pdev);
|
||
|
|
||
|
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) {
|
||
|
pr_err("cnss: Memory Alloc failed for codeswap feature\n");
|
||
|
goto err_pcie_suspend;
|
||
|
}
|
||
|
|
||
|
memset(cpu_addr, 0, EVICT_BIN_MAX_SIZE);
|
||
|
cnss_seg_info = devm_kzalloc(dev, sizeof(*cnss_seg_info),
|
||
|
GFP_KERNEL);
|
||
|
if (!cnss_seg_info) {
|
||
|
pr_err("Fail to allocate memory for cnss_seg_info\n");
|
||
|
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:
|
||
|
smmu_init_fail:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void cnss_wlan_pci_remove(struct pci_dev *pdev)
|
||
|
{
|
||
|
struct device *dev;
|
||
|
|
||
|
if (!penv)
|
||
|
return;
|
||
|
|
||
|
dev = &penv->pldev->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;
|
||
|
|
||
|
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 = 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;
|
||
|
|
||
|
wdriver = penv->driver;
|
||
|
if (!wdriver)
|
||
|
goto out;
|
||
|
|
||
|
if (wdriver->resume && !penv->pcie_link_down_ind) {
|
||
|
if (penv->saved_state)
|
||
|
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 DEFINE_PCI_DEVICE_TABLE(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;
|
||
|
|
||
|
if (sscanf(buf, "%d", &val) != 1)
|
||
|
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",
|
||
|
__func__, ret);
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static DEVICE_ATTR(fw_image_setup, S_IRUSR | S_IWUSR,
|
||
|
fw_image_setup_show, fw_image_setup_store);
|
||
|
|
||
|
void recovery_work_handler(struct work_struct *recovery)
|
||
|
{
|
||
|
cnss_device_self_recovery();
|
||
|
}
|
||
|
|
||
|
DECLARE_WORK(recovery_work, recovery_work_handler);
|
||
|
|
||
|
void cnss_schedule_recovery_work(void)
|
||
|
{
|
||
|
schedule_work(&recovery_work);
|
||
|
}
|
||
|
EXPORT_SYMBOL(cnss_schedule_recovery_work);
|
||
|
|
||
|
void cnss_pci_events_cb(struct msm_pcie_notify *notify)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
|
||
|
if (notify == NULL)
|
||
|
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");
|
||
|
schedule_work(&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(&recovery_work);
|
||
|
}
|
||
|
EXPORT_SYMBOL(cnss_wlan_pci_link_down);
|
||
|
|
||
|
int cnss_pcie_shadow_control(struct pci_dev *dev, bool enable)
|
||
|
{
|
||
|
return 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;
|
||
|
|
||
|
if (!cnss_seg_info) {
|
||
|
swap_seg = NULL;
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
if (!penv->fw_available) {
|
||
|
pr_debug("%s: fw is not available\n", __func__);
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
|
||
|
*swap_seg = *cnss_seg_info;
|
||
|
|
||
|
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 = FW_FILES_QCA6174_FW_3_0.evicted_data;
|
||
|
u_int32_t 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;
|
||
|
u_int32_t total_length = 0;
|
||
|
struct pci_dev *pdev;
|
||
|
|
||
|
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");
|
||
|
goto end;
|
||
|
}
|
||
|
|
||
|
if (penv->fw_available) {
|
||
|
pr_debug("cnss: fw code already copied to host memory\n");
|
||
|
goto end;
|
||
|
}
|
||
|
|
||
|
if (request_firmware(&fw_entry, filename, dev) != 0) {
|
||
|
pr_debug("cnss: failed to get fw: %s\n", filename);
|
||
|
goto end;
|
||
|
}
|
||
|
|
||
|
if (!fw_entry || !fw_entry->data) {
|
||
|
pr_err("%s: INVALID FW entries\n", __func__);
|
||
|
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;
|
||
|
penv->fw_available = 1;
|
||
|
|
||
|
release_fw:
|
||
|
release_firmware(fw_entry);
|
||
|
end:
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
wdrv = penv->driver;
|
||
|
vreg_info = &penv->vreg_info;
|
||
|
gpio_info = &penv->gpio_info;
|
||
|
pdev = penv->pdev;
|
||
|
|
||
|
if (!wdrv) {
|
||
|
penv->driver = wdrv = driver;
|
||
|
} else {
|
||
|
pr_err("driver already registered\n");
|
||
|
return -EEXIST;
|
||
|
}
|
||
|
|
||
|
again:
|
||
|
ret = cnss_wlan_vreg_set(vreg_info, VREG_ON);
|
||
|
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_wlan_gpio_set(gpio_info, WLAN_EN_HIGH);
|
||
|
msleep(WLAN_ENABLE_DELAY);
|
||
|
|
||
|
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 = 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 = msm_pcie_pm_control(MSM_PCIE_RESUME,
|
||
|
cnss_get_pci_dev_bus_number(pdev),
|
||
|
pdev, NULL, PM_OPTIONS);
|
||
|
if (ret) {
|
||
|
pr_err("PCIe link bring-up failed\n");
|
||
|
goto err_pcie_link_up;
|
||
|
}
|
||
|
penv->pcie_link_state = PCIE_LINK_UP;
|
||
|
} else if (!penv->pcie_link_state && penv->pcie_link_down_ind) {
|
||
|
|
||
|
ret = msm_pcie_pm_control(MSM_PCIE_RESUME,
|
||
|
cnss_get_pci_dev_bus_number(pdev),
|
||
|
pdev, NULL, 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 = PCIE_LINK_UP;
|
||
|
|
||
|
ret = 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)
|
||
|
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 = pci_store_saved_state(pdev);
|
||
|
msm_pcie_deregister_event(&penv->event_reg);
|
||
|
msm_pcie_pm_control(MSM_PCIE_SUSPEND,
|
||
|
cnss_get_pci_dev_bus_number(pdev),
|
||
|
pdev, NULL, PM_OPTIONS);
|
||
|
penv->pcie_link_state = PCIE_LINK_DOWN;
|
||
|
cnss_wlan_gpio_set(gpio_info, WLAN_EN_LOW);
|
||
|
msleep(WLAN_ENABLE_DELAY);
|
||
|
cnss_wlan_vreg_set(vreg_info, VREG_OFF);
|
||
|
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 = pci_store_saved_state(pdev);
|
||
|
|
||
|
err_pcie_link_up:
|
||
|
msm_pcie_deregister_event(&penv->event_reg);
|
||
|
if (penv->pcie_link_state) {
|
||
|
msm_pcie_pm_control(MSM_PCIE_SUSPEND,
|
||
|
cnss_get_pci_dev_bus_number(pdev),
|
||
|
pdev, NULL, PM_OPTIONS);
|
||
|
penv->pcie_link_state = PCIE_LINK_DOWN;
|
||
|
}
|
||
|
|
||
|
err_pcie_reg:
|
||
|
cnss_wlan_gpio_set(gpio_info, WLAN_EN_LOW);
|
||
|
cnss_wlan_vreg_set(vreg_info, VREG_OFF);
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
if (penv->bus_client)
|
||
|
msm_bus_scale_client_update_request(penv->bus_client,
|
||
|
CNSS_BUS_WIDTH_NONE);
|
||
|
|
||
|
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();
|
||
|
|
||
|
msm_pcie_deregister_event(&penv->event_reg);
|
||
|
|
||
|
if (penv->pcie_link_state && !penv->pcie_link_down_ind) {
|
||
|
pci_save_state(pdev);
|
||
|
penv->saved_state = pci_store_saved_state(pdev);
|
||
|
|
||
|
if (msm_pcie_pm_control(MSM_PCIE_SUSPEND,
|
||
|
cnss_get_pci_dev_bus_number(pdev),
|
||
|
pdev, NULL, 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 (msm_pcie_pm_control(MSM_PCIE_SUSPEND,
|
||
|
cnss_get_pci_dev_bus_number(pdev),
|
||
|
pdev, NULL, PM_OPTIONS_SUSPEND_LINK_DOWN)) {
|
||
|
pr_err("Failed to shutdown PCIe link (with linkdown option)\n");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
penv->pcie_link_state = PCIE_LINK_DOWN;
|
||
|
penv->driver_status = CNSS_UNINITIALIZED;
|
||
|
penv->monitor_wake_intr = false;
|
||
|
atomic_set(&penv->auto_suspended, 0);
|
||
|
|
||
|
cut_power:
|
||
|
penv->driver = NULL;
|
||
|
|
||
|
cnss_wlan_gpio_set(gpio_info, WLAN_EN_LOW);
|
||
|
|
||
|
if (cnss_wlan_vreg_set(vreg_info, VREG_OFF))
|
||
|
pr_err("wlan vreg OFF failed\n");
|
||
|
}
|
||
|
EXPORT_SYMBOL(cnss_wlan_unregister_driver);
|
||
|
|
||
|
int cnss_set_wlan_unsafe_channel(u16 *unsafe_ch_list, u16 ch_count)
|
||
|
{
|
||
|
if (!penv)
|
||
|
return -ENODEV;
|
||
|
|
||
|
if ((!unsafe_ch_list) || (ch_count > CNSS_MAX_CH_NUM))
|
||
|
return -EINVAL;
|
||
|
|
||
|
penv->unsafe_ch_count = ch_count;
|
||
|
|
||
|
if (ch_count != 0)
|
||
|
memcpy((char *)penv->unsafe_ch_list, (char *)unsafe_ch_list,
|
||
|
ch_count * sizeof(u16));
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
if (!penv)
|
||
|
return -ENODEV;
|
||
|
|
||
|
if (!unsafe_ch_list || !ch_count)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (buf_len < (penv->unsafe_ch_count * sizeof(u16)))
|
||
|
return -ENOMEM;
|
||
|
|
||
|
*ch_count = penv->unsafe_ch_count;
|
||
|
memcpy((char *)unsafe_ch_list, (char *)penv->unsafe_ch_list,
|
||
|
penv->unsafe_ch_count * sizeof(u16));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(cnss_get_wlan_unsafe_channel);
|
||
|
|
||
|
int cnss_wlan_set_dfs_nol(const void *info, u16 info_len)
|
||
|
{
|
||
|
void *temp;
|
||
|
|
||
|
if (!penv)
|
||
|
return -ENODEV;
|
||
|
|
||
|
if (!info || !info_len)
|
||
|
return -EINVAL;
|
||
|
|
||
|
temp = kmalloc(info_len, GFP_KERNEL);
|
||
|
if (!temp)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
memcpy(temp, info, info_len);
|
||
|
|
||
|
kfree(penv->dfs_nol_info);
|
||
|
|
||
|
penv->dfs_nol_info = temp;
|
||
|
penv->dfs_nol_info_len = info_len;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(cnss_wlan_set_dfs_nol);
|
||
|
|
||
|
int cnss_wlan_get_dfs_nol(void *info, u16 info_len)
|
||
|
{
|
||
|
int len;
|
||
|
|
||
|
if (!penv)
|
||
|
return -ENODEV;
|
||
|
|
||
|
if (!info || !info_len)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (penv->dfs_nol_info == NULL || penv->dfs_nol_info_len == 0)
|
||
|
return -ENOENT;
|
||
|
|
||
|
len = min(info_len, penv->dfs_nol_info_len);
|
||
|
|
||
|
memcpy(info, penv->dfs_nol_info, len);
|
||
|
|
||
|
return len;
|
||
|
}
|
||
|
EXPORT_SYMBOL(cnss_wlan_get_dfs_nol);
|
||
|
|
||
|
#ifdef CONFIG_PCI_MSM
|
||
|
int cnss_wlan_pm_control(bool vote)
|
||
|
{
|
||
|
if (!penv || !penv->pdev)
|
||
|
return -ENODEV;
|
||
|
|
||
|
return msm_pcie_pm_control(
|
||
|
vote ? MSM_PCIE_DISABLE_PC : MSM_PCIE_ENABLE_PC,
|
||
|
cnss_get_pci_dev_bus_number(penv->pdev),
|
||
|
penv->pdev, NULL, 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);
|
||
|
|
||
|
int cnss_get_ramdump_mem(unsigned long *address, unsigned long *size)
|
||
|
{
|
||
|
if (!penv || !penv->pldev)
|
||
|
return -ENODEV;
|
||
|
|
||
|
*address = penv->ramdump_phys;
|
||
|
*size = penv->ramdump_size;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(cnss_get_ramdump_mem);
|
||
|
|
||
|
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 (msm_pcie_pm_control(MSM_PCIE_SUSPEND,
|
||
|
cnss_get_pci_dev_bus_number(pdev),
|
||
|
pdev, NULL, PM_OPTIONS_SUSPEND_LINK_DOWN)) {
|
||
|
pr_debug("cnss: Failed to shutdown PCIe link!\n");
|
||
|
ret = -EFAULT;
|
||
|
}
|
||
|
penv->saved_state = NULL;
|
||
|
penv->pcie_link_state = PCIE_LINK_DOWN;
|
||
|
}
|
||
|
|
||
|
cut_power:
|
||
|
cnss_wlan_gpio_set(gpio_info, WLAN_EN_LOW);
|
||
|
|
||
|
if (cnss_wlan_vreg_set(vreg_info, VREG_OFF))
|
||
|
pr_err("cnss: Failed to set WLAN VREG_OFF!\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, VREG_ON);
|
||
|
if (ret) {
|
||
|
pr_err("cnss: Failed to set WLAN VREG_ON!\n");
|
||
|
goto err_wlan_vreg_on;
|
||
|
}
|
||
|
|
||
|
msleep(POWER_ON_DELAY);
|
||
|
cnss_wlan_gpio_set(gpio_info, WLAN_EN_HIGH);
|
||
|
msleep(WLAN_ENABLE_DELAY);
|
||
|
|
||
|
if (!pdev) {
|
||
|
pr_err("%d: invalid pdev\n", __LINE__);
|
||
|
goto err_pcie_link_up;
|
||
|
}
|
||
|
|
||
|
if (!penv->pcie_link_state) {
|
||
|
ret = msm_pcie_pm_control(MSM_PCIE_RESUME,
|
||
|
cnss_get_pci_dev_bus_number(pdev),
|
||
|
pdev, NULL, 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 = PCIE_LINK_UP;
|
||
|
ret = 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)
|
||
|
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 = pci_store_saved_state(pdev);
|
||
|
msm_pcie_pm_control(MSM_PCIE_SUSPEND,
|
||
|
cnss_get_pci_dev_bus_number(pdev),
|
||
|
pdev, NULL, PM_OPTIONS);
|
||
|
penv->pcie_link_state = PCIE_LINK_DOWN;
|
||
|
|
||
|
err_pcie_link_up:
|
||
|
cnss_wlan_gpio_set(gpio_info, WLAN_EN_LOW);
|
||
|
cnss_wlan_vreg_set(vreg_info, VREG_OFF);
|
||
|
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:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
penv->dump_data.version = CNSS_DUMP_FORMAT_VER;
|
||
|
strlcpy(penv->dump_data.name, CNSS_DUMP_NAME,
|
||
|
sizeof(penv->dump_data.name));
|
||
|
|
||
|
if (pdev && wdrv && wdrv->crash_shutdown)
|
||
|
wdrv->crash_shutdown(pdev);
|
||
|
|
||
|
penv->dump_data.magic = CNSS_DUMP_MAGIC_VER_V2;
|
||
|
}
|
||
|
|
||
|
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 (SUBSYS_AFTER_POWERUP == code)
|
||
|
penv->modem_current_status = 1;
|
||
|
else if (SUBSYS_BEFORE_SHUTDOWN == code)
|
||
|
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_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 msm_dump_entry dump_entry;
|
||
|
struct resource *res;
|
||
|
u32 ramdump_size = 0;
|
||
|
u32 smmu_iova_address[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 = WLAN_EN_LOW;
|
||
|
penv->gpio_info.init = WLAN_EN_LOW;
|
||
|
penv->gpio_info.prop = false;
|
||
|
penv->vreg_info.wlan_reg = NULL;
|
||
|
penv->vreg_info.state = VREG_OFF;
|
||
|
penv->pci_register_again = false;
|
||
|
|
||
|
ret = cnss_wlan_get_resources(pdev);
|
||
|
if (ret)
|
||
|
goto err_get_wlan_res;
|
||
|
|
||
|
cnss_wlan_gpio_set(&penv->gpio_info, WLAN_EN_HIGH);
|
||
|
msleep(WLAN_ENABLE_DELAY);
|
||
|
|
||
|
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 = 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 = PCIE_LINK_UP;
|
||
|
|
||
|
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_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", __func__);
|
||
|
goto skip_ramdump;
|
||
|
}
|
||
|
|
||
|
if (penv->ramdump_dynamic) {
|
||
|
penv->dump_data.addr = penv->ramdump_phys;
|
||
|
penv->dump_data.len = penv->ramdump_size;
|
||
|
dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN;
|
||
|
dump_entry.addr = virt_to_phys(&penv->dump_data);
|
||
|
|
||
|
ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &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];
|
||
|
}
|
||
|
|
||
|
ret = pci_register_driver(&cnss_wlan_pci_driver);
|
||
|
if (ret)
|
||
|
goto err_pci_reg;
|
||
|
|
||
|
penv->bus_scale_table = 0;
|
||
|
penv->bus_scale_table = msm_bus_cl_get_pdata(pdev);
|
||
|
|
||
|
if (penv->bus_scale_table) {
|
||
|
penv->bus_client =
|
||
|
msm_bus_scale_register_client(penv->bus_scale_table);
|
||
|
|
||
|
if (!penv->bus_client) {
|
||
|
pr_err("Failed to register with bus_scale client\n");
|
||
|
goto err_bus_reg;
|
||
|
}
|
||
|
}
|
||
|
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_info("cnss: Platform driver probed successfully.\n");
|
||
|
return ret;
|
||
|
|
||
|
err_bus_reg:
|
||
|
if (penv->bus_scale_table)
|
||
|
msm_bus_cl_clear_pdata(penv->bus_scale_table);
|
||
|
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_wlan_gpio_set(&penv->gpio_info, WLAN_EN_LOW);
|
||
|
cnss_wlan_release_resources();
|
||
|
|
||
|
err_get_wlan_res:
|
||
|
penv = NULL;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int cnss_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info;
|
||
|
|
||
|
unregister_pm_notifier(&cnss_pm_notifier);
|
||
|
device_remove_file(&pdev->dev, &dev_attr_fw_image_setup);
|
||
|
|
||
|
cnss_pm_wake_lock_destroy(&penv->ws);
|
||
|
|
||
|
kfree(penv->dfs_nol_info);
|
||
|
|
||
|
if (penv->bus_client)
|
||
|
msm_bus_scale_unregister_client(penv->bus_client);
|
||
|
|
||
|
if (penv->bus_scale_table)
|
||
|
msm_bus_cl_clear_pdata(penv->bus_scale_table);
|
||
|
|
||
|
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_wlan_gpio_set(gpio_info, WLAN_EN_LOW);
|
||
|
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",
|
||
|
.owner = THIS_MODULE,
|
||
|
.of_match_table = cnss_dt_match,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
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(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);
|
||
|
|
||
|
int cnss_request_bus_bandwidth(int bandwidth)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!penv)
|
||
|
return -ENODEV;
|
||
|
|
||
|
if (!penv->bus_client)
|
||
|
return -ENOSYS;
|
||
|
|
||
|
switch (bandwidth) {
|
||
|
case CNSS_BUS_WIDTH_NONE:
|
||
|
case CNSS_BUS_WIDTH_LOW:
|
||
|
case CNSS_BUS_WIDTH_MEDIUM:
|
||
|
case CNSS_BUS_WIDTH_HIGH:
|
||
|
ret = msm_bus_scale_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", __func__, bandwidth);
|
||
|
ret = -EINVAL;
|
||
|
}
|
||
|
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 = 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 (msm_pcie_pm_control(MSM_PCIE_SUSPEND,
|
||
|
cnss_get_pci_dev_bus_number(pdev),
|
||
|
pdev, NULL, 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 = PCIE_LINK_DOWN;
|
||
|
|
||
|
msm_bus_scale_client_update_request(penv->bus_client,
|
||
|
CNSS_BUS_WIDTH_NONE);
|
||
|
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 (msm_pcie_pm_control(MSM_PCIE_RESUME,
|
||
|
cnss_get_pci_dev_bus_number(pdev),
|
||
|
pdev, NULL, 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 = PCIE_LINK_UP;
|
||
|
}
|
||
|
|
||
|
if (penv->saved_state)
|
||
|
pci_load_and_free_saved_state(pdev, &penv->saved_state);
|
||
|
|
||
|
pci_restore_state(pdev);
|
||
|
pci_set_master(pdev);
|
||
|
|
||
|
atomic_set(&penv->auto_suspended, 0);
|
||
|
|
||
|
msm_bus_scale_client_update_request(penv->bus_client,
|
||
|
penv->current_bandwidth_vote);
|
||
|
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;
|
||
|
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);
|
||
|
module_init(cnss_initialize);
|
||
|
module_exit(cnss_exit);
|
||
|
|
||
|
MODULE_LICENSE("GPL v2");
|
||
|
MODULE_DESCRIPTION(DEVICE "CNSS Driver");
|