/* Copyright (c) 2013-2019, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cnss_common.h" #ifdef CONFIG_WCNSS_MEM_PRE_ALLOC #include #endif #define subsys_to_drv(d) container_of(d, struct cnss_data, subsys_desc) #define 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 QCA6180_VENDOR_ID (0x168C) #define QCA6180_DEVICE_ID (0x0041) #define QCA6180_REV_ID_OFFSET (0x08) #define WLAN_EN_VREG_NAME "vdd-wlan-en" #define WLAN_VREG_NAME "vdd-wlan" #define WLAN_VREG_IO_NAME "vdd-wlan-io" #define WLAN_VREG_XTAL_NAME "vdd-wlan-xtal" #define WLAN_VREG_XTAL_AON_NAME "vdd-wlan-xtal-aon" #define WLAN_VREG_CORE_NAME "vdd-wlan-core" #define WLAN_VREG_SP2T_NAME "vdd-wlan-sp2t" #define WLAN_SWREG_NAME "wlan-soc-swreg" #define WLAN_ANT_SWITCH_NAME "wlan-ant-switch" #define WLAN_EN_GPIO_NAME "wlan-en-gpio" #define WLAN_BOOTSTRAP_GPIO_NAME "wlan-bootstrap-gpio" #define PM_OPTIONS 0 #define PM_OPTIONS_SUSPEND_LINK_DOWN \ (MSM_PCIE_CONFIG_NO_CFG_RESTORE | MSM_PCIE_CONFIG_LINKDOWN) #define PM_OPTIONS_RESUME_LINK_DOWN \ (MSM_PCIE_CONFIG_NO_CFG_RESTORE) #define SOC_SWREG_VOLT_MAX 1200000 #define SOC_SWREG_VOLT_MIN 1200000 #define WLAN_ANT_SWITCH_VOLT_MAX 2700000 #define WLAN_ANT_SWITCH_VOLT_MIN 2700000 #define WLAN_ANT_SWITCH_CURR 20000 #define WLAN_VREG_IO_MAX 1800000 #define WLAN_VREG_IO_MIN 1800000 #define WLAN_VREG_XTAL_MAX 1800000 #define WLAN_VREG_XTAL_MIN 1800000 #define WLAN_VREG_CORE_MAX 1300000 #define WLAN_VREG_CORE_MIN 1300000 #define WLAN_VREG_SP2T_MAX 2700000 #define WLAN_VREG_SP2T_MIN 2700000 #define POWER_ON_DELAY 2 #define WLAN_VREG_IO_DELAY_MIN 100 #define WLAN_VREG_IO_DELAY_MAX 1000 #define WLAN_ENABLE_DELAY 10 #define 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_en_reg; struct regulator *wlan_reg; struct regulator *soc_swreg; struct regulator *ant_switch; struct regulator *wlan_reg_io; struct regulator *wlan_reg_xtal; struct regulator *wlan_reg_xtal_aon; struct regulator *wlan_reg_core; struct regulator *wlan_reg_sp2t; bool state; }; struct segment_memory { dma_addr_t dma_region; void *cpu_region; u32 size; }; /* FW image descriptor lists */ struct image_desc_hdr { u8 image_id; u8 reserved[3]; u32 segments_cnt; }; struct segment_desc { u8 segment_id; u8 segment_idx; u8 flags[2]; u32 addr_count; u32 addr_low; u32 addr_high; }; struct region_desc { u32 addr_low; u32 addr_high; u32 size; u32 reserved; }; struct index_file { u32 type; u32 segment_idx; u8 file_name[13]; }; struct cnss_dual_wifi { bool is_dual_wifi_enabled; }; /** * struct wlan_mac_addr - Structure to hold WLAN MAC Address * @mac_addr: MAC address */ #define MAX_NO_OF_MAC_ADDR 4 struct cnss_wlan_mac_addr { u8 mac_addr[MAX_NO_OF_MAC_ADDR][ETH_ALEN]; uint32_t no_of_mac_addr_set; }; /* device_info is expected to be fully populated after cnss_config is invoked. * The function pointer callbacks are expected to be non null as well. */ static struct cnss_data { struct platform_device *pldev; struct subsys_device *subsys; struct subsys_desc subsysdesc; struct cnss_wlan_mac_addr wlan_mac_addr; bool is_wlan_mac_set; bool ramdump_dynamic; struct ramdump_device *ramdump_dev; unsigned long ramdump_size; void *ramdump_addr; phys_addr_t ramdump_phys; struct msm_dump_data dump_data; struct cnss_wlan_driver *driver; struct pci_dev *pdev; const struct pci_device_id *id; struct dma_iommu_mapping *smmu_mapping; dma_addr_t smmu_iova_start; size_t smmu_iova_len; struct cnss_wlan_vreg_info vreg_info; bool wlan_en_vreg_support; struct cnss_wlan_gpio_info gpio_info; bool pcie_link_state; bool pcie_link_down_ind; bool pci_register_again; bool notify_modem_status; struct pci_saved_state *saved_state; u16 revision_id; bool recovery_in_progress; atomic_t fw_available; struct codeswap_codeseg_info *cnss_seg_info; /* Virtual Address of the DMA page */ void *codeseg_cpuaddr[CODESWAP_MAX_CODESEGS]; struct cnss_fw_files fw_files; struct pm_qos_request qos_request; void *modem_notify_handler; int modem_current_status; struct msm_bus_scale_pdata *bus_scale_table; 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; #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]; /* Firmware setup complete lock */ struct mutex fw_setup_stat_lock; void *bdata_cpu; dma_addr_t bdata_dma; u32 bdata_dma_size; u32 bdata_seg_count; struct segment_memory bdata_seg_mem[MAX_NUM_OF_SEGMENTS]; int wlan_bootstrap_gpio; atomic_t auto_suspended; bool monitor_wake_intr; struct cnss_dual_wifi dual_wifi_info; } *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 void cnss_put_wlan_enable_gpio(void) { struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info; struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; if (penv->wlan_en_vreg_support) regulator_put(vreg_info->wlan_en_reg); else gpio_free(gpio_info->num); } static int cnss_wlan_vreg_on(struct cnss_wlan_vreg_info *vreg_info) { int ret; if (vreg_info->wlan_reg_core) { ret = regulator_enable(vreg_info->wlan_reg_core); if (ret) { pr_err("%s: regulator enable failed for wlan_reg_core\n", __func__); goto error_enable_reg_core; } } if (vreg_info->wlan_reg_io) { ret = regulator_enable(vreg_info->wlan_reg_io); if (ret) { pr_err("%s: regulator enable failed for wlan_reg_io\n", __func__); goto error_enable_reg_io; } usleep_range(WLAN_VREG_IO_DELAY_MIN, WLAN_VREG_IO_DELAY_MAX); } if (vreg_info->wlan_reg_xtal_aon) { ret = regulator_enable(vreg_info->wlan_reg_xtal_aon); if (ret) { pr_err("%s: wlan_reg_xtal_aon enable failed\n", __func__); goto error_enable_reg_xtal_aon; } } if (vreg_info->wlan_reg_xtal) { ret = regulator_enable(vreg_info->wlan_reg_xtal); if (ret) { pr_err("%s: regulator enable failed for wlan_reg_xtal\n", __func__); goto error_enable_reg_xtal; } } ret = regulator_enable(vreg_info->wlan_reg); if (ret) { pr_err("%s: regulator enable failed for WLAN power\n", __func__); goto error_enable; } if (vreg_info->wlan_reg_sp2t) { ret = regulator_enable(vreg_info->wlan_reg_sp2t); if (ret) { pr_err("%s: regulator enable failed for wlan_reg_sp2t\n", __func__); goto error_enable_reg_sp2t; } } if (vreg_info->ant_switch) { ret = regulator_enable(vreg_info->ant_switch); if (ret) { pr_err("%s: regulator enable failed for ant_switch\n", __func__); goto error_enable_ant_switch; } } if (vreg_info->soc_swreg) { ret = regulator_enable(vreg_info->soc_swreg); if (ret) { pr_err("%s: regulator enable failed for external soc-swreg\n", __func__); goto error_enable_soc_swreg; } } return ret; error_enable_soc_swreg: if (vreg_info->ant_switch) regulator_disable(vreg_info->ant_switch); error_enable_ant_switch: if (vreg_info->wlan_reg_sp2t) regulator_disable(vreg_info->wlan_reg_sp2t); error_enable_reg_sp2t: regulator_disable(vreg_info->wlan_reg); error_enable: if (vreg_info->wlan_reg_xtal) regulator_disable(vreg_info->wlan_reg_xtal); error_enable_reg_xtal: if (vreg_info->wlan_reg_xtal_aon) regulator_disable(vreg_info->wlan_reg_xtal_aon); error_enable_reg_xtal_aon: if (vreg_info->wlan_reg_io) regulator_disable(vreg_info->wlan_reg_io); error_enable_reg_io: if (vreg_info->wlan_reg_core) regulator_disable(vreg_info->wlan_reg_core); error_enable_reg_core: return ret; } static int cnss_wlan_vreg_off(struct cnss_wlan_vreg_info *vreg_info) { int ret; if (vreg_info->soc_swreg) { ret = regulator_disable(vreg_info->soc_swreg); if (ret) { pr_err("%s: regulator disable failed for external soc-swreg\n", __func__); goto error_disable; } } if (vreg_info->ant_switch) { ret = regulator_disable(vreg_info->ant_switch); if (ret) { pr_err("%s: regulator disable failed for ant_switch\n", __func__); goto error_disable; } } if (vreg_info->wlan_reg_sp2t) { ret = regulator_disable(vreg_info->wlan_reg_sp2t); if (ret) { pr_err("%s: regulator disable failed for wlan_reg_sp2t\n", __func__); goto error_disable; } } ret = regulator_disable(vreg_info->wlan_reg); if (ret) { pr_err("%s: regulator disable failed for WLAN power\n", __func__); goto error_disable; } if (vreg_info->wlan_reg_xtal) { ret = regulator_disable(vreg_info->wlan_reg_xtal); if (ret) { pr_err("%s: regulator disable failed for wlan_reg_xtal\n", __func__); goto error_disable; } } if (vreg_info->wlan_reg_xtal_aon) { ret = regulator_disable(vreg_info->wlan_reg_xtal_aon); if (ret) { pr_err("%s: wlan_reg_xtal_aon disable failed\n", __func__); goto error_disable; } } if (vreg_info->wlan_reg_io) { ret = regulator_disable(vreg_info->wlan_reg_io); if (ret) { pr_err("%s: regulator disable failed for wlan_reg_io\n", __func__); goto error_disable; } } if (vreg_info->wlan_reg_core) { ret = regulator_disable(vreg_info->wlan_reg_core); if (ret) { pr_err("%s: regulator disable failed for wlan_reg_core\n", __func__); goto error_disable; } } error_disable: return ret; } static int cnss_wlan_vreg_set(struct cnss_wlan_vreg_info *vreg_info, bool state) { int ret = 0; if (vreg_info->state == state) { pr_debug("Already wlan vreg state is %s\n", state ? "enabled" : "disabled"); goto out; } if (state) ret = cnss_wlan_vreg_on(vreg_info); else ret = cnss_wlan_vreg_off(vreg_info); if (ret) goto out; pr_debug("%s: wlan vreg is now %s\n", __func__, state ? "enabled" : "disabled"); vreg_info->state = state; out: return ret; } static int cnss_wlan_gpio_init(struct cnss_wlan_gpio_info *info) { int ret = 0; ret = gpio_request(info->num, info->name); if (ret) { pr_err("can't get gpio %s ret %d\n", info->name, ret); goto err_gpio_req; } ret = gpio_direction_output(info->num, info->init); if (ret) { pr_err("can't set gpio direction %s ret %d\n", info->name, ret); goto err_gpio_dir; } info->state = info->init; return ret; err_gpio_dir: gpio_free(info->num); err_gpio_req: return ret; } static int cnss_wlan_bootstrap_gpio_init(void) { int ret = 0; ret = gpio_request(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_GPIO_NAME); if (ret) { pr_err("%s: Can't get GPIO %s, ret = %d\n", __func__, WLAN_BOOTSTRAP_GPIO_NAME, ret); goto out; } ret = gpio_direction_output(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_HIGH); if (ret) { pr_err("%s: Can't set GPIO %s direction, ret = %d\n", __func__, WLAN_BOOTSTRAP_GPIO_NAME, ret); gpio_free(penv->wlan_bootstrap_gpio); goto out; } msleep(WLAN_BOOTSTRAP_DELAY); out: return ret; } static void cnss_wlan_gpio_set(struct cnss_wlan_gpio_info *info, bool state) { #ifndef CONFIG_MSM_GVM_QUIN if (!info->prop) return; if (info->state == state) { pr_debug("Already %s gpio is %s\n", info->name, state ? "high" : "low"); return; } if (state == WLAN_EN_LOW && penv->dual_wifi_info.is_dual_wifi_enabled) { pr_debug("%s Dual WiFi enabled\n", __func__); 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"); #else return; #endif } static int cnss_configure_wlan_en_gpio(bool state) { int ret = 0; struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info; struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; if (penv->wlan_en_vreg_support) { if (state) ret = regulator_enable(vreg_info->wlan_en_reg); else ret = regulator_disable(vreg_info->wlan_en_reg); } else { cnss_wlan_gpio_set(gpio_info, state); } msleep(WLAN_ENABLE_DELAY); return ret; } static 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 void cnss_disable_xtal_ldo(struct platform_device *pdev) { struct cnss_wlan_vreg_info *info = &penv->vreg_info; if (info->wlan_reg_xtal) { regulator_disable(info->wlan_reg_xtal); regulator_put(info->wlan_reg_xtal); } if (info->wlan_reg_xtal_aon) { regulator_disable(info->wlan_reg_xtal_aon); regulator_put(info->wlan_reg_xtal_aon); } } static int cnss_enable_xtal_ldo(struct platform_device *pdev) { int ret = 0; struct cnss_wlan_vreg_info *info = &penv->vreg_info; if (!of_get_property(pdev->dev.of_node, WLAN_VREG_XTAL_AON_NAME "-supply", NULL)) goto enable_xtal; info->wlan_reg_xtal_aon = regulator_get(&pdev->dev, WLAN_VREG_XTAL_AON_NAME); if (IS_ERR(info->wlan_reg_xtal_aon)) { ret = PTR_ERR(info->wlan_reg_xtal_aon); pr_err("%s: XTAL AON Regulator get failed err:%d\n", __func__, ret); return ret; } ret = regulator_enable(info->wlan_reg_xtal_aon); if (ret) { pr_err("%s: VREG_XTAL_ON enable failed\n", __func__); goto end; } enable_xtal: if (!of_get_property(pdev->dev.of_node, WLAN_VREG_XTAL_NAME "-supply", NULL)) goto out_disable_xtal_aon; info->wlan_reg_xtal = regulator_get(&pdev->dev, WLAN_VREG_XTAL_NAME); if (IS_ERR(info->wlan_reg_xtal)) { ret = PTR_ERR(info->wlan_reg_xtal); pr_err("%s XTAL Regulator get failed err:%d\n", __func__, ret); goto out_disable_xtal_aon; } ret = regulator_set_voltage(info->wlan_reg_xtal, WLAN_VREG_XTAL_MIN, WLAN_VREG_XTAL_MAX); if (ret) { pr_err("%s: Set wlan_vreg_xtal failed!\n", __func__); goto out_put_xtal; } ret = regulator_enable(info->wlan_reg_xtal); if (ret) { pr_err("%s: Enable wlan_vreg_xtal failed!\n", __func__); goto out_put_xtal; } return 0; out_put_xtal: if (info->wlan_reg_xtal) regulator_put(info->wlan_reg_xtal); out_disable_xtal_aon: if (info->wlan_reg_xtal_aon) regulator_disable(info->wlan_reg_xtal_aon); end: if (info->wlan_reg_xtal_aon) regulator_put(info->wlan_reg_xtal_aon); return ret; } static int cnss_get_wlan_enable_gpio( struct cnss_wlan_gpio_info *gpio_info, struct platform_device *pdev) { int ret = 0; struct device *dev = &pdev->dev; if (!of_find_property(dev->of_node, gpio_info->name, NULL)) { gpio_info->prop = false; return -ENODEV; } gpio_info->prop = true; ret = of_get_named_gpio(dev->of_node, gpio_info->name, 0); if (ret >= 0) { gpio_info->num = ret; } else { if (ret == -EPROBE_DEFER) pr_debug("get WLAN_EN GPIO probe defer\n"); else pr_err( "can't get gpio %s ret %d", gpio_info->name, ret); } ret = cnss_pinctrl_init(gpio_info, pdev); if (ret) pr_debug("%s: pinctrl init failed!\n", __func__); ret = cnss_wlan_gpio_init(gpio_info); if (ret) pr_err("gpio init failed\n"); return ret; } static int cnss_get_wlan_bootstrap_gpio(struct platform_device *pdev) { int ret = 0; struct device_node *node = (&pdev->dev)->of_node; if (!of_find_property(node, WLAN_BOOTSTRAP_GPIO_NAME, NULL)) return ret; penv->wlan_bootstrap_gpio = of_get_named_gpio(node, WLAN_BOOTSTRAP_GPIO_NAME, 0); if (penv->wlan_bootstrap_gpio > 0) { ret = cnss_wlan_bootstrap_gpio_init(); } else { ret = penv->wlan_bootstrap_gpio; pr_err( "%s: Can't get GPIO %s, ret = %d", __func__, WLAN_BOOTSTRAP_GPIO_NAME, ret); } return ret; } static int cnss_wlan_get_resources(struct platform_device *pdev) { int ret = 0; struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info; struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; struct device_node *node = pdev->dev.of_node; if (of_get_property(node, WLAN_VREG_CORE_NAME "-supply", NULL)) { vreg_info->wlan_reg_core = regulator_get(&pdev->dev, WLAN_VREG_CORE_NAME); if (IS_ERR(vreg_info->wlan_reg_core)) { ret = PTR_ERR(vreg_info->wlan_reg_core); if (ret == -EPROBE_DEFER) { pr_err("%s: wlan_reg_core probe deferred!\n", __func__); } else { pr_err("%s: Get wlan_reg_core failed!\n", __func__); } goto err_reg_core_get; } ret = regulator_set_voltage(vreg_info->wlan_reg_core, WLAN_VREG_CORE_MIN, WLAN_VREG_CORE_MAX); if (ret) { pr_err("%s: Set wlan_reg_core failed!\n", __func__); goto err_reg_core_set; } ret = regulator_enable(vreg_info->wlan_reg_core); if (ret) { pr_err("%s: Enable wlan_reg_core failed!\n", __func__); goto err_reg_core_enable; } } if (of_get_property(node, WLAN_VREG_IO_NAME "-supply", NULL)) { vreg_info->wlan_reg_io = regulator_get(&pdev->dev, WLAN_VREG_IO_NAME); if (!IS_ERR(vreg_info->wlan_reg_io)) { ret = regulator_set_voltage(vreg_info->wlan_reg_io, WLAN_VREG_IO_MIN, WLAN_VREG_IO_MAX); if (ret) { pr_err("%s: Set wlan_vreg_io failed!\n", __func__); goto err_reg_io_set; } ret = regulator_enable(vreg_info->wlan_reg_io); if (ret) { pr_err("%s: Enable wlan_vreg_io failed!\n", __func__); goto err_reg_io_enable; } usleep_range(WLAN_VREG_IO_DELAY_MIN, WLAN_VREG_IO_DELAY_MAX); } } if (cnss_enable_xtal_ldo(pdev)) goto err_reg_xtal_enable; vreg_info->wlan_reg = regulator_get(&pdev->dev, WLAN_VREG_NAME); if (IS_ERR(vreg_info->wlan_reg)) { if (PTR_ERR(vreg_info->wlan_reg) == -EPROBE_DEFER) pr_err("%s: vreg probe defer\n", __func__); else pr_err("%s: vreg regulator get failed\n", __func__); ret = PTR_ERR(vreg_info->wlan_reg); goto err_reg_get; } ret = regulator_enable(vreg_info->wlan_reg); if (ret) { pr_err("%s: vreg initial vote failed\n", __func__); goto err_reg_enable; } if (of_get_property(node, WLAN_VREG_SP2T_NAME "-supply", NULL)) { vreg_info->wlan_reg_sp2t = regulator_get(&pdev->dev, WLAN_VREG_SP2T_NAME); if (!IS_ERR(vreg_info->wlan_reg_sp2t)) { ret = regulator_set_voltage(vreg_info->wlan_reg_sp2t, WLAN_VREG_SP2T_MIN, WLAN_VREG_SP2T_MAX); if (ret) { pr_err("%s: Set wlan_vreg_sp2t failed!\n", __func__); goto err_reg_sp2t_set; } ret = regulator_enable(vreg_info->wlan_reg_sp2t); if (ret) { pr_err("%s: Enable wlan_vreg_sp2t failed!\n", __func__); goto err_reg_sp2t_enable; } } } if (of_get_property(node, WLAN_ANT_SWITCH_NAME "-supply", NULL)) { vreg_info->ant_switch = regulator_get(&pdev->dev, WLAN_ANT_SWITCH_NAME); if (!IS_ERR(vreg_info->ant_switch)) { ret = regulator_set_voltage(vreg_info->ant_switch, WLAN_ANT_SWITCH_VOLT_MIN, WLAN_ANT_SWITCH_VOLT_MAX); if (ret < 0) { pr_err("%s: Set ant_switch voltage failed!\n", __func__); goto err_ant_switch_set; } ret = regulator_set_load(vreg_info->ant_switch, WLAN_ANT_SWITCH_CURR); if (ret < 0) { pr_err("%s: Set ant_switch current failed!\n", __func__); goto err_ant_switch_set; } ret = regulator_enable(vreg_info->ant_switch); if (ret < 0) { pr_err("%s: Enable ant_switch failed!\n", __func__); goto err_ant_switch_enable; } } } if (of_find_property(node, "qcom,wlan-uart-access", NULL)) penv->cap.cap_flag |= CNSS_HAS_UART_ACCESS; if (of_get_property(node, WLAN_SWREG_NAME "-supply", NULL)) { vreg_info->soc_swreg = regulator_get(&pdev->dev, WLAN_SWREG_NAME); if (IS_ERR(vreg_info->soc_swreg)) { pr_err("%s: soc-swreg node not found\n", __func__); goto err_reg_get2; } ret = regulator_set_voltage(vreg_info->soc_swreg, SOC_SWREG_VOLT_MIN, SOC_SWREG_VOLT_MAX); if (ret) { pr_err("%s: vreg initial voltage set failed on soc-swreg\n", __func__); goto err_reg_set; } ret = regulator_enable(vreg_info->soc_swreg); if (ret) { pr_err("%s: vreg initial vote failed\n", __func__); goto err_reg_enable2; } penv->cap.cap_flag |= CNSS_HAS_EXTERNAL_SWREG; } penv->wlan_en_vreg_support = of_property_read_bool(node, "qcom,wlan-en-vreg-support"); if (penv->wlan_en_vreg_support) { vreg_info->wlan_en_reg = regulator_get(&pdev->dev, WLAN_EN_VREG_NAME); if (IS_ERR(vreg_info->wlan_en_reg)) { pr_err("%s:wlan_en vreg get failed\n", __func__); ret = PTR_ERR(vreg_info->wlan_en_reg); goto err_wlan_en_reg_get; } } if (!penv->wlan_en_vreg_support) { ret = cnss_get_wlan_enable_gpio(gpio_info, pdev); if (ret) { pr_err( "%s:Failed to config the WLAN_EN gpio\n", __func__); goto err_gpio_wlan_en; } } vreg_info->state = VREG_ON; ret = cnss_get_wlan_bootstrap_gpio(pdev); if (ret) { pr_err("%s: Failed to enable wlan bootstrap gpio\n", __func__); goto err_gpio_wlan_bootstrap; } return ret; err_gpio_wlan_bootstrap: cnss_put_wlan_enable_gpio(); err_gpio_wlan_en: err_wlan_en_reg_get: vreg_info->wlan_en_reg = NULL; if (vreg_info->soc_swreg) regulator_disable(vreg_info->soc_swreg); vreg_info->state = 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); regulator_disable(vreg_info->wlan_reg); err_reg_enable: regulator_put(vreg_info->wlan_reg); err_reg_get: cnss_disable_xtal_ldo(pdev); err_reg_xtal_enable: if (vreg_info->wlan_reg_io) regulator_disable(vreg_info->wlan_reg_io); err_reg_io_enable: err_reg_io_set: if (vreg_info->wlan_reg_io) regulator_put(vreg_info->wlan_reg_io); if (vreg_info->wlan_reg_core) regulator_disable(vreg_info->wlan_reg_core); err_reg_core_enable: err_reg_core_set: if (vreg_info->wlan_reg_core) regulator_put(vreg_info->wlan_reg_core); err_reg_core_get: return ret; } static void cnss_wlan_release_resources(void) { struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info; struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; if (penv->wlan_bootstrap_gpio > 0) gpio_free(penv->wlan_bootstrap_gpio); cnss_put_wlan_enable_gpio(); gpio_info->state = 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); regulator_put(vreg_info->wlan_reg); if (vreg_info->wlan_reg_xtal) regulator_put(vreg_info->wlan_reg_xtal); if (vreg_info->wlan_reg_xtal_aon) regulator_put(vreg_info->wlan_reg_xtal_aon); if (vreg_info->wlan_reg_io) regulator_put(vreg_info->wlan_reg_io); if (vreg_info->wlan_reg_core) regulator_put(vreg_info->wlan_reg_core); vreg_info->state = 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); #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; mutex_lock(&penv->fw_setup_stat_lock); image_desc_info->fw_addr = penv->fw_dma; image_desc_info->fw_size = penv->fw_dma_size; image_desc_info->bdata_addr = penv->bdata_dma; image_desc_info->bdata_size = penv->bdata_dma_size; mutex_unlock(&penv->fw_setup_stat_lock); return 0; } EXPORT_SYMBOL(cnss_get_fw_image); static ssize_t wlan_setup_show(struct device *dev, struct device_attribute *attr, char *buf) { if (!penv) return -ENODEV; return scnprintf(buf, PAGE_SIZE, "%u\n", penv->revision_id); } static DEVICE_ATTR(wlan_setup, 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 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)) { ret = PTR_ERR(mapping); pr_err("%s: create mapping failed, err = %d\n", __func__, ret); goto map_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; } #ifdef CONFIG_PCI_MSM struct pci_saved_state *cnss_pci_store_saved_state(struct pci_dev *dev) { return pci_store_saved_state(dev); } int cnss_msm_pcie_pm_control( enum msm_pcie_pm_opt pm_opt, u32 bus_num, struct pci_dev *pdev, u32 options) { return msm_pcie_pm_control(pm_opt, bus_num, pdev, NULL, options); } #ifndef CONFIG_GHS_VMM static int cnss_msm_pcie_suspend_resume( enum msm_pcie_pm_opt pm_opt, u32 bus_num, struct pci_dev *pdev, u32 options) { return msm_pcie_pm_control(pm_opt, bus_num, pdev, NULL, options); } #else static inline int cnss_msm_pcie_suspend_resume( enum msm_pcie_pm_opt pm_opt, u32 bus_num, struct pci_dev *pdev, u32 options) { return 0; } #endif int cnss_pci_load_and_free_saved_state( struct pci_dev *dev, struct pci_saved_state **state) { return pci_load_and_free_saved_state(dev, state); } int cnss_msm_pcie_shadow_control(struct pci_dev *dev, bool enable) { return msm_pcie_shadow_control(dev, enable); } int cnss_msm_pcie_deregister_event(struct msm_pcie_register_event *reg) { return msm_pcie_deregister_event(reg); } int cnss_msm_pcie_recover_config(struct pci_dev *dev) { return msm_pcie_recover_config(dev); } int cnss_msm_pcie_register_event(struct msm_pcie_register_event *reg) { return msm_pcie_register_event(reg); } int cnss_msm_pcie_enumerate(u32 rc_idx) { return msm_pcie_enumerate(rc_idx); } #else /* !defined CONFIG_PCI_MSM */ struct pci_saved_state *cnss_pci_store_saved_state(struct pci_dev *dev) { return NULL; } int cnss_msm_pcie_pm_control( enum msm_pcie_pm_opt pm_opt, u32 bus_num, struct pci_dev *pdev, u32 options) { return 0; } int cnss_pci_load_and_free_saved_state( struct pci_dev *dev, struct pci_saved_state **state) { return 0; } int cnss_msm_pcie_shadow_control(struct pci_dev *dev, bool enable) { return 0; } int cnss_msm_pcie_deregister_event(struct msm_pcie_register_event *reg) { return 0; } int cnss_msm_pcie_recover_config(struct pci_dev *dev) { return 0; } int cnss_msm_pcie_register_event(struct msm_pcie_register_event *reg) { return 0; } int cnss_msm_pcie_enumerate(u32 rc_idx) { return 0; } #endif static int cnss_wlan_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int ret = 0; struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; void *cpu_addr; dma_addr_t dma_handle; struct codeswap_codeseg_info *cnss_seg_info = NULL; struct device *dev = &pdev->dev; penv->pdev = pdev; penv->id = id; atomic_set(&penv->fw_available, 0); penv->device_id = pdev->device; if (penv->smmu_iova_len) { ret = cnss_smmu_init(&pdev->dev); if (ret) { pr_err("%s: SMMU init failed, err = %d\n", __func__, ret); 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 = cnss_pci_store_saved_state(pdev); ret = cnss_msm_pcie_suspend_resume( MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), pdev, PM_OPTIONS); if (ret) { pr_err("Failed to shutdown PCIe link\n"); goto err_pcie_suspend; } penv->pcie_link_state = PCIE_LINK_DOWN; } cnss_configure_wlan_en_gpio(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; } mutex_lock(&penv->fw_setup_stat_lock); cnss_wlan_fw_mem_alloc(pdev); mutex_unlock(&penv->fw_setup_stat_lock); ret = device_create_file(&penv->pldev->dev, &dev_attr_wlan_setup); if (ret) { pr_err("Can't Create Device file\n"); goto err_pcie_suspend; } if (cnss_wlan_is_codeswap_supported(penv->revision_id)) { pr_debug("Code-swap not enabled: %d\n", penv->revision_id); goto err_pcie_suspend; } cpu_addr = dma_alloc_coherent(dev, EVICT_BIN_MAX_SIZE, &dma_handle, GFP_KERNEL); if (!cpu_addr || !dma_handle) { 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 = cnss_pci_store_saved_state(pdev); } } penv->monitor_wake_intr = false; out: return ret; } static int cnss_wlan_pci_resume(struct device *dev) { int ret = 0; struct cnss_wlan_driver *wdriver; struct pci_dev *pdev = to_pci_dev(dev); if (!penv) goto out; wdriver = penv->driver; if (!wdriver) goto out; if (wdriver->resume && !penv->pcie_link_down_ind) { if (penv->saved_state) cnss_pci_load_and_free_saved_state( pdev, &penv->saved_state); pci_restore_state(pdev); ret = wdriver->resume(pdev); } out: return ret; } static int cnss_wlan_runtime_suspend(struct device *dev) { int ret = 0; struct cnss_wlan_driver *wdrv; if (!penv) return -EAGAIN; if (penv->pcie_link_down_ind) { pr_debug("PCI link down recovery is in progress!\n"); return -EAGAIN; } pr_debug("cnss: runtime suspend start\n"); wdrv = penv->driver; if (wdrv && wdrv->runtime_ops && wdrv->runtime_ops->runtime_suspend) ret = wdrv->runtime_ops->runtime_suspend(to_pci_dev(dev)); pr_debug("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_debug("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; mutex_lock(&penv->fw_setup_stat_lock); pr_info("%s: Firmware setup in progress\n", __func__); if (kstrtoint(buf, 0, &val)) { mutex_unlock(&penv->fw_setup_stat_lock); return -EINVAL; } if (val == FW_IMAGE_FTM || val == FW_IMAGE_MISSION || val == FW_IMAGE_BDATA) { pr_info("%s: fw image setup triggered %d\n", __func__, val); ret = cnss_setup_fw_image_table(val); if (ret != 0) { pr_err("%s: Invalid parsing of FW image files %d", __func__, ret); mutex_unlock(&penv->fw_setup_stat_lock); return -EINVAL; } penv->fw_image_setup = val; } else if (val == FW_IMAGE_PRINT) { print_allocated_image_table(); } else if (val == BMI_TEST_SETUP) { penv->bmi_test = val; } pr_info("%s: Firmware setup completed\n", __func__); mutex_unlock(&penv->fw_setup_stat_lock); return count; } static DEVICE_ATTR(fw_image_setup, S_IRUSR | S_IWUSR, fw_image_setup_show, fw_image_setup_store); void cnss_pci_recovery_work_handler(struct work_struct *recovery) { cnss_pci_device_self_recovery(); } DECLARE_WORK(cnss_pci_recovery_work, cnss_pci_recovery_work_handler); void cnss_schedule_recovery_work(void) { schedule_work(&cnss_pci_recovery_work); } EXPORT_SYMBOL(cnss_schedule_recovery_work); static inline void __cnss_disable_irq(void *data) { struct pci_dev *pdev = data; disable_irq_nosync(pdev->irq); } 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"); __cnss_disable_irq(notify->user); schedule_work(&cnss_pci_recovery_work); break; case MSM_PCIE_EVENT_WAKEUP: if (penv->monitor_wake_intr && atomic_read(&penv->auto_suspended)) { penv->monitor_wake_intr = false; pm_request_resume(&penv->pdev->dev); } break; default: pr_err("cnss: invalid event from PCIe callback %d\n", notify->event); } } void cnss_wlan_pci_link_down(void) { unsigned long flags; if (pcie_link_down_panic) panic("PCIe link is down!\n"); spin_lock_irqsave(&pci_link_down_lock, flags); if (penv->pcie_link_down_ind) { pr_debug("PCI link down recovery is in progress, ignore!\n"); spin_unlock_irqrestore(&pci_link_down_lock, flags); return; } penv->pcie_link_down_ind = true; spin_unlock_irqrestore(&pci_link_down_lock, flags); pr_err("PCI link down detected by host driver, schedule recovery!\n"); schedule_work(&cnss_pci_recovery_work); } EXPORT_SYMBOL(cnss_wlan_pci_link_down); int cnss_pcie_shadow_control(struct pci_dev *dev, bool enable) { return cnss_msm_pcie_shadow_control(dev, enable); } EXPORT_SYMBOL(cnss_pcie_shadow_control); int cnss_get_codeswap_struct(struct codeswap_codeseg_info *swap_seg) { struct codeswap_codeseg_info *cnss_seg_info = penv->cnss_seg_info; mutex_lock(&penv->fw_setup_stat_lock); if (!cnss_seg_info) { swap_seg = NULL; mutex_unlock(&penv->fw_setup_stat_lock); return -ENOENT; } if (!atomic_read(&penv->fw_available)) { pr_debug("%s: fw is not available\n", __func__); mutex_unlock(&penv->fw_setup_stat_lock); return -ENOENT; } *swap_seg = *cnss_seg_info; mutex_unlock(&penv->fw_setup_stat_lock); return 0; } EXPORT_SYMBOL(cnss_get_codeswap_struct); static void cnss_wlan_memory_expansion(void) { struct device *dev; const struct firmware *fw_entry; const char *filename; 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; mutex_lock(&penv->fw_setup_stat_lock); filename = cnss_wlan_get_evicted_data_file(); pdev = penv->pdev; dev = &pdev->dev; cnss_seg_info = penv->cnss_seg_info; if (!cnss_seg_info) { pr_debug("cnss: cnss_seg_info is NULL\n"); mutex_unlock(&penv->fw_setup_stat_lock); goto end; } if (atomic_read(&penv->fw_available)) { pr_debug("cnss: fw code already copied to host memory\n"); mutex_unlock(&penv->fw_setup_stat_lock); goto end; } if (request_firmware(&fw_entry, filename, dev) != 0) { pr_debug("cnss: failed to get fw: %s\n", filename); mutex_unlock(&penv->fw_setup_stat_lock); goto end; } if (!fw_entry || !fw_entry->data) { pr_err("%s: INVALID FW entries\n", __func__); mutex_unlock(&penv->fw_setup_stat_lock); goto release_fw; } dma_virt_addr = (char *)penv->codeseg_cpuaddr[0]; fw_data = (u8 *) fw_entry->data; fw_temp = fw_data; fw_entry_size = fw_entry->size; if (fw_entry_size > EVICT_BIN_MAX_SIZE) fw_entry_size = EVICT_BIN_MAX_SIZE; size_left = fw_entry_size; dma_size_left = EVICT_BIN_MAX_SIZE; while ((size_left && fw_temp) && (dma_size_left > 0)) { fw_temp = fw_temp + 4; size_left = size_left - 4; length = *(int *)fw_temp; if ((length > size_left || length <= 0) || (dma_size_left <= 0 || length > dma_size_left)) { pr_err("cnss: wrong length read:%d\n", length); break; } fw_temp = fw_temp + 4; size_left = size_left - 4; memcpy(dma_virt_addr, fw_temp, length); dma_size_left = dma_size_left - length; size_left = size_left - length; fw_temp = fw_temp + length; dma_virt_addr = dma_virt_addr + length; total_length += length; pr_debug("cnss: bytes_left to copy: fw:%d; dma_page:%d\n", size_left, dma_size_left); } pr_debug("cnss: total_bytes copied: %d\n", total_length); cnss_seg_info->codeseg_total_bytes = total_length; atomic_set(&penv->fw_available, 1); mutex_unlock(&penv->fw_setup_stat_lock); release_fw: release_firmware(fw_entry); end: return; } /** * cnss_get_wlan_mac_address() - API to return MAC addresses buffer * @dev: struct device pointer * @num: buffer for number of mac addresses supported * * API returns the pointer to the buffer filled with mac addresses and * updates num with the number of mac addresses the buffer contains. * * Return: pointer to mac address buffer. */ u8 *cnss_pci_get_wlan_mac_address(uint32_t *num) { struct cnss_wlan_mac_addr *addr = NULL; if (!penv) { pr_err("%s: Invalid Platform Driver Context\n", __func__); goto end; } if (!penv->is_wlan_mac_set) { pr_info("%s: Platform Driver doesn't have any mac address\n", __func__); goto end; } addr = &penv->wlan_mac_addr; *num = addr->no_of_mac_addr_set; return &addr->mac_addr[0][0]; end: *num = 0; return NULL; } /** * cnss_get_wlan_mac_address() - API to return MAC addresses buffer * @dev: struct device pointer * @num: buffer for number of mac addresses supported * * API returns the pointer to the buffer filled with mac addresses and * updates num with the number of mac addresses the buffer contains. * * Return: pointer to mac address buffer. */ u8 *cnss_get_wlan_mac_address(struct device *dev, uint32_t *num) { struct cnss_wlan_mac_addr *addr = NULL; if (!penv) { pr_err("%s: Invalid Platform Driver Context\n", __func__); goto end; } if (!penv->is_wlan_mac_set) { pr_info("%s: Platform Driver doesn't have any mac address\n", __func__); goto end; } addr = &penv->wlan_mac_addr; *num = addr->no_of_mac_addr_set; return &addr->mac_addr[0][0]; end: *num = 0; return NULL; } EXPORT_SYMBOL(cnss_get_wlan_mac_address); /** * cnss_pcie_set_wlan_mac_address() - API to get two wlan mac address * @in: Input buffer with wlan mac addresses * @len: Size of the buffer passed * * API to store wlan mac address passed by the caller. The stored mac * addresses are used by the wlan functional driver to program wlan HW. * * Return: kernel error code. */ int cnss_pcie_set_wlan_mac_address(const u8 *in, uint32_t len) { uint32_t no_of_mac_addr; struct cnss_wlan_mac_addr *addr = NULL; int iter = 0; u8 *temp = NULL; if (len == 0 || (len % ETH_ALEN) != 0) { pr_err("%s: Invalid Length:%d\n", __func__, len); return -EINVAL; } no_of_mac_addr = len / ETH_ALEN; if (no_of_mac_addr > MAX_NO_OF_MAC_ADDR) { pr_err("%s: Num of supported MAC addresses are:%d given:%d\n", __func__, MAX_NO_OF_MAC_ADDR, no_of_mac_addr); return -EINVAL; } if (!penv) { pr_err("%s: Invalid CNSS Platform Context\n", __func__); return -ENOENT; } if (penv->is_wlan_mac_set) { pr_info("%s: Already MAC address are configured\n", __func__); return 0; } penv->is_wlan_mac_set = true; addr = &penv->wlan_mac_addr; addr->no_of_mac_addr_set = no_of_mac_addr; temp = &addr->mac_addr[0][0]; for (; iter < no_of_mac_addr; ++iter, temp += ETH_ALEN, in += ETH_ALEN) { ether_addr_copy(temp, in); pr_debug("%s MAC_ADDR:%02x:%02x:%02x:%02x:%02x:%02x\n", __func__, temp[0], temp[1], temp[2], temp[3], temp[4], temp[5]); } return 0; } int cnss_wlan_register_driver(struct cnss_wlan_driver *driver) { int ret = 0; int probe_again = 0; struct cnss_wlan_driver *wdrv; struct cnss_wlan_vreg_info *vreg_info; struct cnss_wlan_gpio_info *gpio_info; struct pci_dev *pdev; if (!penv) return -ENODEV; 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_configure_wlan_en_gpio(WLAN_EN_HIGH); if (!pdev) { pr_debug("%s: invalid pdev. register pci device\n", __func__); ret = pci_register_driver(&cnss_wlan_pci_driver); if (ret) { pr_err("%s: pci registration failed\n", __func__); goto err_pcie_reg; } pdev = penv->pdev; if (!pdev) { pr_err("%s: pdev is still invalid\n", __func__); goto err_pcie_reg; } } penv->event_reg.events = MSM_PCIE_EVENT_LINKDOWN | MSM_PCIE_EVENT_WAKEUP; penv->event_reg.user = pdev; penv->event_reg.mode = MSM_PCIE_TRIGGER_CALLBACK; penv->event_reg.callback = cnss_pci_events_cb; penv->event_reg.options = MSM_PCIE_CONFIG_NO_RECOVERY; ret = cnss_msm_pcie_register_event(&penv->event_reg); if (ret) pr_err("%s: PCIe event register failed! %d\n", __func__, ret); if (!penv->pcie_link_state && !penv->pcie_link_down_ind) { ret = cnss_msm_pcie_suspend_resume( MSM_PCIE_RESUME, cnss_get_pci_dev_bus_number(pdev), pdev, PM_OPTIONS); if (ret) { pr_err("PCIe link bring-up failed\n"); goto err_pcie_link_up; } penv->pcie_link_state = PCIE_LINK_UP; } else if (!penv->pcie_link_state && penv->pcie_link_down_ind) { ret = cnss_msm_pcie_suspend_resume( MSM_PCIE_RESUME, cnss_get_pci_dev_bus_number(pdev), pdev, PM_OPTIONS_RESUME_LINK_DOWN); if (ret) { pr_err("PCIe link bring-up failed (link down option)\n"); goto err_pcie_link_up; } penv->pcie_link_state = PCIE_LINK_UP; ret = cnss_msm_pcie_recover_config(pdev); if (ret) { pr_err("cnss: PCI link failed to recover\n"); goto err_pcie_link_up; } penv->pcie_link_down_ind = false; } if (!cnss_wlan_is_codeswap_supported(penv->revision_id)) cnss_wlan_memory_expansion(); if (wdrv->probe) { if (penv->saved_state) cnss_pci_load_and_free_saved_state( pdev, &penv->saved_state); pci_restore_state(pdev); ret = wdrv->probe(pdev, penv->id); if (ret) { wcnss_prealloc_check_memory_leak(); wcnss_pre_alloc_reset(); if (probe_again > 3) { pr_err("Failed to probe WLAN\n"); goto err_wlan_probe; } pci_save_state(pdev); penv->saved_state = cnss_pci_store_saved_state(pdev); cnss_msm_pcie_deregister_event(&penv->event_reg); cnss_msm_pcie_suspend_resume( MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), pdev, PM_OPTIONS); penv->pcie_link_state = PCIE_LINK_DOWN; cnss_configure_wlan_en_gpio(WLAN_EN_LOW); 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 = cnss_pci_store_saved_state(pdev); err_pcie_link_up: cnss_msm_pcie_deregister_event(&penv->event_reg); if (penv->pcie_link_state) { cnss_msm_pcie_suspend_resume( MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), pdev, PM_OPTIONS); penv->pcie_link_state = PCIE_LINK_DOWN; } err_pcie_reg: cnss_configure_wlan_en_gpio(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(); cnss_msm_pcie_deregister_event(&penv->event_reg); if (penv->pcie_link_state && !penv->pcie_link_down_ind) { pci_save_state(pdev); penv->saved_state = cnss_pci_store_saved_state(pdev); if (cnss_msm_pcie_suspend_resume( MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), pdev, PM_OPTIONS)) { pr_err("Failed to shutdown PCIe link\n"); return; } } else if (penv->pcie_link_state && penv->pcie_link_down_ind) { penv->saved_state = NULL; if (cnss_msm_pcie_suspend_resume( MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), pdev, PM_OPTIONS_SUSPEND_LINK_DOWN)) { pr_err("Failed to shutdown PCIe link (with linkdown option)\n"); return; } } penv->pcie_link_state = 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_configure_wlan_en_gpio(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); #ifdef CONFIG_PCI_MSM int cnss_wlan_pm_control(bool vote) { if (!penv || !penv->pdev) return -ENODEV; return cnss_msm_pcie_pm_control( vote ? MSM_PCIE_DISABLE_PC : MSM_PCIE_ENABLE_PC, cnss_get_pci_dev_bus_number(penv->pdev), penv->pdev, PM_OPTIONS); } EXPORT_SYMBOL(cnss_wlan_pm_control); #endif void cnss_lock_pm_sem(void) { down_read(&cnss_pm_sem); } EXPORT_SYMBOL(cnss_lock_pm_sem); void cnss_release_pm_sem(void) { up_read(&cnss_pm_sem); } EXPORT_SYMBOL(cnss_release_pm_sem); void cnss_pci_schedule_recovery_work(void) { schedule_work(&cnss_pci_recovery_work); } void *cnss_pci_get_virt_ramdump_mem(unsigned long *size) { if (!penv || !penv->pldev) return NULL; *size = penv->ramdump_size; return penv->ramdump_addr; } void cnss_pci_device_crashed(void) { if (penv && penv->subsys) { subsys_set_crash_status(penv->subsys, CRASH_STATUS_ERR_FATAL); subsystem_restart_dev(penv->subsys); } } void *cnss_get_virt_ramdump_mem(unsigned long *size) { if (!penv || !penv->pldev) return NULL; *size = penv->ramdump_size; return penv->ramdump_addr; } EXPORT_SYMBOL(cnss_get_virt_ramdump_mem); void cnss_device_crashed(void) { if (penv && penv->subsys) { subsys_set_crash_status(penv->subsys, CRASH_STATUS_ERR_FATAL); subsystem_restart_dev(penv->subsys); } } EXPORT_SYMBOL(cnss_device_crashed); static int cnss_shutdown(const struct subsys_desc *subsys, bool force_stop) { struct cnss_wlan_driver *wdrv; struct pci_dev *pdev; struct cnss_wlan_vreg_info *vreg_info; struct cnss_wlan_gpio_info *gpio_info; int ret = 0; if (!penv) return -ENODEV; penv->recovery_in_progress = true; wdrv = penv->driver; pdev = penv->pdev; vreg_info = &penv->vreg_info; gpio_info = &penv->gpio_info; if (!pdev) { ret = -EINVAL; goto cut_power; } if (wdrv && wdrv->shutdown) wdrv->shutdown(pdev); if (penv->pcie_link_state) { if (cnss_msm_pcie_pm_control( MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), pdev, PM_OPTIONS_SUSPEND_LINK_DOWN)) { pr_debug("cnss: Failed to shutdown PCIe link!\n"); ret = -EFAULT; } penv->saved_state = NULL; penv->pcie_link_state = PCIE_LINK_DOWN; } cut_power: cnss_configure_wlan_en_gpio(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_configure_wlan_en_gpio(WLAN_EN_HIGH); if (!pdev) { pr_err("%d: invalid pdev\n", __LINE__); goto err_pcie_link_up; } if (!penv->pcie_link_state) { ret = cnss_msm_pcie_pm_control( MSM_PCIE_RESUME, cnss_get_pci_dev_bus_number(pdev), pdev, PM_OPTIONS_RESUME_LINK_DOWN); if (ret) { pr_err("cnss: Failed to bring-up PCIe link!\n"); goto err_pcie_link_up; } penv->pcie_link_state = PCIE_LINK_UP; ret = cnss_msm_pcie_recover_config(penv->pdev); if (ret) { pr_err("cnss: PCI link failed to recover\n"); goto err_pcie_link_up; } penv->pcie_link_down_ind = false; } if (wdrv && wdrv->reinit) { if (penv->saved_state) cnss_pci_load_and_free_saved_state( pdev, &penv->saved_state); pci_restore_state(pdev); ret = wdrv->reinit(pdev, penv->id); if (ret) { pr_err("%d: Failed to do reinit\n", __LINE__); goto err_wlan_reinit; } } else { pr_err("%d: wdrv->reinit is invalid\n", __LINE__); goto err_pcie_link_up; } if (penv->notify_modem_status && wdrv->modem_status) wdrv->modem_status(pdev, penv->modem_current_status); out: penv->recovery_in_progress = false; return ret; err_wlan_reinit: pci_save_state(pdev); penv->saved_state = cnss_pci_store_saved_state(pdev); cnss_msm_pcie_pm_control( MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), pdev, PM_OPTIONS); penv->pcie_link_state = PCIE_LINK_DOWN; err_pcie_link_up: cnss_configure_wlan_en_gpio(WLAN_EN_LOW); cnss_wlan_vreg_set(vreg_info, VREG_OFF); if (penv->pdev) { if (wdrv && wdrv->update_status) wdrv->update_status(penv->pdev, CNSS_SSR_FAIL); if (!penv->recovery_in_progress) { pr_err("%d: Unregistering pci device\n", __LINE__); pci_unregister_driver(&cnss_wlan_pci_driver); penv->pdev = NULL; penv->pci_register_again = true; } } err_wlan_vreg_on: return ret; } void cnss_pci_device_self_recovery(void) { if (!penv) return; if (penv->recovery_in_progress) { pr_err("cnss: Recovery already in progress\n"); return; } if (penv->driver_status == CNSS_LOAD_UNLOAD) { pr_err("cnss: load unload in progress\n"); return; } penv->recovery_count++; penv->recovery_in_progress = true; cnss_pm_wake_lock(&penv->ws); cnss_shutdown(NULL, false); msleep(WLAN_RECOVERY_DELAY); cnss_powerup(NULL); cnss_pm_wake_lock_release(&penv->ws); penv->recovery_in_progress = false; } static int cnss_ramdump(int enable, const struct subsys_desc *subsys) { struct ramdump_segment segment; if (!penv) return -ENODEV; if (!penv->ramdump_size) return -ENOENT; if (!enable) return 0; memset(&segment, 0, sizeof(segment)); segment.v_address = penv->ramdump_addr; segment.size = penv->ramdump_size; return do_ramdump(penv->ramdump_dev, &segment, 1); } static void cnss_crash_shutdown(const struct subsys_desc *subsys) { struct cnss_wlan_driver *wdrv; struct pci_dev *pdev; if (!penv) return; wdrv = penv->driver; pdev = penv->pdev; if (pdev && wdrv && wdrv->crash_shutdown) wdrv->crash_shutdown(pdev); } void cnss_device_self_recovery(void) { if (!penv) return; if (penv->recovery_in_progress) { pr_err("cnss: Recovery already in progress\n"); return; } if (penv->driver_status == CNSS_LOAD_UNLOAD) { pr_err("cnss: load unload in progress\n"); return; } penv->recovery_count++; penv->recovery_in_progress = true; cnss_pm_wake_lock(&penv->ws); cnss_shutdown(NULL, false); msleep(WLAN_RECOVERY_DELAY); cnss_powerup(NULL); cnss_pm_wake_lock_release(&penv->ws); penv->recovery_in_progress = false; } EXPORT_SYMBOL(cnss_device_self_recovery); static int cnss_modem_notifier_nb(struct notifier_block *this, unsigned long code, void *ss_handle) { struct cnss_wlan_driver *wdrv; struct pci_dev *pdev; pr_debug("%s: Modem-Notify: event %lu\n", __func__, code); if (!penv) return NOTIFY_DONE; if (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_init_dump_entry(void) { struct msm_dump_entry dump_entry; if (!penv) return -ENODEV; if (!penv->ramdump_dynamic) return 0; penv->dump_data.addr = penv->ramdump_phys; penv->dump_data.len = penv->ramdump_size; penv->dump_data.version = CNSS_DUMP_FORMAT_VER; penv->dump_data.magic = CNSS_DUMP_MAGIC_VER_V2; strlcpy(penv->dump_data.name, CNSS_DUMP_NAME, sizeof(penv->dump_data.name)); dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN; dump_entry.addr = virt_to_phys(&penv->dump_data); return msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); } static int cnss_probe(struct platform_device *pdev) { int ret = 0; struct esoc_desc *desc; const char *client_desc; struct device *dev = &pdev->dev; #ifndef CONFIG_MSM_GVM_QUIN u32 rc_num; #endif 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; mutex_init(&penv->fw_setup_stat_lock); ret = cnss_wlan_get_resources(pdev); if (ret) goto err_get_wlan_res; ret = cnss_configure_wlan_en_gpio(WLAN_EN_HIGH); if (ret) { pr_err("%s: Failed to enable WLAN enable gpio\n", __func__); goto err_get_rc; } #ifndef CONFIG_MSM_GVM_QUIN ret = of_property_read_u32(dev->of_node, "qcom,wlan-rc-num", &rc_num); if (ret) { pr_err("%s: Failed to find PCIe RC number!\n", __func__); goto err_get_rc; } ret = cnss_msm_pcie_enumerate(rc_num); if (ret) { pr_err("%s: Failed to enable PCIe RC%x!\n", __func__, rc_num); goto err_pcie_enumerate; } #endif 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_bool(dev->of_node, "qcom,is-dual-wifi-enabled")) penv->dual_wifi_info.is_dual_wifi_enabled = true; if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic", &ramdump_size) == 0) { penv->ramdump_addr = dma_alloc_coherent(&pdev->dev, ramdump_size, &penv->ramdump_phys, GFP_KERNEL); if (penv->ramdump_addr) penv->ramdump_size = ramdump_size; penv->ramdump_dynamic = true; } else { res = platform_get_resource_byname(penv->pldev, IORESOURCE_MEM, "ramdump"); if (res) { penv->ramdump_phys = res->start; ramdump_size = resource_size(res); penv->ramdump_addr = ioremap(penv->ramdump_phys, ramdump_size); if (penv->ramdump_addr) penv->ramdump_size = ramdump_size; penv->ramdump_dynamic = false; } } pr_debug("%s: ramdump addr: %p, phys: %pa\n", __func__, penv->ramdump_addr, &penv->ramdump_phys); if (penv->ramdump_size == 0) { pr_info("%s: CNSS ramdump will not be collected", __func__); goto skip_ramdump; } ret = cnss_init_dump_entry(); if (ret) { pr_err("%s: Dump table setup failed: %d\n", __func__, ret); goto err_ramdump_create; } penv->ramdump_dev = create_ramdump_device(penv->subsysdesc.name, penv->subsysdesc.dev); if (!penv->ramdump_dev) { ret = -ENOMEM; goto err_ramdump_create; } skip_ramdump: penv->modem_current_status = 0; if (penv->notify_modem_status) { penv->modem_notify_handler = subsys_notif_register_notifier(penv->esoc_desc ? penv->esoc_desc->name : "modem", &mnb); if (IS_ERR(penv->modem_notify_handler)) { ret = PTR_ERR(penv->modem_notify_handler); pr_err("%s: Register notifier Failed\n", __func__); goto err_notif_modem; } } if (of_property_read_u32_array(dev->of_node, "qcom,wlan-smmu-iova-address", smmu_iova_address, 2) == 0) { penv->smmu_iova_start = smmu_iova_address[0]; penv->smmu_iova_len = smmu_iova_address[1]; } 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: #ifndef CONFIG_MSM_GVM_QUIN err_pcie_enumerate: #endif err_get_rc: cnss_configure_wlan_en_gpio(WLAN_EN_LOW); cnss_wlan_release_resources(); err_get_wlan_res: penv = NULL; return ret; } static int cnss_remove(struct platform_device *pdev) { unregister_pm_notifier(&cnss_pm_notifier); device_remove_file(&pdev->dev, &dev_attr_fw_image_setup); cnss_pm_wake_lock_destroy(&penv->ws); 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_configure_wlan_en_gpio(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, #ifdef CONFIG_CNSS_ASYNC .probe_type = PROBE_PREFER_ASYNCHRONOUS, #endif }, }; static int __init cnss_initialize(void) { return platform_driver_register(&cnss_driver); } static void __exit cnss_exit(void) { struct platform_device *pdev = penv->pldev; if (penv->ramdump_dev) destroy_ramdump_device(penv->ramdump_dev); if (penv->notify_modem_status) subsys_notif_unregister_notifier(penv->modem_notify_handler, &mnb); subsys_unregister(penv->subsys); if (penv->esoc_desc) devm_unregister_esoc_client(&pdev->dev, penv->esoc_desc); platform_driver_unregister(&cnss_driver); } void cnss_request_pm_qos_type(int latency_type, u32 qos_val) { if (!penv) { pr_err("%s: penv is NULL!\n", __func__); return; } pm_qos_add_request(&penv->qos_request, latency_type, qos_val); } EXPORT_SYMBOL(cnss_request_pm_qos_type); void cnss_request_pm_qos(u32 qos_val) { if (!penv) { pr_err("%s: penv is NULL!\n", __func__); return; } pm_qos_add_request(&penv->qos_request, PM_QOS_CPU_DMA_LATENCY, qos_val); } EXPORT_SYMBOL(cnss_request_pm_qos); void cnss_remove_pm_qos(void) { if (!penv) { pr_err("%s: penv is NULL!\n", __func__); return; } pm_qos_remove_request(&penv->qos_request); } EXPORT_SYMBOL(cnss_remove_pm_qos); void cnss_pci_request_pm_qos_type(int latency_type, u32 qos_val) { if (!penv) { pr_err("%s: penv is NULL\n", __func__); return; } pm_qos_add_request(&penv->qos_request, latency_type, qos_val); } EXPORT_SYMBOL(cnss_pci_request_pm_qos_type); void cnss_pci_request_pm_qos(u32 qos_val) { if (!penv) { pr_err("%s: penv is NULL\n", __func__); return; } pm_qos_add_request(&penv->qos_request, PM_QOS_CPU_DMA_LATENCY, qos_val); } EXPORT_SYMBOL(cnss_pci_request_pm_qos); void cnss_pci_remove_pm_qos(void) { if (!penv) { pr_err("%s: penv is NULL\n", __func__); return; } pm_qos_remove_request(&penv->qos_request); } EXPORT_SYMBOL(cnss_pci_remove_pm_qos); int cnss_pci_request_bus_bandwidth(int bandwidth) { int ret = 0; if (!penv) return -ENODEV; if (!penv->bus_client) return -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; } 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 = cnss_pci_store_saved_state(pdev); pci_disable_device(pdev); ret = pci_set_power_state(pdev, PCI_D3hot); if (ret) pr_err("%s: Set D3Hot failed: %d\n", __func__, ret); if (cnss_msm_pcie_pm_control( MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), pdev, PM_OPTIONS)) { pr_err("%s: Failed to shutdown PCIe link\n", __func__); ret = -EAGAIN; goto out; } } atomic_set(&penv->auto_suspended, 1); penv->monitor_wake_intr = true; penv->pcie_link_state = 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 (cnss_msm_pcie_pm_control( MSM_PCIE_RESUME, cnss_get_pci_dev_bus_number(pdev), pdev, PM_OPTIONS)) { pr_err("%s: Failed to resume PCIe link\n", __func__); ret = -EAGAIN; goto out; } ret = pci_enable_device(pdev); if (ret) pr_err("%s: enable device failed: %d\n", __func__, ret); penv->pcie_link_state = PCIE_LINK_UP; } if (penv->saved_state) cnss_pci_load_and_free_saved_state(pdev, &penv->saved_state); pci_restore_state(pdev); pci_set_master(pdev); atomic_set(&penv->auto_suspended, 0); 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; case CNSS_PM_GET_NORESUME: pm_runtime_get_noresume(dev); break; default: ret = -EINVAL; break; } return ret; } EXPORT_SYMBOL(cnss_pm_runtime_request); void cnss_runtime_init(struct device *dev, int auto_delay) { pm_runtime_set_autosuspend_delay(dev, auto_delay); pm_runtime_use_autosuspend(dev); pm_runtime_allow(dev); pm_runtime_mark_last_busy(dev); pm_runtime_put_noidle(dev); pm_suspend_ignore_children(dev, true); } EXPORT_SYMBOL(cnss_runtime_init); void cnss_runtime_exit(struct device *dev) { pm_runtime_get_noresume(dev); pm_runtime_set_active(dev); } EXPORT_SYMBOL(cnss_runtime_exit); static void __cnss_set_pcie_monitor_intr(struct device *dev, bool val) { penv->monitor_wake_intr = val; } static void __cnss_set_auto_suspend(struct device *dev, int val) { atomic_set(&penv->auto_suspended, val); } static int __cnss_resume_link(struct device *dev, u32 flags) { int ret; struct pci_dev *pdev = to_pci_dev(dev); u8 bus_num = cnss_get_pci_dev_bus_number(pdev); ret = cnss_msm_pcie_pm_control(MSM_PCIE_RESUME, bus_num, pdev, flags); if (ret) pr_err("%s: PCIe link resume failed with flags:%d bus_num:%d\n", __func__, flags, bus_num); penv->pcie_link_state = PCIE_LINK_UP; return ret; } static int __cnss_suspend_link(struct device *dev, u32 flags) { struct pci_dev *pdev = to_pci_dev(dev); u8 bus_num = cnss_get_pci_dev_bus_number(pdev); int ret; if (!penv->pcie_link_state) return 0; ret = cnss_msm_pcie_pm_control(MSM_PCIE_SUSPEND, bus_num, pdev, flags); if (ret) { pr_err("%s: Failed to suspend link\n", __func__); return ret; } penv->pcie_link_state = PCIE_LINK_DOWN; return ret; } static int __cnss_pcie_recover_config(struct device *dev) { int ret; ret = cnss_msm_pcie_recover_config(to_pci_dev(dev)); if (ret) pr_err("%s: PCIe Recover config failed\n", __func__); return ret; } static int __cnss_event_reg(struct device *dev) { int ret; struct msm_pcie_register_event *event_reg; event_reg = &penv->event_reg; event_reg->events = MSM_PCIE_EVENT_LINKDOWN | MSM_PCIE_EVENT_WAKEUP; event_reg->user = to_pci_dev(dev); event_reg->mode = MSM_PCIE_TRIGGER_CALLBACK; event_reg->callback = cnss_pci_events_cb; event_reg->options = MSM_PCIE_CONFIG_NO_RECOVERY; ret = cnss_msm_pcie_register_event(event_reg); if (ret) pr_err("%s: PCIe event register failed! %d\n", __func__, ret); return ret; } static void __cnss_event_dereg(struct device *dev) { cnss_msm_pcie_deregister_event(&penv->event_reg); } static struct pci_dev *__cnss_get_pcie_dev(struct device *dev) { int ret; struct pci_dev *pdev = penv->pdev; if (pdev) return pdev; ret = pci_register_driver(&cnss_wlan_pci_driver); if (ret) { pr_err("%s: pci re-registration failed\n", __func__); return NULL; } pdev = penv->pdev; return pdev; } static int __cnss_pcie_power_up(struct device *dev) { struct cnss_wlan_vreg_info *vreg_info; struct cnss_wlan_gpio_info *gpio_info; int ret; vreg_info = &penv->vreg_info; gpio_info = &penv->gpio_info; ret = cnss_wlan_vreg_set(vreg_info, VREG_ON); if (ret) { pr_err("%s: WLAN VREG ON Failed\n", __func__); return ret; } msleep(POWER_ON_DELAY); if (penv->wlan_bootstrap_gpio > 0) { gpio_set_value(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_HIGH); msleep(WLAN_BOOTSTRAP_DELAY); } cnss_configure_wlan_en_gpio(WLAN_EN_HIGH); return 0; } static int __cnss_pcie_power_down(struct device *dev) { struct cnss_wlan_vreg_info *vreg_info; struct cnss_wlan_gpio_info *gpio_info; int ret; vreg_info = &penv->vreg_info; gpio_info = &penv->gpio_info; cnss_configure_wlan_en_gpio(WLAN_EN_LOW); if (penv->wlan_bootstrap_gpio > 0) gpio_set_value(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_LOW); ret = cnss_wlan_vreg_set(vreg_info, VREG_OFF); if (ret) pr_err("%s: Failed to turn off 3.3V regulator\n", __func__); return ret; } static int __cnss_suspend_link_state(struct device *dev) { int ret; struct pci_dev *pdev = to_pci_dev(dev); int link_ind; if (!penv->pcie_link_state) { pr_debug("%s: Link is already suspended\n", __func__); return 0; } link_ind = penv->pcie_link_down_ind; if (!link_ind) pci_save_state(pdev); penv->saved_state = link_ind ? NULL : cnss_pci_store_saved_state(pdev); ret = link_ind ? __cnss_suspend_link(dev, PM_OPTIONS_SUSPEND_LINK_DOWN) : __cnss_suspend_link(dev, PM_OPTIONS); if (ret) { pr_err("%s: Link Suspend failed in state:%s\n", __func__, link_ind ? "LINK_DOWN" : "LINK_ACTIVE"); return ret; } penv->pcie_link_state = PCIE_LINK_DOWN; return 0; } static int __cnss_restore_pci_config_space(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); int ret = 0; if (penv->saved_state) ret = cnss_pci_load_and_free_saved_state(pdev, &penv->saved_state); pci_restore_state(pdev); return ret; } static int __cnss_resume_link_state(struct device *dev) { int ret; int link_ind; if (penv->pcie_link_state) { pr_debug("%s: Link is already in active state\n", __func__); return 0; } link_ind = penv->pcie_link_down_ind; ret = link_ind ? __cnss_resume_link(dev, PM_OPTIONS_RESUME_LINK_DOWN) : __cnss_resume_link(dev, PM_OPTIONS); if (ret) { pr_err("%s: Resume Link failed in link state:%s\n", __func__, link_ind ? "LINK_DOWN" : "LINK_ACTIVE"); return ret; } penv->pcie_link_state = PCIE_LINK_UP; ret = link_ind ? __cnss_pcie_recover_config(dev) : __cnss_restore_pci_config_space(dev); if (ret) { pr_err("%s: Link Recovery Config Failed link_state:%s\n", __func__, link_ind ? "LINK_DOWN" : "LINK_ACTIVE"); penv->pcie_link_state = PCIE_LINK_DOWN; return ret; } penv->pcie_link_down_ind = false; return ret; } int cnss_pcie_power_up(struct device *dev) { int ret; struct pci_dev *pdev; if (!penv) { pr_err("%s: platform data is NULL\n", __func__); return -ENODEV; } ret = __cnss_pcie_power_up(dev); if (ret) { pr_err("%s: Power UP Failed\n", __func__); return ret; } pdev = __cnss_get_pcie_dev(dev); if (!pdev) { pr_err("%s: PCIe Dev is NULL\n", __func__); goto power_down; } ret = __cnss_event_reg(dev); if (ret) pr_err("%s: PCIe event registration failed\n", __func__); ret = __cnss_resume_link_state(dev); if (ret) { pr_err("%s: Link Bring Up Failed\n", __func__); goto event_dereg; } __cnss_set_pcie_monitor_intr(dev, true); return ret; event_dereg: __cnss_event_dereg(dev); power_down: __cnss_pcie_power_down(dev); pr_err("%s: Device Power Up Failed Fatal Error!\n", __func__); return ret; } static void __cnss_vote_bus_width(struct device *dev, uint32_t option) { if (penv->bus_client) msm_bus_scale_client_update_request(penv->bus_client, option); } int cnss_pcie_power_down(struct device *dev) { int ret; struct pci_dev *pdev = to_pci_dev(dev); if (!penv) { pr_err("%s: Invalid Platform data\n", __func__); return -ENODEV; } if (!pdev) { pr_err("%s: Invalid Pdev, Cut Power to device\n", __func__); __cnss_pcie_power_down(dev); return -ENODEV; } __cnss_vote_bus_width(dev, CNSS_BUS_WIDTH_NONE); __cnss_event_dereg(dev); ret = __cnss_suspend_link_state(dev); if (ret) { pr_err("%s: Suspend Link failed\n", __func__); return ret; } __cnss_set_pcie_monitor_intr(dev, false); __cnss_set_auto_suspend(dev, 0); ret = __cnss_pcie_power_down(dev); if (ret) pr_err("%s: Power Down Failed\n", __func__); return ret; } module_init(cnss_initialize); module_exit(cnss_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION(DEVICE "CNSS Driver");