/* Copyright (c) 2016-2018, 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 "main.h" #include "debug.h" #include "pci.h" #include "qmi.h" #include "bus.h" #include "genl.h" #define CNSS_DUMP_FORMAT_VER 0x11 #define CNSS_DUMP_FORMAT_VER_V2 0x22 #define CNSS_DUMP_MAGIC_VER_V2 0x42445953 #define CNSS_DUMP_NAME "CNSS_WLAN" #define CNSS_DUMP_DESC_SIZE 0x1000 #define CNSS_DUMP_SEG_VER 0x1 #define WLAN_RECOVERY_DELAY 1000 #define FILE_SYSTEM_READY 1 #define FW_READY_TIMEOUT 5000 #define FW_ASSERT_TIMEOUT 5000 #define CNSS_EVENT_PENDING 2989 #define MAX_NUMBER_OF_SOCS 4 #define WAKE_MSI_NAME "WAKE" struct cnss_plat_data *plat_env[MAX_NUMBER_OF_SOCS]; int plat_env_index; static DECLARE_RWSEM(cnss_pm_sem); static bool qmi_bypass; #ifdef CONFIG_CNSS2_DEBUG module_param(qmi_bypass, bool, 0600); MODULE_PARM_DESC(qmi_bypass, "Bypass QMI from platform driver"); #endif bool qca6290_support; EXPORT_SYMBOL(qca6290_support); module_param(qca6290_support, bool, 0600); MODULE_PARM_DESC(qca6290_support, "qca6290_support"); bool skip_cnss; EXPORT_SYMBOL(skip_cnss); module_param(skip_cnss, bool, 0600); MODULE_PARM_DESC(skip_cnss, "skip_cnss"); static bool enable_waltest; #ifdef CONFIG_CNSS2_DEBUG module_param(enable_waltest, bool, 0600); MODULE_PARM_DESC(enable_waltest, "Enable to handle firmware waltest"); #endif enum cnss_debug_quirks { LINK_DOWN_SELF_RECOVERY, SKIP_DEVICE_BOOT, USE_CORE_ONLY_FW, }; unsigned long quirks; #ifdef CONFIG_CNSS2_DEBUG module_param(quirks, ulong, 0600); MODULE_PARM_DESC(quirks, "Debug quirks for the driver"); #endif 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" }; struct cnss_driver_event { struct list_head list; enum cnss_driver_event_type type; bool sync; struct completion complete; int ret; void *data; }; static void cnss_set_plat_priv(struct platform_device *plat_dev, struct cnss_plat_data *plat_priv) { if (plat_env_index >= MAX_NUMBER_OF_SOCS) { pr_err("ERROR: No space to allocate save the plat_priv\n"); return; } plat_env[plat_env_index++] = plat_priv; } void *cnss_get_pci_dev_by_device_id(int device_id) { int i; for (i = 0; i < plat_env_index; i++) { if (plat_env[i]->device_id == device_id) return plat_env[i]->pci_dev; } return NULL; } EXPORT_SYMBOL(cnss_get_pci_dev_by_device_id); struct cnss_plat_data *cnss_get_plat_priv_by_device_id(int id) { int i; for (i = 0; i < plat_env_index; i++) { if (plat_env[i]->device_id == id) return plat_env[i]; } return NULL; } struct cnss_plat_data *cnss_get_plat_priv(struct platform_device *plat_dev) { int i; if (!plat_dev) return NULL; for (i = 0; i < plat_env_index; i++) { if (plat_env[i]->plat_dev == plat_dev) return plat_env[i]; } return NULL; } 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 void cnss_pm_stay_awake(struct cnss_plat_data *plat_priv) { if (atomic_inc_return(&plat_priv->pm_count) != 1) return; cnss_pr_dbg("PM stay awake, state: 0x%lx, count: %d\n", plat_priv->driver_state, atomic_read(&plat_priv->pm_count)); pm_stay_awake(&plat_priv->plat_dev->dev); } static void cnss_pm_relax(struct cnss_plat_data *plat_priv) { int r = atomic_dec_return(&plat_priv->pm_count); WARN_ON(r < 0); if (r != 0) return; cnss_pr_dbg("PM relax, state: 0x%lx, count: %d\n", plat_priv->driver_state, atomic_read(&plat_priv->pm_count)); pm_relax(&plat_priv->plat_dev->dev); } void cnss_lock_pm_sem(struct device *dev) { down_read(&cnss_pm_sem); } EXPORT_SYMBOL(cnss_lock_pm_sem); void cnss_release_pm_sem(struct device *dev) { up_read(&cnss_pm_sem); } EXPORT_SYMBOL(cnss_release_pm_sem); int cnss_get_fw_files_for_target(struct device *dev, struct cnss_fw_files *pfw_files, u32 target_type, u32 target_version) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); if (!plat_priv) return -EINVAL; if (!pfw_files) return -ENODEV; switch (target_version) { case QCA6174_REV3_VERSION: case QCA6174_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)); cnss_pr_err("Unknown target version, type: 0x%X, version: 0x%X", target_type, target_version); break; } return 0; } EXPORT_SYMBOL(cnss_get_fw_files_for_target); int cnss_request_bus_bandwidth(struct device *dev, int bandwidth) { int ret; struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); struct cnss_bus_bw_info *bus_bw_info; if (!plat_priv) return -ENODEV; bus_bw_info = &plat_priv->bus_bw_info; if (!bus_bw_info->bus_client) return -EINVAL; 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( bus_bw_info->bus_client, bandwidth); if (!ret) bus_bw_info->current_bw_vote = bandwidth; else cnss_pr_err("Could not set bus bandwidth: %d, err = %d\n", bandwidth, ret); break; default: cnss_pr_err("Invalid bus bandwidth: %d", bandwidth); ret = -EINVAL; } return ret; } EXPORT_SYMBOL(cnss_request_bus_bandwidth); int cnss_get_platform_cap(struct device *dev, struct cnss_platform_cap *cap) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); if (!plat_priv) return -ENODEV; if (cap) *cap = plat_priv->cap; return 0; } EXPORT_SYMBOL(cnss_get_platform_cap); int cnss_get_soc_info(struct device *dev, struct cnss_soc_info *info) { int ret; struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); void *bus_priv = cnss_bus_dev_to_bus_priv(dev); if (!plat_priv) return -ENODEV; ret = cnss_pci_get_bar_info(bus_priv, &info->va, &info->pa); if (ret) return ret; return 0; } EXPORT_SYMBOL(cnss_get_soc_info); void cnss_request_pm_qos(struct device *dev, u32 qos_val) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); if (!plat_priv) return; pm_qos_add_request(&plat_priv->qos_request, PM_QOS_CPU_DMA_LATENCY, qos_val); } EXPORT_SYMBOL(cnss_request_pm_qos); void cnss_remove_pm_qos(struct device *dev) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); if (!plat_priv) return; pm_qos_remove_request(&plat_priv->qos_request); } EXPORT_SYMBOL(cnss_remove_pm_qos); int cnss_wlan_enable(struct device *dev, struct cnss_wlan_enable_cfg *config, enum cnss_driver_mode mode, const char *host_version) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); struct wlfw_wlan_cfg_req_msg_v01 req; u32 i; int ret; if (!plat_priv) return 0; if (plat_priv->device_id == QCA6018_DEVICE_ID) { cnss_pr_dbg("Disable cnss_wlan_enable for QCA6018\n"); return 0; } if (plat_priv->device_id == QCA6174_DEVICE_ID) return 0; if (qmi_bypass) return 0; if (mode == CNSS_CALIBRATION || mode == CNSS_WALTEST || mode == CNSS_FTM_CALIBRATION) goto skip_cfg; if (!config || !host_version) { cnss_pr_err("Invalid config or host_version pointer\n"); return -EINVAL; } cnss_pr_dbg("Mode: %d, config: %pK, host_version: %s\n", mode, config, host_version); if (mode == CNSS_WALTEST || mode == CNSS_CCPM) goto skip_cfg; memset(&req, 0, sizeof(req)); req.host_version_valid = 1; strlcpy(req.host_version, host_version, QMI_WLFW_MAX_STR_LEN_V01 + 1); req.tgt_cfg_valid = 1; if (config->num_ce_tgt_cfg > QMI_WLFW_MAX_NUM_CE_V01) req.tgt_cfg_len = QMI_WLFW_MAX_NUM_CE_V01; else req.tgt_cfg_len = config->num_ce_tgt_cfg; for (i = 0; i < req.tgt_cfg_len; i++) { req.tgt_cfg[i].pipe_num = config->ce_tgt_cfg[i].pipe_num; req.tgt_cfg[i].pipe_dir = config->ce_tgt_cfg[i].pipe_dir; req.tgt_cfg[i].nentries = config->ce_tgt_cfg[i].nentries; req.tgt_cfg[i].nbytes_max = config->ce_tgt_cfg[i].nbytes_max; req.tgt_cfg[i].flags = config->ce_tgt_cfg[i].flags; } req.svc_cfg_valid = 1; if (config->num_ce_svc_pipe_cfg > QMI_WLFW_MAX_NUM_SVC_V01) req.svc_cfg_len = QMI_WLFW_MAX_NUM_SVC_V01; else req.svc_cfg_len = config->num_ce_svc_pipe_cfg; for (i = 0; i < req.svc_cfg_len; i++) { req.svc_cfg[i].service_id = config->ce_svc_cfg[i].service_id; req.svc_cfg[i].pipe_dir = config->ce_svc_cfg[i].pipe_dir; req.svc_cfg[i].pipe_num = config->ce_svc_cfg[i].pipe_num; } #ifdef CONFIG_CNSS2_SHADOW_REGISTER req.shadow_reg_v2_valid = 1; if (config->num_shadow_reg_v2_cfg > QMI_WLFW_MAX_NUM_SHADOW_REG_V2_V01) req.shadow_reg_v2_len = QMI_WLFW_MAX_NUM_SHADOW_REG_V2_V01; else req.shadow_reg_v2_len = config->num_shadow_reg_v2_cfg; memcpy(req.shadow_reg_v2, config->shadow_reg_v2_cfg, sizeof(struct wlfw_shadow_reg_v2_cfg_s_v01) * req.shadow_reg_v2_len); #else req.shadow_reg_v2_valid = 0; #endif if (mode != QMI_WLFW_CALIBRATION_V01 && mode != QMI_WLFW_FTM_CALIBRATION_V01) { ret = cnss_wlfw_wlan_cfg_send_sync(plat_priv, &req); if (ret) goto out; } skip_cfg: if (mode == CNSS_CALIBRATION || mode == CNSS_FTM_CALIBRATION) set_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state); ret = cnss_wlfw_wlan_mode_send_sync(plat_priv, mode); out: return ret; } EXPORT_SYMBOL(cnss_wlan_enable); int cnss_wlan_disable(struct device *dev, enum cnss_driver_mode mode) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); if (!plat_priv) return 0; if (plat_priv->device_id == QCA6174_DEVICE_ID) return 0; if (qmi_bypass) return 0; return cnss_wlfw_wlan_mode_send_sync(plat_priv, QMI_WLFW_OFF_V01); } EXPORT_SYMBOL(cnss_wlan_disable); #ifdef CONFIG_CNSS2_DEBUG int cnss_athdiag_read(struct device *dev, u32 offset, u32 mem_type, u32 data_len, u8 *output) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); int ret = 0; if (!plat_priv) { cnss_pr_err("plat_priv is NULL!\n"); return -EINVAL; } if (plat_priv->device_id == QCA6174_DEVICE_ID) return 0; if (!output || data_len == 0 || data_len > QMI_WLFW_MAX_DATA_SIZE_V01) { cnss_pr_err("Inv param athdiag read: output %p, data_len %u\n", output, data_len); ret = -EINVAL; goto out; } if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) { cnss_pr_err("Invalid state for athdiag read: 0x%lx\n", plat_priv->driver_state); ret = -EINVAL; goto out; } ret = cnss_wlfw_athdiag_read_send_sync(plat_priv, offset, mem_type, data_len, output); out: return ret; } EXPORT_SYMBOL(cnss_athdiag_read); int cnss_athdiag_write(struct device *dev, u32 offset, u32 mem_type, u32 data_len, u8 *input) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); int ret = 0; if (!plat_priv) { cnss_pr_err("plat_priv is NULL!\n"); return -EINVAL; } if (plat_priv->device_id == QCA6174_DEVICE_ID) return 0; if (!input || data_len == 0 || data_len > QMI_WLFW_MAX_DATA_SIZE_V01) { cnss_pr_err("Inv param athdiag write: input %p, data_len %u\n", input, data_len); ret = -EINVAL; goto out; } if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) { cnss_pr_err("Invalid state for athdiag write: 0x%lx\n", plat_priv->driver_state); ret = -EINVAL; goto out; } ret = cnss_wlfw_athdiag_write_send_sync(plat_priv, offset, mem_type, data_len, input); out: return ret; } EXPORT_SYMBOL(cnss_athdiag_write); #else int cnss_athdiag_read(struct device *dev, u32 offset, u32 mem_type, u32 data_len, u8 *output) { return -EPERM; } EXPORT_SYMBOL(cnss_athdiag_read); int cnss_athdiag_write(struct device *dev, u32 offset, u32 mem_type, u32 data_len, u8 *input) { return -EPERM; } EXPORT_SYMBOL(cnss_athdiag_write); #endif int cnss_set_fw_log_mode(struct device *dev, u8 fw_log_mode) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); if (!plat_priv) return -EINVAL; if (plat_priv->device_id == QCA6174_DEVICE_ID) return 0; return cnss_wlfw_ini_send_sync(plat_priv, fw_log_mode); } EXPORT_SYMBOL(cnss_set_fw_log_mode); u32 cnss_get_wake_msi(struct cnss_plat_data *plat_priv) { struct cnss_pci_data *pci_priv = plat_priv->bus_priv; int ret, num_vectors; u32 user_base_data, base_vector; ret = cnss_get_user_msi_assignment(&pci_priv->pci_dev->dev, WAKE_MSI_NAME, &num_vectors, &user_base_data, &base_vector); if (ret) { cnss_pr_err("WAKE MSI is not valid\n"); return 0; } return user_base_data; } static int cnss_fw_mem_ready_hdlr(struct cnss_plat_data *plat_priv) { int ret = 0; if (!plat_priv) return -ENODEV; set_bit(CNSS_FW_MEM_READY, &plat_priv->driver_state); ret = cnss_wlfw_tgt_cap_send_sync(plat_priv); if (ret) goto out; ret = cnss_wlfw_bdf_dnld_send_sync(plat_priv); if (ret) goto out; if (plat_priv->device_id == QCA6290_DEVICE_ID) { ret = cnss_pci_load_m3(plat_priv); if (ret) goto out; } ret = cnss_wlfw_m3_dnld_send_sync(plat_priv); if (ret) goto out; return 0; out: return ret; } static int cnss_driver_call_probe(struct cnss_plat_data *plat_priv) { int ret = 0; struct cnss_pci_data *pci_priv = plat_priv->bus_priv; if (test_bit(CNSS_DRIVER_DEBUG, &plat_priv->driver_state)) { clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); pr_err("Skip driver probe\n"); goto out; } if (!plat_priv->driver_ops) { cnss_pr_err("driver_ops is NULL\n"); ret = -EINVAL; goto out; } if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) { ret = plat_priv->driver_ops->reinit(pci_priv->pci_dev, pci_priv->pci_device_id); if (ret) { cnss_pr_err("Failed to reinit host driver, err = %d\n", ret); goto out; } clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); } else if (test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state)) { ret = plat_priv->driver_ops->probe(pci_priv->pci_dev, pci_priv->pci_device_id); if (ret) { cnss_pr_err("Failed to probe host driver, err = %d\n", ret); goto out; } clear_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state); set_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state); } return 0; out: return ret; } static int cnss_driver_call_remove(struct cnss_plat_data *plat_priv) { struct cnss_pci_data *pci_priv = plat_priv->bus_priv; if (test_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state) || test_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state) || test_bit(CNSS_DRIVER_DEBUG, &plat_priv->driver_state)) { cnss_pr_dbg("Skip driver remove\n"); return 0; } if (!plat_priv->driver_ops) { cnss_pr_err("driver_ops is NULL\n"); return -EINVAL; } if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) { plat_priv->driver_ops->shutdown(pci_priv->pci_dev); } else if (test_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state)) { plat_priv->driver_ops->remove(pci_priv->pci_dev); clear_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state); } return 0; } int cnss_is_fw_ready(struct device *dev) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); if (!plat_priv) return 0; if (plat_priv->device_id == QCA6018_DEVICE_ID) { cnss_pr_err("ret cnss_is_fw_ready true for QCA6018\n"); return 1; } if (test_bit(CNSS_FW_READY, &plat_priv->driver_state)) return 1; return 0; } EXPORT_SYMBOL(cnss_is_fw_ready); int cnss_is_cold_boot_cal_done(struct device *dev) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); if (test_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state)) return 0; return 1; } EXPORT_SYMBOL(cnss_is_cold_boot_cal_done); static int cnss_fw_ready_hdlr(struct cnss_plat_data *plat_priv) { int ret = 0; if (!plat_priv) return -ENODEV; del_timer(&plat_priv->fw_boot_timer); set_bit(CNSS_FW_READY, &plat_priv->driver_state); if (test_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state)) { clear_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state); clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); } if (enable_waltest) { ret = cnss_wlfw_wlan_mode_send_sync(plat_priv, QMI_WLFW_WALTEST_V01); } else if (test_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state)) { ret = cnss_wlfw_wlan_mode_send_sync(plat_priv, QMI_WLFW_CALIBRATION_V01); } else if (test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state) || test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) { } else { complete(&plat_priv->power_up_complete); } if (ret) goto shutdown; return 0; shutdown: if (plat_priv->device_id == QCA6290_DEVICE_ID) cnss_pci_stop_mhi(plat_priv->bus_priv); return ret; } static char *cnss_driver_event_to_str(enum cnss_driver_event_type type) { switch (type) { case CNSS_DRIVER_EVENT_SERVER_ARRIVE: return "SERVER_ARRIVE"; case CNSS_DRIVER_EVENT_SERVER_EXIT: return "SERVER_EXIT"; case CNSS_DRIVER_EVENT_REQUEST_MEM: return "REQUEST_MEM"; case CNSS_DRIVER_EVENT_FW_MEM_READY: return "FW_MEM_READY"; case CNSS_DRIVER_EVENT_FW_READY: return "FW_READY"; case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_START: return "COLD_BOOT_CAL_START"; case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_DONE: return "COLD_BOOT_CAL_DONE"; case CNSS_DRIVER_EVENT_REGISTER_DRIVER: return "REGISTER_DRIVER"; case CNSS_DRIVER_EVENT_UNREGISTER_DRIVER: return "UNREGISTER_DRIVER"; case CNSS_DRIVER_EVENT_RECOVERY: return "RECOVERY"; case CNSS_DRIVER_EVENT_FORCE_FW_ASSERT: return "FORCE_FW_ASSERT"; case CNSS_DRIVER_EVENT_POWER_UP: return "POWER_UP"; case CNSS_DRIVER_EVENT_POWER_DOWN: return "POWER_DOWN"; case CNSS_DRIVER_EVENT_QDSS_TRACE_REQ_MEM: return "QDSS_TRACE_REQ_MEM"; case CNSS_DRIVER_EVENT_QDSS_TRACE_SAVE: return "QDSS_TRACE_SAVE"; case CNSS_DRIVER_EVENT_QDSS_TRACE_FREE: return "QDSS_TRACE_FREE"; case CNSS_DRIVER_EVENT_MAX: return "EVENT_MAX"; } return "UNKNOWN"; }; int cnss_driver_event_post(struct cnss_plat_data *plat_priv, enum cnss_driver_event_type type, bool sync, void *data) { struct cnss_driver_event *event; unsigned long flags; int gfp = GFP_KERNEL; int ret = 0; if (!plat_priv) return -ENODEV; cnss_pr_dbg("Posting event: %s(%d)%s, state: 0x%lx\n", cnss_driver_event_to_str(type), type, sync ? "-sync" : "", plat_priv->driver_state); if (type >= CNSS_DRIVER_EVENT_MAX) { cnss_pr_err("Invalid Event type: %d, can't post", type); return -EINVAL; } if (in_interrupt() || irqs_disabled()) gfp = GFP_ATOMIC; event = kzalloc(sizeof(*event), gfp); if (!event) return -ENOMEM; event->type = type; event->data = data; init_completion(&event->complete); event->ret = CNSS_EVENT_PENDING; event->sync = sync; spin_lock_irqsave(&plat_priv->event_lock, flags); list_add_tail(&event->list, &plat_priv->event_list); spin_unlock_irqrestore(&plat_priv->event_lock, flags); queue_work(plat_priv->event_wq, &plat_priv->event_work); if (!sync) goto out; printk(KERN_INFO "Waiting for Event(%s) to complete\n", cnss_driver_event_to_str(type)); ret = wait_for_completion_interruptible(&event->complete); cnss_pr_dbg("Completed event: %s(%d), state: 0x%lx, ret: %d/%d\n", cnss_driver_event_to_str(type), type, plat_priv->driver_state, ret, event->ret); spin_lock_irqsave(&plat_priv->event_lock, flags); if (ret == -ERESTARTSYS && event->ret == CNSS_EVENT_PENDING) { event->sync = false; spin_unlock_irqrestore(&plat_priv->event_lock, flags); ret = -EINTR; goto out; } spin_unlock_irqrestore(&plat_priv->event_lock, flags); ret = event->ret; kfree(event); out: return ret; } int cnss_power_up(struct device *dev) { int ret = 0; struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); unsigned int timeout; if (!plat_priv) { printk(KERN_ERR "plat_priv is NULL\n"); return -ENODEV; } cnss_pr_dbg("Powering up device\n"); ret = cnss_driver_event_post(plat_priv, CNSS_DRIVER_EVENT_POWER_UP, true, NULL); if (ret) goto out; if (plat_priv->device_id == QCA6174_DEVICE_ID) goto out; timeout = cnss_get_qmi_timeout(); reinit_completion(&plat_priv->power_up_complete); ret = wait_for_completion_timeout(&plat_priv->power_up_complete, msecs_to_jiffies(timeout) << 2); if (!ret) { cnss_pr_err("Timeout waiting for power up to complete\n"); ret = -EAGAIN; goto out; } return 0; out: return ret; } EXPORT_SYMBOL(cnss_power_up); int cnss_power_down(struct device *dev) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); if (!plat_priv) { printk(KERN_ERR "plat_priv is NULL\n"); return -ENODEV; } cnss_pr_dbg("Powering down device\n"); return cnss_driver_event_post(plat_priv, CNSS_DRIVER_EVENT_POWER_DOWN, true, NULL); } EXPORT_SYMBOL(cnss_power_down); int cnss_unregister_qca8074_cb(struct cnss_plat_data *plat_priv) { void *handler = plat_priv->esoc_info.modem_notify_handler; if (handler) { /* unregister atomic handler first */ subsys_notif_unregister_atomic_notifier(handler, &plat_priv->modem_atomic_nb); subsys_notif_unregister_notifier(handler, &plat_priv->modem_nb); memset(&plat_priv->modem_nb, 0, sizeof(struct notifier_block)); plat_priv->esoc_info.modem_notify_handler = NULL; } } int cnss_unregister_qca6290_cb(struct cnss_plat_data *plat_priv) { void *handler = plat_priv->esoc_info.modem_notify_handler; if (handler) { subsys_notif_unregister_notifier(handler, &plat_priv->modem_nb); memset(&plat_priv->modem_nb, 0, sizeof(struct notifier_block)); plat_priv->esoc_info.modem_notify_handler = NULL; } return 0; } static int cnss_qca8074_notifier_atomic_nb(struct notifier_block *nb, unsigned long code, void *ss_handle) { struct cnss_plat_data *plat_priv = container_of(nb, struct cnss_plat_data, modem_atomic_nb); struct cnss_pci_data *pci_priv = plat_priv->bus_priv; struct cnss_wlan_driver *driver_ops; driver_ops = plat_priv->driver_ops; if (code == SUBSYS_PREPARE_FOR_FATAL_SHUTDOWN) driver_ops->fatal(plat_priv->plat_dev, plat_priv->plat_dev_id); return NOTIFY_OK; } static int cnss_qca6290_notifier_nb(struct notifier_block *nb, unsigned long code, void *ss_handle) { struct cnss_plat_data *plat_priv = container_of(nb, struct cnss_plat_data, modem_nb); struct cnss_pci_data *pci_priv = plat_priv->bus_priv; struct cnss_wlan_driver *driver_ops; driver_ops = plat_priv->driver_ops; if (code == SUBSYS_RAMDUMP_NOTIFICATION) { driver_ops->reinit(plat_priv->pci_dev, plat_priv->pci_dev_id); return NOTIFY_DONE; } return NOTIFY_OK; } void *cnss_register_qca6290_cb(struct cnss_plat_data *plat_priv) { struct cnss_subsys_info *subsys_info; subsys_info = &plat_priv->subsys_info; subsys_info->subsys_desc.name = "QCA6290"; plat_priv->modem_nb.notifier_call = cnss_qca6290_notifier_nb; return subsys_notif_register_notifier(subsys_info->subsys_desc.name, &plat_priv->modem_nb); } static int cnss_qca8074_notifier_nb(struct notifier_block *nb, unsigned long code, void *ss_handle) { struct cnss_plat_data *plat_priv = container_of(nb, struct cnss_plat_data, modem_nb); struct cnss_pci_data *pci_priv = plat_priv->bus_priv; struct cnss_wlan_driver *driver_ops; driver_ops = plat_priv->driver_ops; if (code == SUBSYS_AFTER_POWERUP) { driver_ops->probe(plat_priv->plat_dev, plat_priv->plat_dev_id); } else if (code == SUBSYS_BEFORE_SHUTDOWN) { driver_ops->remove(plat_priv->plat_dev); } else if (code == SUBSYS_RAMDUMP_NOTIFICATION) { coresight_abort(); driver_ops->reinit(plat_priv->plat_dev, plat_priv->plat_dev_id); return NOTIFY_DONE; } else { driver_ops->update_status(plat_priv->plat_dev, code, plat_priv->plat_dev_id); } return NOTIFY_OK; } void *cnss_register_qca8074_cb(struct cnss_plat_data *plat_priv) { struct cnss_subsys_info *subsys_info; void *ss_handle = NULL; subsys_info = &plat_priv->subsys_info; plat_priv->modem_nb.notifier_call = cnss_qca8074_notifier_nb; ss_handle = subsys_notif_register_notifier( subsys_info->subsys_desc.name, &plat_priv->modem_nb); /* register the atomic notifier as well */ if (ss_handle) { plat_priv->modem_atomic_nb.notifier_call = cnss_qca8074_notifier_atomic_nb; subsys_notif_register_atomic_notifier(ss_handle, &plat_priv->modem_atomic_nb); } return ss_handle; } int cnss_wlan_register_driver(struct cnss_wlan_driver *driver_ops) { int ret, i; struct cnss_plat_data *plat_priv; struct cnss_subsys_info *subsys_info; struct cnss_esoc_info *esoc_info; for (i = 0; i < plat_env_index; i++) { plat_priv = plat_env[i]; if (!plat_priv) continue; if ((plat_priv->device_id == QCA8074_DEVICE_ID || plat_priv->device_id == QCA8074V2_DEVICE_ID || plat_priv->device_id == QCA6018_DEVICE_ID) && (strcmp(driver_ops->name, "pld_ahb") == 0)) { plat_priv->driver_status = CNSS_LOAD_UNLOAD; plat_priv->driver_ops = driver_ops; cnss_register_subsys(plat_priv); esoc_info = &plat_priv->esoc_info; esoc_info->modem_notify_handler = cnss_register_qca8074_cb(plat_priv); subsys_info = &plat_priv->subsys_info; subsys_info->subsys_handle = subsystem_get(subsys_info->subsys_desc.name); if (!subsys_info->subsys_handle) { ret = -EINVAL; goto reset_ctx; } else if (IS_ERR(subsys_info->subsys_handle)) { ret = PTR_ERR(subsys_info->subsys_handle); goto reset_ctx; } } if (plat_priv->device_id == QCA6290_DEVICE_ID && (strcmp(driver_ops->name, "pld_pcie") == 0)) { cnss_pci_init(plat_priv); plat_priv->driver_status = CNSS_LOAD_UNLOAD; plat_priv->driver_ops = driver_ops; set_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state); esoc_info = &plat_priv->esoc_info; esoc_info->modem_notify_handler = cnss_register_qca6290_cb(plat_priv); cnss_register_subsys(plat_priv); } plat_priv->driver_status = CNSS_INITIALIZED; } return 0; reset_ctx: cnss_pr_err("Failed to get subsystem, err = %d\n", ret); plat_priv->driver_status = CNSS_UNINITIALIZED; plat_priv->driver_ops = NULL; out: return ret; } EXPORT_SYMBOL(cnss_wlan_register_driver); void cnss_wlan_unregister_driver(struct cnss_wlan_driver *driver_ops) { struct cnss_plat_data *plat_priv; struct cnss_plat_data *plat_priv_w; struct cnss_subsys_info *subsys_info; struct cnss_wlan_driver *ops; int i; for (i = 0; i < plat_env_index; i++) { plat_priv = plat_env[i]; if (!plat_priv) { printk(KERN_ERR "%s plat_priv is NULL!\n", __func__); return; } plat_priv->driver_status = CNSS_LOAD_UNLOAD; ops = plat_priv->driver_ops; if ((plat_priv->device_id == QCA8074_DEVICE_ID || plat_priv->device_id == QCA8074V2_DEVICE_ID || plat_priv->device_id == QCA6018_DEVICE_ID) && ops) { subsys_info = &plat_priv->subsys_info; if (subsys_info->subsys_handle && !subsys_info->subsystem_put_in_progress) { subsys_info->subsystem_put_in_progress = true; subsystem_put(subsys_info->subsys_handle); subsys_info->subsystem_put_in_progress = false; } else { ops->remove(plat_priv->plat_dev); } subsys_info->subsys_handle = NULL; cnss_unregister_qca8074_cb(plat_priv); } if (plat_priv->device_id == QCA6290_DEVICE_ID && ops) { set_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state); subsys_info = &plat_priv->subsys_info; if (subsys_info->subsys_handle) cnss_unregister_subsys(plat_priv); else ops->remove(plat_priv->pci_dev); cnss_unregister_qca6290_cb(plat_priv); } plat_priv->driver_ops = NULL; plat_priv->driver_status = CNSS_UNINITIALIZED; plat_priv->driver_state = 0; } } EXPORT_SYMBOL(cnss_wlan_unregister_driver); void *cnss_subsystem_get(struct device *dev, int device_id) { struct cnss_plat_data *plat_priv; struct cnss_subsys_info *subsys_info; plat_priv = cnss_get_plat_priv_by_device_id(device_id); if (!plat_priv) { return NULL; } if (device_id == QCA6290_DEVICE_ID) cnss_pci_init(plat_priv); subsys_info = &plat_priv->subsys_info; if (subsys_info->subsys_handle) { pr_err("%s: error: subsys handle %p is not NULL ", __func__, subsys_info->subsys_handle); return NULL; } subsys_info->subsys_handle = subsystem_get(subsys_info->subsys_desc.name); return subsys_info->subsys_handle; } EXPORT_SYMBOL(cnss_subsystem_get); void cnss_subsystem_put(struct device *dev) { struct cnss_plat_data *plat_priv; struct cnss_subsys_info *subsys_info; plat_priv = cnss_bus_dev_to_plat_priv(dev); if (!plat_priv) return; subsys_info = &plat_priv->subsys_info; if (!subsys_info->subsys_handle) { pr_err("%s: error: subsys handle is NULL", __func__); return; } if (!subsys_info->subsystem_put_in_progress) { subsys_info->subsystem_put_in_progress = true; subsystem_put(subsys_info->subsys_handle); subsys_info->subsystem_put_in_progress = false; subsys_info->subsys_handle = NULL; } if (plat_priv->device_id == QCA6290_DEVICE_ID) cnss_unregister_subsys(plat_priv); plat_priv->driver_state = 0; } EXPORT_SYMBOL(cnss_subsystem_put); static int cnss_get_resources(struct cnss_plat_data *plat_priv) { int ret; ret = cnss_get_pinctrl(plat_priv); if (ret) { cnss_pr_err("Failed to get pinctrl, err = %d\n", ret); goto out; } return 0; out: return ret; } static void cnss_put_resources(struct cnss_plat_data *plat_priv) { } static int cnss_modem_notifier_nb(struct notifier_block *nb, unsigned long code, void *ss_handle) { struct cnss_plat_data *plat_priv = container_of(nb, struct cnss_plat_data, modem_nb); struct cnss_pci_data *pci_priv = plat_priv->bus_priv; struct cnss_esoc_info *esoc_info; struct cnss_wlan_driver *driver_ops; cnss_pr_dbg("Modem notifier: event %lu\n", code); if (!pci_priv) return NOTIFY_DONE; esoc_info = &plat_priv->esoc_info; if (code == SUBSYS_AFTER_POWERUP) esoc_info->modem_current_status = 1; else if (code == SUBSYS_BEFORE_SHUTDOWN) esoc_info->modem_current_status = 0; else return NOTIFY_DONE; driver_ops = plat_priv->driver_ops; if (!driver_ops || !driver_ops->modem_status) return NOTIFY_DONE; driver_ops->modem_status(pci_priv->pci_dev, esoc_info->modem_current_status); return NOTIFY_OK; } static int cnss_register_esoc(struct cnss_plat_data *plat_priv) { int ret = 0; struct device *dev; struct cnss_esoc_info *esoc_info; struct esoc_desc *esoc_desc; const char *client_desc; dev = &plat_priv->plat_dev->dev; esoc_info = &plat_priv->esoc_info; esoc_info->notify_modem_status = of_property_read_bool(dev->of_node, "qcom,notify-modem-status"); if (esoc_info->notify_modem_status) goto out; ret = of_property_read_string_index(dev->of_node, "esoc-names", 0, &client_desc); if (ret) { cnss_pr_dbg("esoc-names is not defined in DT, skip!\n"); } else { esoc_desc = devm_register_esoc_client(dev, client_desc); if (IS_ERR_OR_NULL(esoc_desc)) { ret = PTR_RET(esoc_desc); cnss_pr_err("Failed to register esoc_desc, err = %d\n", ret); goto out; } esoc_info->esoc_desc = esoc_desc; } plat_priv->modem_nb.notifier_call = cnss_modem_notifier_nb; esoc_info->modem_current_status = 0; esoc_info->modem_notify_handler = subsys_notif_register_notifier(esoc_info->esoc_desc ? esoc_info->esoc_desc->name : "modem", &plat_priv->modem_nb); if (IS_ERR(esoc_info->modem_notify_handler)) { ret = PTR_ERR(esoc_info->modem_notify_handler); cnss_pr_err("Failed to register esoc notifier, err = %d\n", ret); goto unreg_esoc; } return 0; unreg_esoc: if (esoc_info->esoc_desc) devm_unregister_esoc_client(dev, esoc_info->esoc_desc); out: return ret; } static void cnss_unregister_esoc(struct cnss_plat_data *plat_priv) { struct device *dev; struct cnss_esoc_info *esoc_info; dev = &plat_priv->plat_dev->dev; esoc_info = &plat_priv->esoc_info; if (esoc_info->notify_modem_status) subsys_notif_unregister_notifier(esoc_info-> modem_notify_handler, &plat_priv->modem_nb); if (esoc_info->esoc_desc) devm_unregister_esoc_client(dev, esoc_info->esoc_desc); } static int cnss_qca6174_powerup(struct cnss_plat_data *plat_priv) { int ret = 0; struct cnss_pci_data *pci_priv = plat_priv->bus_priv; if (!pci_priv) { cnss_pr_err("pci_priv is NULL!\n"); return -ENODEV; } if (!plat_priv->driver_ops) { cnss_pr_err("driver_ops is NULL!\n"); return -EINVAL; } ret = cnss_power_on_device(plat_priv, 0); if (ret) { cnss_pr_err("Failed to power on device, err = %d\n", ret); goto out; } ret = cnss_resume_pci_link(pci_priv); if (ret) { cnss_pr_err("Failed to resume PCI link, err = %d\n", ret); goto power_off; } ret = cnss_driver_call_probe(plat_priv); if (ret) goto suspend_link; return 0; suspend_link: cnss_suspend_pci_link(pci_priv); power_off: cnss_power_off_device(plat_priv, 0); out: return ret; } static int cnss_qca6174_shutdown(struct cnss_plat_data *plat_priv) { int ret = 0; struct cnss_pci_data *pci_priv = plat_priv->bus_priv; if (!pci_priv) return -ENODEV; if (!plat_priv->driver_ops) return -EINVAL; cnss_pm_request_resume(pci_priv); cnss_driver_call_remove(plat_priv); cnss_request_bus_bandwidth(&plat_priv->plat_dev->dev, CNSS_BUS_WIDTH_NONE); cnss_pci_set_monitor_wake_intr(pci_priv, false); cnss_pci_set_auto_suspended(pci_priv, 0); ret = cnss_suspend_pci_link(pci_priv); if (ret) cnss_pr_err("Failed to suspend PCI link, err = %d\n", ret); cnss_power_off_device(plat_priv, 0); clear_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state); return ret; } static void cnss_qca6174_crash_shutdown(struct cnss_plat_data *plat_priv) { struct cnss_pci_data *pci_priv = plat_priv->bus_priv; if (!plat_priv->driver_ops) return; plat_priv->driver_ops->crash_shutdown(pci_priv->pci_dev); } static int cnss_qca6290_powerup(struct cnss_plat_data *plat_priv) { int ret = 0; struct cnss_pci_data *pci_priv = plat_priv->bus_priv; unsigned int timeout; if (!pci_priv) { cnss_pr_err("cnss_qca6290_powerup pci_priv is NULL!\n"); return -ENODEV; } if (plat_priv->ramdump_info_v2.dump_data_valid || test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) { cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_DEINIT); cnss_pci_clear_dump_info(pci_priv); } timeout = cnss_get_qmi_timeout(); if (test_bit(USE_CORE_ONLY_FW, &quirks)) { clear_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state); clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); return 0; } ret = cnss_driver_call_probe(plat_priv); if (ret) goto stop_mhi; return 0; stop_mhi: cnss_pci_stop_mhi(pci_priv); power_off: out: return ret; } static int cnss_qca6290_shutdown(struct cnss_plat_data *plat_priv) { struct cnss_pci_data *pci_priv = plat_priv->bus_priv; if (!pci_priv) return -ENODEV; cnss_driver_call_remove(plat_priv); cnss_pci_stop_mhi(pci_priv); cnss_pci_remove(plat_priv->pci_dev); plat_priv->driver_state = 0; return 0; } static void cnss_qca6290_crash_shutdown(struct cnss_plat_data *plat_priv) { struct cnss_pci_data *pci_priv = plat_priv->bus_priv; int ret = 0; cnss_pr_dbg("Crash shutdown with driver_state 0x%lx\n", plat_priv->driver_state); if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state) || test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state) || test_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state)) return; ret = cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_RDDM_KERNEL_PANIC); if (ret) { cnss_pr_err("Fail to complete RDDM, err = %d\n", ret); return; } cnss_pci_collect_dump_info(pci_priv); } static int cnss_powerup(struct subsys_desc *subsys_desc) { int ret = 0; struct cnss_plat_data *plat_priv; if (!subsys_desc->dev) { printk(KERN_ERR "dev from subsys_desc is NULL\n"); return -ENODEV; } plat_priv = dev_get_drvdata(subsys_desc->dev); if (!plat_priv) return -ENODEV; ret = cnss_pci_probe(plat_priv->pci_dev, plat_priv->pci_dev_id); if (ret) { pr_err("ERROR : %s:%d ret %d\n", __func__, __LINE__, ret); return -ENODEV; } switch (plat_priv->device_id) { case QCA6174_DEVICE_ID: ret = cnss_qca6174_powerup(plat_priv); break; case QCA6290_EMULATION_DEVICE_ID: case QCA6290_DEVICE_ID: ret = cnss_qca6290_powerup(plat_priv); break; default: cnss_pr_err("Unknown device_id found: 0x%lx\n", plat_priv->device_id); ret = -ENODEV; } return ret; } static int cnss_shutdown(const struct subsys_desc *subsys_desc, bool force_stop) { int ret = 0; struct cnss_plat_data *plat_priv; if (!subsys_desc->dev) { printk(KERN_ERR "dev from subsys_desc is NULL\n"); return -ENODEV; } plat_priv = dev_get_drvdata(subsys_desc->dev); if (!plat_priv) { printk(KERN_ERR "plat_priv is NULL!\n"); return -ENODEV; } switch (plat_priv->device_id) { case QCA6174_DEVICE_ID: ret = cnss_qca6174_shutdown(plat_priv); break; case QCA6290_DEVICE_ID: ret = cnss_qca6290_shutdown(plat_priv); break; default: cnss_pr_err("Unknown device_id found: 0x%lx\n", plat_priv->device_id); ret = -ENODEV; } return ret; } static int cnss_qca6290_ramdump(struct cnss_plat_data *plat_priv) { struct cnss_ramdump_info_v2 *info_v2 = &plat_priv->ramdump_info_v2; struct cnss_dump_data *dump_data = &info_v2->dump_data; struct cnss_dump_seg *dump_seg = info_v2->dump_data_vaddr; struct ramdump_segment *ramdump_segs, *s; int i, ret = 0; if (!info_v2->dump_data_valid || dump_data->nentries == 0) return 0; ramdump_segs = kcalloc(dump_data->nentries, sizeof(*ramdump_segs), GFP_KERNEL); if (!ramdump_segs) return -ENOMEM; s = ramdump_segs; for (i = 0; i < dump_data->nentries; i++) { s->address = dump_seg->address; s->v_address = dump_seg->v_address; s->size = dump_seg->size; s++; dump_seg++; } ret = create_ramdump_device_file(info_v2->ramdump_dev); if (ret) { kfree(ramdump_segs); return ret; } ret = do_elf_ramdump(info_v2->ramdump_dev, ramdump_segs, dump_data->nentries); kfree(ramdump_segs); cnss_pci_set_mhi_state(plat_priv->bus_priv, CNSS_MHI_DEINIT); cnss_pci_clear_dump_info(plat_priv->bus_priv); return ret; } static int cnss_qca6174_ramdump(struct cnss_plat_data *plat_priv) { int ret = 0; struct cnss_ramdump_info *ramdump_info; struct ramdump_segment segment; ramdump_info = &plat_priv->ramdump_info; if (!ramdump_info->ramdump_size) return -EINVAL; memset(&segment, 0, sizeof(segment)); segment.v_address = ramdump_info->ramdump_va; segment.size = ramdump_info->ramdump_size; ret = do_ramdump(ramdump_info->ramdump_dev, &segment, 1); return ret; } static int cnss_ramdump(int enable, const struct subsys_desc *subsys_desc) { int ret = 0; struct cnss_plat_data *plat_priv = dev_get_drvdata(subsys_desc->dev); if (!plat_priv) { printk(KERN_ERR "plat_priv is NULL!\n"); return -ENODEV; } if (!enable) return 0; switch (plat_priv->device_id) { case QCA6174_DEVICE_ID: ret = cnss_qca6174_ramdump(plat_priv); break; case QCA6290_EMULATION_DEVICE_ID: case QCA6290_DEVICE_ID: ret = cnss_qca6290_ramdump(plat_priv); break; default: cnss_pr_err("Unknown device_id found: 0x%lx\n", plat_priv->device_id); ret = -ENODEV; } return ret; } void *cnss_get_virt_ramdump_mem(struct device *dev, unsigned long *size) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); struct cnss_ramdump_info *ramdump_info; if (!plat_priv) return NULL; ramdump_info = &plat_priv->ramdump_info; *size = ramdump_info->ramdump_size; return ramdump_info->ramdump_va; } EXPORT_SYMBOL(cnss_get_virt_ramdump_mem); void cnss_device_crashed(struct device *dev) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); struct cnss_subsys_info *subsys_info; if (!plat_priv) return; subsys_info = &plat_priv->subsys_info; if (subsys_info->subsys_device) { set_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); subsys_set_crash_status(subsys_info->subsys_device, true); subsystem_restart_dev(subsys_info->subsys_device); } } EXPORT_SYMBOL(cnss_device_crashed); static void cnss_crash_shutdown(const struct subsys_desc *subsys_desc) { struct cnss_plat_data *plat_priv = dev_get_drvdata(subsys_desc->dev); if (!plat_priv) { printk(KERN_ERR "plat_priv is NULL!\n"); return; } switch (plat_priv->device_id) { case QCA6174_DEVICE_ID: cnss_qca6174_crash_shutdown(plat_priv); break; case QCA6290_EMULATION_DEVICE_ID: case QCA6290_DEVICE_ID: cnss_qca6290_crash_shutdown(plat_priv); break; default: cnss_pr_err("Unknown device_id found: 0x%lx\n", plat_priv->device_id); } } static const char *cnss_recovery_reason_to_str(enum cnss_recovery_reason reason) { switch (reason) { case CNSS_REASON_DEFAULT: return "DEFAULT"; case CNSS_REASON_LINK_DOWN: return "LINK_DOWN"; case CNSS_REASON_RDDM: return "RDDM"; case CNSS_REASON_TIMEOUT: return "TIMEOUT"; } return "UNKNOWN"; }; static int cnss_do_recovery(struct cnss_plat_data *plat_priv, enum cnss_recovery_reason reason) { struct cnss_pci_data *pci_priv = plat_priv->bus_priv; struct cnss_subsys_info *subsys_info = &plat_priv->subsys_info; int ret = 0; plat_priv->recovery_count++; if (plat_priv->device_id == QCA6174_DEVICE_ID) goto self_recovery; if (plat_priv->driver_ops && test_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state)) plat_priv->driver_ops->update_status(pci_priv->pci_dev, CNSS_RECOVERY, pci_priv->pci_device_id); switch (reason) { case CNSS_REASON_LINK_DOWN: if (test_bit(LINK_DOWN_SELF_RECOVERY, &quirks)) goto self_recovery; break; case CNSS_REASON_RDDM: clear_bit(CNSS_DEV_ERR_NOTIFY, &plat_priv->driver_state); ret = cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_RDDM); if (ret) { cnss_pr_err("Failed to complete RDDM, err = %d\n", ret); break; } cnss_pci_collect_dump_info(pci_priv); break; case CNSS_REASON_DEFAULT: case CNSS_REASON_TIMEOUT: break; default: cnss_pr_err("Unsupported recovery reason: %s(%d)\n", cnss_recovery_reason_to_str(reason), reason); break; } cnss_qca6290_ramdump(plat_priv); if (!subsys_info->subsys_device) return 0; subsys_set_crash_status(subsys_info->subsys_device, true); subsystem_restart_dev(subsys_info->subsys_device); return 0; self_recovery: cnss_shutdown(&subsys_info->subsys_desc, false); cnss_powerup(plat_priv); return 0; } static int cnss_driver_recovery_hdlr(struct cnss_plat_data *plat_priv, void *data) { struct cnss_recovery_data *recovery_data = data; int ret = 0; cnss_pr_dbg("Driver recovery is triggered with reason: %s(%d)\n", cnss_recovery_reason_to_str(recovery_data->reason), recovery_data->reason); if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) { cnss_pr_err("Recovery is already in progress!\n"); ret = -EINVAL; goto out; } if (test_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state)) { cnss_pr_err("Driver unload is in progress, ignore recovery\n"); ret = -EINVAL; goto out; } switch (plat_priv->device_id) { case QCA6174_DEVICE_ID: if (test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state)) { cnss_pr_err("Driver load is in progress, ignore recovery\n"); ret = -EINVAL; goto out; } break; default: if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) { set_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state); } else if (test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state)) { cnss_pr_err("Driver probe is in progress, ignore recovery\n"); ret = -EINVAL; goto out; } break; } set_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); ret = cnss_do_recovery(plat_priv, recovery_data->reason); out: kfree(data); return ret; } int cnss_self_recovery(struct device *dev, enum cnss_recovery_reason reason) { cnss_schedule_recovery(dev, reason); return 0; } EXPORT_SYMBOL(cnss_self_recovery); void cnss_schedule_recovery(struct device *dev, enum cnss_recovery_reason reason) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); struct cnss_recovery_data *data; int gfp = GFP_KERNEL; if (in_interrupt() || irqs_disabled()) gfp = GFP_ATOMIC; data = kzalloc(sizeof(*data), gfp); if (!data) return; data->reason = reason; cnss_driver_event_post(plat_priv, CNSS_DRIVER_EVENT_RECOVERY, false, data); } EXPORT_SYMBOL(cnss_schedule_recovery); static int cnss_force_fw_assert_hdlr(struct cnss_plat_data *plat_priv) { struct cnss_pci_data *pci_priv = plat_priv->bus_priv; int ret; ret = cnss_pci_set_mhi_state(plat_priv->bus_priv, CNSS_MHI_TRIGGER_RDDM); if (ret) { cnss_pr_err("Failed to trigger RDDM, err = %d\n", ret); cnss_schedule_recovery(&pci_priv->pci_dev->dev, CNSS_REASON_DEFAULT); return 0; } if (!test_bit(CNSS_DEV_ERR_NOTIFY, &plat_priv->driver_state)) { mod_timer(&plat_priv->fw_boot_timer, jiffies + msecs_to_jiffies(FW_ASSERT_TIMEOUT)); } return 0; } int cnss_force_fw_assert(struct device *dev) { struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); if (!plat_priv) { printk(KERN_ERR "plat_priv is NULL\n"); return -ENODEV; } if (plat_priv->device_id == QCA6174_DEVICE_ID) { cnss_pr_info("Forced FW assert is not supported\n"); return -EOPNOTSUPP; } if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) { cnss_pr_info("Recovery is already in progress, ignore forced FW assert\n"); return 0; } cnss_driver_event_post(plat_priv, CNSS_DRIVER_EVENT_FORCE_FW_ASSERT, false, NULL); return 0; } EXPORT_SYMBOL(cnss_force_fw_assert); void fw_boot_timeout(unsigned long data) { struct cnss_plat_data *plat_priv = (struct cnss_plat_data *)data; struct cnss_pci_data *pci_priv = plat_priv->bus_priv; printk(KERN_ERR "Timeout waiting for FW ready indication!\n"); cnss_schedule_recovery(&pci_priv->pci_dev->dev, CNSS_REASON_TIMEOUT); } static int cnss_register_driver_hdlr(struct cnss_plat_data *plat_priv, void *data) { int ret = 0; struct cnss_subsys_info *subsys_info = &plat_priv->subsys_info; set_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state); plat_priv->driver_ops = data; ret = cnss_powerup(plat_priv); if (ret) { clear_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state); plat_priv->driver_ops = NULL; } return ret; } static int cnss_unregister_driver_hdlr(struct cnss_plat_data *plat_priv) { struct cnss_subsys_info *subsys_info = &plat_priv->subsys_info; set_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state); cnss_shutdown(&subsys_info->subsys_desc, false); plat_priv->driver_ops = NULL; return 0; } static int cnss_cold_boot_cal_start_hdlr(struct cnss_plat_data *plat_priv) { int ret = 0; struct cnss_subsys_info *subsys_info = &plat_priv->subsys_info; set_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state); ret = cnss_powerup(plat_priv); if (ret) clear_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state); return ret; } static int cnss_cold_boot_cal_done_hdlr(struct cnss_plat_data *plat_priv) { plat_priv->cal_done = true; clear_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state); return 0; } static int cnss_power_up_hdlr(struct cnss_plat_data *plat_priv) { struct cnss_subsys_info *subsys_info = &plat_priv->subsys_info; return cnss_powerup(plat_priv); } static int cnss_power_down_hdlr(struct cnss_plat_data *plat_priv) { struct cnss_subsys_info *subsys_info = &plat_priv->subsys_info; cnss_shutdown(&subsys_info->subsys_desc, false); return 0; } static int cnss_qdss_trace_req_mem_hdlr(struct cnss_plat_data *plat_priv) { int ret = 0; ret = cnss_bus_alloc_qdss_mem(plat_priv); if (ret < 0) return ret; return cnss_wlfw_qdss_trace_mem_info_send_sync(plat_priv); } static void *cnss_qdss_trace_pa_to_va(struct cnss_plat_data *plat_priv, u64 pa, u32 size, int *seg_id) { int i = 0; struct cnss_fw_mem *qdss_mem = plat_priv->qdss_mem; u64 offset = 0; void *va = NULL; u64 local_pa; u32 local_size; for (i = 0; i < plat_priv->qdss_mem_seg_len; i++) { local_pa = (u64)qdss_mem[i].pa; local_size = (u32)qdss_mem[i].size; if (pa == local_pa && size <= local_size) { va = qdss_mem[i].va; break; } if (pa > local_pa && pa < local_pa + local_size && pa + size <= local_pa + local_size) { offset = pa - local_pa; va = qdss_mem[i].va + offset; break; } } *seg_id = i; return va; } static int cnss_qdss_trace_save_hdlr(struct cnss_plat_data *plat_priv, void *data) { struct cnss_qmi_event_qdss_trace_save_data *event_data = data; struct cnss_fw_mem *qdss_mem = plat_priv->qdss_mem; int ret = 0; int i; void *va = NULL; u64 pa; u32 size; int seg_id = 0; if (!plat_priv->qdss_mem_seg_len) { cnss_pr_err("Memory for QDSS trace is not available\n"); return -ENOMEM; } if (event_data->mem_seg_len == 0) { for (i = 0; i < plat_priv->qdss_mem_seg_len; i++) { ret = cnss_genl_send_msg(qdss_mem[i].va, CNSS_GENL_MSG_TYPE_QDSS, event_data->file_name, qdss_mem[i].size); if (ret < 0) { cnss_pr_err("Fail to save QDSS data: %d\n", ret); break; } } } else { for (i = 0; i < event_data->mem_seg_len; i++) { pa = event_data->mem_seg[i].addr; size = event_data->mem_seg[i].size; va = cnss_qdss_trace_pa_to_va(plat_priv, pa, size, &seg_id); if (!va) { cnss_pr_err("Fail to find matching va for pa %p\n", pa); ret = -EINVAL; break; } ret = cnss_genl_send_msg(va, CNSS_GENL_MSG_TYPE_QDSS, event_data->file_name, size); if (ret < 0) { cnss_pr_err("Fail to save QDSS data: %d\n", ret); break; } } } kfree(data); return ret; } static int cnss_qdss_trace_free_hdlr(struct cnss_plat_data *plat_priv) { cnss_bus_free_qdss_mem(plat_priv); return 0; } static void cnss_driver_event_work(struct work_struct *work) { struct cnss_plat_data *plat_priv = container_of(work, struct cnss_plat_data, event_work); struct cnss_driver_event *event; unsigned long flags; int ret = 0; if (!plat_priv) { printk(KERN_ERR "plat_priv is NULL!\n"); return; } spin_lock_irqsave(&plat_priv->event_lock, flags); while (!list_empty(&plat_priv->event_list)) { event = list_first_entry(&plat_priv->event_list, struct cnss_driver_event, list); list_del(&event->list); spin_unlock_irqrestore(&plat_priv->event_lock, flags); cnss_pr_dbg("Processing driver event: %s%s(%d), state: 0x%lx\n", cnss_driver_event_to_str(event->type), event->sync ? "-sync" : "", event->type, plat_priv->driver_state); switch (event->type) { case CNSS_DRIVER_EVENT_SERVER_ARRIVE: ret = cnss_wlfw_server_arrive(plat_priv); break; case CNSS_DRIVER_EVENT_SERVER_EXIT: ret = cnss_wlfw_server_exit(plat_priv); break; case CNSS_DRIVER_EVENT_REQUEST_MEM: ret = cnss_pci_alloc_fw_mem(plat_priv); if (ret) break; ret = cnss_wlfw_respond_mem_send_sync(plat_priv); break; case CNSS_DRIVER_EVENT_FW_MEM_READY: ret = cnss_fw_mem_ready_hdlr(plat_priv); break; case CNSS_DRIVER_EVENT_FW_READY: ret = cnss_fw_ready_hdlr(plat_priv); break; case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_START: ret = cnss_cold_boot_cal_start_hdlr(plat_priv); break; case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_DONE: ret = cnss_cold_boot_cal_done_hdlr(plat_priv); break; case CNSS_DRIVER_EVENT_REGISTER_DRIVER: ret = cnss_register_driver_hdlr(plat_priv, event->data); break; case CNSS_DRIVER_EVENT_UNREGISTER_DRIVER: ret = cnss_unregister_driver_hdlr(plat_priv); break; case CNSS_DRIVER_EVENT_RECOVERY: ret = cnss_driver_recovery_hdlr(plat_priv, event->data); break; case CNSS_DRIVER_EVENT_FORCE_FW_ASSERT: ret = cnss_force_fw_assert_hdlr(plat_priv); break; case CNSS_DRIVER_EVENT_POWER_UP: ret = cnss_power_up_hdlr(plat_priv); break; case CNSS_DRIVER_EVENT_POWER_DOWN: ret = cnss_power_down_hdlr(plat_priv); break; case CNSS_DRIVER_EVENT_QDSS_TRACE_REQ_MEM: ret = cnss_qdss_trace_req_mem_hdlr(plat_priv); break; case CNSS_DRIVER_EVENT_QDSS_TRACE_SAVE: ret = cnss_qdss_trace_save_hdlr(plat_priv, event->data); break; case CNSS_DRIVER_EVENT_QDSS_TRACE_FREE: ret = cnss_qdss_trace_free_hdlr(plat_priv); break; default: cnss_pr_err("Invalid driver event type: %d", event->type); kfree(event); spin_lock_irqsave(&plat_priv->event_lock, flags); continue; } spin_lock_irqsave(&plat_priv->event_lock, flags); if (event->sync) { event->ret = ret; complete(&event->complete); continue; } spin_unlock_irqrestore(&plat_priv->event_lock, flags); kfree(event); spin_lock_irqsave(&plat_priv->event_lock, flags); } spin_unlock_irqrestore(&plat_priv->event_lock, flags); } int cnss_register_subsys(struct cnss_plat_data *plat_priv) { int ret = 0; struct cnss_subsys_info *subsys_info; subsys_info = &plat_priv->subsys_info; switch (plat_priv->device_id) { case QCA6174_DEVICE_ID: subsys_info->subsys_desc.name = "AR6320"; break; case QCA6290_EMULATION_DEVICE_ID: case QCA6290_DEVICE_ID: subsys_info->subsys_desc.name = "QCA6290"; break; case QCA8074_DEVICE_ID: case QCA8074V2_DEVICE_ID: case QCA6018_DEVICE_ID: subsys_info->subsys_desc.name = "q6v5-wcss"; return 0; default: cnss_pr_err("Unknown device ID: 0x%lx\n", plat_priv->device_id); ret = -ENODEV; goto out; } subsys_info->subsys_desc.owner = THIS_MODULE; subsys_info->subsys_desc.powerup = cnss_powerup; subsys_info->subsys_desc.shutdown = cnss_shutdown; subsys_info->subsys_desc.ramdump = cnss_ramdump; subsys_info->subsys_desc.crash_shutdown = cnss_crash_shutdown; subsys_info->subsys_desc.dev = &plat_priv->plat_dev->dev; subsys_info->subsys_device = subsys_register(&subsys_info->subsys_desc); if (IS_ERR(subsys_info->subsys_device)) { ret = PTR_ERR(subsys_info->subsys_device); cnss_pr_err("Failed to register subsys, err = %d\n", ret); goto out; } subsys_info->subsys_handle = subsystem_get(subsys_info->subsys_desc.name); if (!subsys_info->subsys_handle) { cnss_pr_err("Failed to get subsys_handle!\n"); ret = -EINVAL; goto unregister_subsys; } else if (IS_ERR(subsys_info->subsys_handle)) { ret = PTR_ERR(subsys_info->subsys_handle); cnss_pr_err("Failed to do subsystem_get, err = %d\n", ret); goto unregister_subsys; } return 0; unregister_subsys: subsys_unregister(subsys_info->subsys_device); out: return ret; } void cnss_unregister_subsys(struct cnss_plat_data *plat_priv) { struct cnss_subsys_info *subsys_info; if (plat_priv->device_id == QCA8074_DEVICE_ID || plat_priv->device_id == QCA8074V2_DEVICE_ID || plat_priv->device_id == QCA6018_DEVICE_ID) return; subsys_info = &plat_priv->subsys_info; if (subsys_info->subsys_handle) subsystem_put(subsys_info->subsys_handle); subsys_info->subsys_handle = NULL; subsys_unregister(subsys_info->subsys_device); subsys_info->subsys_device = NULL; } static int cnss_init_dump_entry(struct cnss_plat_data *plat_priv) { struct cnss_ramdump_info *ramdump_info; struct msm_dump_entry dump_entry; ramdump_info = &plat_priv->ramdump_info; ramdump_info->dump_data.addr = ramdump_info->ramdump_pa; ramdump_info->dump_data.len = ramdump_info->ramdump_size; ramdump_info->dump_data.version = CNSS_DUMP_FORMAT_VER; ramdump_info->dump_data.magic = CNSS_DUMP_MAGIC_VER_V2; strlcpy(ramdump_info->dump_data.name, CNSS_DUMP_NAME, sizeof(ramdump_info->dump_data.name)); dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN; dump_entry.addr = virt_to_phys(&ramdump_info->dump_data); return msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); } static int cnss_qca6174_register_ramdump(struct cnss_plat_data *plat_priv) { int ret = 0; struct device *dev; struct cnss_subsys_info *subsys_info; struct cnss_ramdump_info *ramdump_info; u32 ramdump_size = 0; dev = &plat_priv->plat_dev->dev; subsys_info = &plat_priv->subsys_info; ramdump_info = &plat_priv->ramdump_info; if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic", &ramdump_size) == 0) { ramdump_info->ramdump_va = dma_alloc_coherent(dev, ramdump_size, &ramdump_info->ramdump_pa, GFP_KERNEL); if (ramdump_info->ramdump_va) ramdump_info->ramdump_size = ramdump_size; } cnss_pr_dbg("ramdump va: %pK, pa: %pa\n", ramdump_info->ramdump_va, &ramdump_info->ramdump_pa); if (ramdump_info->ramdump_size == 0) { cnss_pr_info("Ramdump will not be collected"); goto out; } ret = cnss_init_dump_entry(plat_priv); if (ret < 0) { cnss_pr_err("Failed to setup dump table, err = %d\n", ret); goto free_ramdump; } ramdump_info->ramdump_dev = create_ramdump_device( subsys_info->subsys_desc.name, subsys_info->subsys_desc.dev); if (!ramdump_info->ramdump_dev) { cnss_pr_err("Failed to create ramdump device!"); ret = -ENOMEM; goto free_ramdump; } return 0; free_ramdump: dma_free_coherent(dev, ramdump_info->ramdump_size, ramdump_info->ramdump_va, ramdump_info->ramdump_pa); out: return ret; } static void cnss_qca6174_unregister_ramdump(struct cnss_plat_data *plat_priv) { struct device *dev; struct cnss_ramdump_info *ramdump_info; dev = &plat_priv->plat_dev->dev; ramdump_info = &plat_priv->ramdump_info; if (ramdump_info->ramdump_dev) destroy_ramdump_device(ramdump_info->ramdump_dev); if (ramdump_info->ramdump_va) dma_free_coherent(dev, ramdump_info->ramdump_size, ramdump_info->ramdump_va, ramdump_info->ramdump_pa); } static int cnss_qca6290_register_ramdump(struct cnss_plat_data *plat_priv) { int ret = 0; struct cnss_subsys_info *subsys_info; struct cnss_ramdump_info_v2 *info_v2; struct cnss_dump_data *dump_data; struct msm_dump_entry dump_entry; struct device *dev = &plat_priv->plat_dev->dev; u32 ramdump_size = 0; subsys_info = &plat_priv->subsys_info; info_v2 = &plat_priv->ramdump_info_v2; dump_data = &info_v2->dump_data; #ifdef CONFIG_CNSS2_RAMDUMP if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic", &ramdump_size) == 0) info_v2->ramdump_size = ramdump_size; #endif info_v2->ramdump_size = 0; cnss_pr_dbg("Ramdump size 0x%lx\n", info_v2->ramdump_size); info_v2->dump_data_vaddr = kzalloc(CNSS_DUMP_DESC_SIZE, GFP_KERNEL); if (!info_v2->dump_data_vaddr) return -ENOMEM; dump_data->paddr = virt_to_phys(info_v2->dump_data_vaddr); dump_data->version = CNSS_DUMP_FORMAT_VER_V2; dump_data->magic = CNSS_DUMP_MAGIC_VER_V2; dump_data->seg_version = CNSS_DUMP_SEG_VER; strlcpy(dump_data->name, CNSS_DUMP_NAME, sizeof(dump_data->name)); dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN; dump_entry.addr = virt_to_phys(dump_data); ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); if (ret < 0) { cnss_pr_err("Failed to setup dump table, err = %d\n", ret); goto free_ramdump; } plat_priv->table_index = ret; info_v2->ramdump_dev = create_ramdump_device(subsys_info->subsys_desc.name, subsys_info->subsys_desc.dev); if (!info_v2->ramdump_dev) { cnss_pr_err("Failed to create ramdump device!\n"); ret = -ENOMEM; goto free_ramdump; } return 0; free_ramdump: kfree(info_v2->dump_data_vaddr); info_v2->dump_data_vaddr = NULL; return ret; } static void cnss_qca6290_unregister_ramdump(struct cnss_plat_data *plat_priv) { int ret; struct cnss_ramdump_info_v2 *info_v2; info_v2 = &plat_priv->ramdump_info_v2; if (info_v2->ramdump_dev) destroy_ramdump_device(info_v2->ramdump_dev); ret = msm_dump_data_unregister(MSM_DUMP_TABLE_APPS, plat_priv->table_index); if (!ret) cnss_pr_err("Failed to unregister dump table, err = %d\n", ret); plat_priv->table_index = -1; kfree(info_v2->dump_data_vaddr); info_v2->dump_data_vaddr = NULL; info_v2->dump_data_valid = false; } int cnss_register_ramdump(struct cnss_plat_data *plat_priv) { int ret = 0; switch (plat_priv->device_id) { case QCA6174_DEVICE_ID: ret = cnss_qca6174_register_ramdump(plat_priv); break; case QCA6290_EMULATION_DEVICE_ID: case QCA6290_DEVICE_ID: ret = cnss_qca6290_register_ramdump(plat_priv); break; default: cnss_pr_err("Unknown device ID: 0x%lx\n", plat_priv->device_id); ret = -ENODEV; break; } return ret; } void cnss_unregister_ramdump(struct cnss_plat_data *plat_priv) { switch (plat_priv->device_id) { case QCA6174_DEVICE_ID: cnss_qca6174_unregister_ramdump(plat_priv); break; case QCA6290_EMULATION_DEVICE_ID: case QCA6290_DEVICE_ID: cnss_qca6290_unregister_ramdump(plat_priv); break; default: cnss_pr_err("Unknown device ID: 0x%lx\n", plat_priv->device_id); break; } } static int cnss_register_bus_scale(struct cnss_plat_data *plat_priv) { int ret = 0; struct cnss_bus_bw_info *bus_bw_info; bus_bw_info = &plat_priv->bus_bw_info; bus_bw_info->bus_scale_table = msm_bus_cl_get_pdata(plat_priv->plat_dev); if (bus_bw_info->bus_scale_table) { bus_bw_info->bus_client = msm_bus_scale_register_client( bus_bw_info->bus_scale_table); if (!bus_bw_info->bus_client) { cnss_pr_err("Failed to register bus scale client!\n"); ret = -EINVAL; goto out; } } return 0; out: return ret; } static void cnss_unregister_bus_scale(struct cnss_plat_data *plat_priv) { struct cnss_bus_bw_info *bus_bw_info; bus_bw_info = &plat_priv->bus_bw_info; if (bus_bw_info->bus_client) msm_bus_scale_unregister_client(bus_bw_info->bus_client); } static ssize_t cnss_fs_ready_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int fs_ready = 0; struct cnss_plat_data *plat_priv = dev_get_drvdata(dev); if (sscanf(buf, "%du", &fs_ready) != 1) return -EINVAL; cnss_pr_dbg("File system is ready, fs_ready is %d, count is %zu\n", fs_ready, count); if (qmi_bypass) { printk(KERN_INFO "QMI is bypassed.\n"); return count; } if (!plat_priv) { printk(KERN_ERR "plat_priv is NULL!\n"); return count; } switch (plat_priv->device_id) { case QCA6290_EMULATION_DEVICE_ID: case QCA6290_DEVICE_ID: break; default: cnss_pr_err("Not supported for device ID 0x%lx\n", plat_priv->device_id); return count; } if (fs_ready == FILE_SYSTEM_READY) { cnss_driver_event_post(plat_priv, CNSS_DRIVER_EVENT_COLD_BOOT_CAL_START, true, NULL); } return count; } static DEVICE_ATTR(fs_ready, 0220, NULL, cnss_fs_ready_store); static int cnss_create_sysfs(struct cnss_plat_data *plat_priv) { int ret = 0; ret = device_create_file(&plat_priv->plat_dev->dev, &dev_attr_fs_ready); if (ret) { cnss_pr_err("Failed to create device file, err = %d\n", ret); goto out; } return 0; out: return ret; } static void cnss_remove_sysfs(struct cnss_plat_data *plat_priv) { device_remove_file(&plat_priv->plat_dev->dev, &dev_attr_fs_ready); } static int cnss_event_work_init(struct cnss_plat_data *plat_priv) { spin_lock_init(&plat_priv->event_lock); plat_priv->event_wq = alloc_workqueue("cnss_driver_event", WQ_UNBOUND, 1); if (!plat_priv->event_wq) { cnss_pr_err("Failed to create event workqueue!\n"); return -EFAULT; } plat_priv->qmi_resp_wq = alloc_workqueue("qmi_resp_wq", WQ_UNBOUND, 1); if (!plat_priv->qmi_resp_wq) { cnss_pr_err("Failed to create qmi_resp_wq workqueue!\n"); goto free_wq; } INIT_WORK(&plat_priv->event_work, cnss_driver_event_work); INIT_LIST_HEAD(&plat_priv->event_list); return 0; free_wq: destroy_workqueue(plat_priv->event_wq); return -EFAULT; } static void cnss_event_work_deinit(struct cnss_plat_data *plat_priv) { if (plat_priv->event_wq) destroy_workqueue(plat_priv->event_wq); if (plat_priv->qmi_resp_wq) destroy_workqueue(plat_priv->qmi_resp_wq); } static const struct platform_device_id cnss_platform_id_table[] = { { .name = "qca6174", .driver_data = QCA6174_DEVICE_ID, }, { .name = "qca6290", .driver_data = QCA6290_DEVICE_ID, }, { .name = "qca8074", .driver_data = QCA8074_DEVICE_ID, }, { .name = "qca8074v2", .driver_data = QCA8074V2_DEVICE_ID, }, { .name = "qca6018", .driver_data = QCA6018_DEVICE_ID, }, }; static const struct of_device_id cnss_of_match_table[] = { { .compatible = "qcom,cnss", .data = (void *)&cnss_platform_id_table[0]}, { .compatible = "qcom,cnss-qca6290", .data = (void *)&cnss_platform_id_table[1]}, { .compatible = "qcom,cnss-qca8074", .data = (void *)&cnss_platform_id_table[2]}, { .compatible = "qcom,cnss-qca8074v2", .data = (void *)&cnss_platform_id_table[3]}, { .compatible = "qcom,cnss-qca6018", .data = (void *)&cnss_platform_id_table[4]}, { }, }; MODULE_DEVICE_TABLE(of, cnss_of_match_table); static int cnss_probe(struct platform_device *plat_dev) { int ret = 0; struct cnss_plat_data *plat_priv; const struct of_device_id *of_id; const struct platform_device_id *device_id; const int *soc_version_major; if (qca6290_support) cold_boot_support = false; if (skip_cnss) { printk(KERN_ERR "Skipping CNSS_PROBE\n"); return 0; } if (cnss_get_plat_priv(plat_dev)) { printk(KERN_ERR "Driver is already initialized!\n"); ret = -EEXIST; goto out; } of_id = of_match_device(cnss_of_match_table, &plat_dev->dev); if (!of_id || !of_id->data) { printk(KERN_ERR "Failed to find of match device!\n"); ret = -ENODEV; goto out; } device_id = of_id->data; #ifdef CONFIG_CNSS_QCA6290 if (device_id->driver_data == QCA6174_DEVICE_ID) { printk(KERN_ERR "No probe for QCA6174\n"); ret = -EINVAL; goto out; } #else if (device_id->driver_data == QCA6290_DEVICE_ID) { printk(KERN_ERR "No probe for QCA6290\n"); ret = -EINVAL; goto out; } #endif if (device_id->driver_data == QCA6018_DEVICE_ID) goto skip_soc_version_checks; soc_version_major = of_get_property(of_find_node_by_path("/"), "soc_version_major", NULL); BUG_ON(!soc_version_major); if (device_id->driver_data == QCA8074_DEVICE_ID) { if (*soc_version_major == 2) { pr_err("Skip QCA8074V1 in V2 platform\n"); ret = -ENODEV; goto out; } } if (device_id->driver_data == QCA8074V2_DEVICE_ID) { if (*soc_version_major == 1) { pr_err("Skip QCA8074V2 in V1 platform\n"); ret = -ENODEV; goto out; } } skip_soc_version_checks: plat_priv = devm_kzalloc(&plat_dev->dev, sizeof(*plat_priv), GFP_KERNEL); if (!plat_priv) { ret = -ENOMEM; goto out; } plat_priv->plat_dev = plat_dev; plat_priv->device_id = device_id->driver_data; plat_priv->plat_dev_id = device_id; switch(plat_priv->device_id) { case QCA6290_DEVICE_ID: plat_priv->wlfw_service_instance_id = WLFW_SERVICE_INS_ID_V01_QCA6290; plat_priv->service_id = WLFW_SERVICE_ID_V01_NPR; break; case QCA8074_DEVICE_ID: case QCA8074V2_DEVICE_ID: case QCA6018_DEVICE_ID: plat_priv->wlfw_service_instance_id = WLFW_SERVICE_INS_ID_V01_QCA8074; plat_priv->service_id = WLFW_SERVICE_ID_V01_HK; break; default: cnss_pr_err("No such device id %d\n",device_id); return -ENODEV; } cnss_set_plat_priv(plat_dev, plat_priv); platform_set_drvdata(plat_dev, plat_priv); memset(&qmi_log, 0, sizeof(struct qmi_history) * QMI_HISTORY_SIZE); ret = cnss_get_resources(plat_priv); if (ret) goto reset_ctx; if (plat_priv->device_id == QCA6290_DEVICE_ID) { ret = cnss_pci_init(plat_priv); if (ret) goto power_off; } #ifdef CONFIG_CNSS2_PM ret = cnss_register_esoc(plat_priv); if (ret) goto deinit_pci; ret = cnss_register_bus_scale(plat_priv); if (ret) goto unreg_esoc; #endif ret = cnss_create_sysfs(plat_priv); if (ret) goto unreg_bus_scale; ret = cnss_event_work_init(plat_priv); if (ret) goto remove_sysfs; ret = cnss_qmi_init(plat_priv); if (ret) goto deinit_event_work; ret = cnss_debugfs_create(plat_priv); if (ret) goto deinit_qmi; setup_timer(&plat_priv->fw_boot_timer, fw_boot_timeout, (unsigned long)plat_priv); #ifdef CONFIG_CNSS2_PM register_pm_notifier(&cnss_pm_notifier); #endif init_completion(&plat_priv->power_up_complete); ret = cnss_genl_init(); if (ret < 0) cnss_pr_err("CNSS genl init failed %d\n", ret); cnss_pr_info("Platform driver probed successfully.\n"); return 0; deinit_qmi: cnss_qmi_deinit(plat_priv); deinit_event_work: cnss_event_work_deinit(plat_priv); remove_sysfs: cnss_remove_sysfs(plat_priv); unreg_bus_scale: cnss_unregister_bus_scale(plat_priv); unreg_esoc: cnss_unregister_esoc(plat_priv); deinit_pci: if (!test_bit(SKIP_DEVICE_BOOT, &quirks)) cnss_pci_deinit(plat_priv); power_off: if (!test_bit(SKIP_DEVICE_BOOT, &quirks)) cnss_power_off_device(plat_priv, 0); free_res: cnss_put_resources(plat_priv); reset_ctx: platform_set_drvdata(plat_dev, NULL); cnss_set_plat_priv(plat_dev, NULL); out: return ret; } static int cnss_remove(struct platform_device *plat_dev) { struct cnss_plat_data *plat_priv = platform_get_drvdata(plat_dev); complete_all(&plat_priv->power_up_complete); #ifdef CONFIG_CNSS2_PM unregister_pm_notifier(&cnss_pm_notifier); #endif cnss_genl_exit(); del_timer(&plat_priv->fw_boot_timer); cnss_debugfs_destroy(plat_priv); cnss_qmi_deinit(plat_priv); cnss_event_work_deinit(plat_priv); cnss_remove_sysfs(plat_priv); cnss_unregister_bus_scale(plat_priv); cnss_unregister_esoc(plat_priv); cnss_pci_deinit(plat_priv); cnss_put_resources(plat_priv); platform_set_drvdata(plat_dev, NULL); return 0; } static struct platform_driver cnss_platform_driver = { .probe = cnss_probe, .remove = cnss_remove, .driver = { .name = "cnss2", .owner = THIS_MODULE, .of_match_table = cnss_of_match_table, }, }; static int __init cnss_initialize(void) { int ret = 0; cnss_debug_init(); ret = platform_driver_register(&cnss_platform_driver); if (ret) cnss_debug_deinit(); return ret; } static void __exit cnss_exit(void) { platform_driver_unregister(&cnss_platform_driver); cnss_debug_deinit(); } module_init(cnss_initialize); module_exit(cnss_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("CNSS2 Platform Driver");