18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Qualcomm Wireless Connectivity Subsystem Peripheral Image Loader 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2016 Linaro Ltd 68c2ecf20Sopenharmony_ci * Copyright (C) 2014 Sony Mobile Communications AB 78c2ecf20Sopenharmony_ci * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/clk.h> 118c2ecf20Sopenharmony_ci#include <linux/delay.h> 128c2ecf20Sopenharmony_ci#include <linux/firmware.h> 138c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 148c2ecf20Sopenharmony_ci#include <linux/kernel.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/io.h> 178c2ecf20Sopenharmony_ci#include <linux/of_address.h> 188c2ecf20Sopenharmony_ci#include <linux/of_device.h> 198c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 208c2ecf20Sopenharmony_ci#include <linux/qcom_scm.h> 218c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h> 228c2ecf20Sopenharmony_ci#include <linux/remoteproc.h> 238c2ecf20Sopenharmony_ci#include <linux/soc/qcom/mdt_loader.h> 248c2ecf20Sopenharmony_ci#include <linux/soc/qcom/smem.h> 258c2ecf20Sopenharmony_ci#include <linux/soc/qcom/smem_state.h> 268c2ecf20Sopenharmony_ci#include <linux/rpmsg/qcom_smd.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#include "qcom_common.h" 298c2ecf20Sopenharmony_ci#include "remoteproc_internal.h" 308c2ecf20Sopenharmony_ci#include "qcom_pil_info.h" 318c2ecf20Sopenharmony_ci#include "qcom_wcnss.h" 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define WCNSS_CRASH_REASON_SMEM 422 348c2ecf20Sopenharmony_ci#define WCNSS_FIRMWARE_NAME "wcnss.mdt" 358c2ecf20Sopenharmony_ci#define WCNSS_PAS_ID 6 368c2ecf20Sopenharmony_ci#define WCNSS_SSCTL_ID 0x13 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci#define WCNSS_SPARE_NVBIN_DLND BIT(25) 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#define WCNSS_PMU_IRIS_XO_CFG BIT(3) 418c2ecf20Sopenharmony_ci#define WCNSS_PMU_IRIS_XO_EN BIT(4) 428c2ecf20Sopenharmony_ci#define WCNSS_PMU_GC_BUS_MUX_SEL_TOP BIT(5) 438c2ecf20Sopenharmony_ci#define WCNSS_PMU_IRIS_XO_CFG_STS BIT(6) /* 1: in progress, 0: done */ 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci#define WCNSS_PMU_IRIS_RESET BIT(7) 468c2ecf20Sopenharmony_ci#define WCNSS_PMU_IRIS_RESET_STS BIT(8) /* 1: in progress, 0: done */ 478c2ecf20Sopenharmony_ci#define WCNSS_PMU_IRIS_XO_READ BIT(9) 488c2ecf20Sopenharmony_ci#define WCNSS_PMU_IRIS_XO_READ_STS BIT(10) 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci#define WCNSS_PMU_XO_MODE_MASK GENMASK(2, 1) 518c2ecf20Sopenharmony_ci#define WCNSS_PMU_XO_MODE_19p2 0 528c2ecf20Sopenharmony_ci#define WCNSS_PMU_XO_MODE_48 3 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistruct wcnss_data { 558c2ecf20Sopenharmony_ci size_t pmu_offset; 568c2ecf20Sopenharmony_ci size_t spare_offset; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci const struct wcnss_vreg_info *vregs; 598c2ecf20Sopenharmony_ci size_t num_vregs; 608c2ecf20Sopenharmony_ci}; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistruct qcom_wcnss { 638c2ecf20Sopenharmony_ci struct device *dev; 648c2ecf20Sopenharmony_ci struct rproc *rproc; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci void __iomem *pmu_cfg; 678c2ecf20Sopenharmony_ci void __iomem *spare_out; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci bool use_48mhz_xo; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci int wdog_irq; 728c2ecf20Sopenharmony_ci int fatal_irq; 738c2ecf20Sopenharmony_ci int ready_irq; 748c2ecf20Sopenharmony_ci int handover_irq; 758c2ecf20Sopenharmony_ci int stop_ack_irq; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci struct qcom_smem_state *state; 788c2ecf20Sopenharmony_ci unsigned stop_bit; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci struct mutex iris_lock; 818c2ecf20Sopenharmony_ci struct qcom_iris *iris; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci struct regulator_bulk_data *vregs; 848c2ecf20Sopenharmony_ci size_t num_vregs; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci struct completion start_done; 878c2ecf20Sopenharmony_ci struct completion stop_done; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci phys_addr_t mem_phys; 908c2ecf20Sopenharmony_ci phys_addr_t mem_reloc; 918c2ecf20Sopenharmony_ci void *mem_region; 928c2ecf20Sopenharmony_ci size_t mem_size; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci struct qcom_rproc_subdev smd_subdev; 958c2ecf20Sopenharmony_ci struct qcom_sysmon *sysmon; 968c2ecf20Sopenharmony_ci}; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic const struct wcnss_data riva_data = { 998c2ecf20Sopenharmony_ci .pmu_offset = 0x28, 1008c2ecf20Sopenharmony_ci .spare_offset = 0xb4, 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci .vregs = (struct wcnss_vreg_info[]) { 1038c2ecf20Sopenharmony_ci { "vddmx", 1050000, 1150000, 0 }, 1048c2ecf20Sopenharmony_ci { "vddcx", 1050000, 1150000, 0 }, 1058c2ecf20Sopenharmony_ci { "vddpx", 1800000, 1800000, 0 }, 1068c2ecf20Sopenharmony_ci }, 1078c2ecf20Sopenharmony_ci .num_vregs = 3, 1088c2ecf20Sopenharmony_ci}; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic const struct wcnss_data pronto_v1_data = { 1118c2ecf20Sopenharmony_ci .pmu_offset = 0x1004, 1128c2ecf20Sopenharmony_ci .spare_offset = 0x1088, 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci .vregs = (struct wcnss_vreg_info[]) { 1158c2ecf20Sopenharmony_ci { "vddmx", 950000, 1150000, 0 }, 1168c2ecf20Sopenharmony_ci { "vddcx", .super_turbo = true}, 1178c2ecf20Sopenharmony_ci { "vddpx", 1800000, 1800000, 0 }, 1188c2ecf20Sopenharmony_ci }, 1198c2ecf20Sopenharmony_ci .num_vregs = 3, 1208c2ecf20Sopenharmony_ci}; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistatic const struct wcnss_data pronto_v2_data = { 1238c2ecf20Sopenharmony_ci .pmu_offset = 0x1004, 1248c2ecf20Sopenharmony_ci .spare_offset = 0x1088, 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci .vregs = (struct wcnss_vreg_info[]) { 1278c2ecf20Sopenharmony_ci { "vddmx", 1287500, 1287500, 0 }, 1288c2ecf20Sopenharmony_ci { "vddcx", .super_turbo = true }, 1298c2ecf20Sopenharmony_ci { "vddpx", 1800000, 1800000, 0 }, 1308c2ecf20Sopenharmony_ci }, 1318c2ecf20Sopenharmony_ci .num_vregs = 3, 1328c2ecf20Sopenharmony_ci}; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_civoid qcom_wcnss_assign_iris(struct qcom_wcnss *wcnss, 1358c2ecf20Sopenharmony_ci struct qcom_iris *iris, 1368c2ecf20Sopenharmony_ci bool use_48mhz_xo) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci mutex_lock(&wcnss->iris_lock); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci wcnss->iris = iris; 1418c2ecf20Sopenharmony_ci wcnss->use_48mhz_xo = use_48mhz_xo; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci mutex_unlock(&wcnss->iris_lock); 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic int wcnss_load(struct rproc *rproc, const struct firmware *fw) 1478c2ecf20Sopenharmony_ci{ 1488c2ecf20Sopenharmony_ci struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv; 1498c2ecf20Sopenharmony_ci int ret; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci ret = qcom_mdt_load(wcnss->dev, fw, rproc->firmware, WCNSS_PAS_ID, 1528c2ecf20Sopenharmony_ci wcnss->mem_region, wcnss->mem_phys, 1538c2ecf20Sopenharmony_ci wcnss->mem_size, &wcnss->mem_reloc); 1548c2ecf20Sopenharmony_ci if (ret) 1558c2ecf20Sopenharmony_ci return ret; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci qcom_pil_info_store("wcnss", wcnss->mem_phys, wcnss->mem_size); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci return 0; 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_cistatic void wcnss_indicate_nv_download(struct qcom_wcnss *wcnss) 1638c2ecf20Sopenharmony_ci{ 1648c2ecf20Sopenharmony_ci u32 val; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci /* Indicate NV download capability */ 1678c2ecf20Sopenharmony_ci val = readl(wcnss->spare_out); 1688c2ecf20Sopenharmony_ci val |= WCNSS_SPARE_NVBIN_DLND; 1698c2ecf20Sopenharmony_ci writel(val, wcnss->spare_out); 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic void wcnss_configure_iris(struct qcom_wcnss *wcnss) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci u32 val; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci /* Clear PMU cfg register */ 1778c2ecf20Sopenharmony_ci writel(0, wcnss->pmu_cfg); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci val = WCNSS_PMU_GC_BUS_MUX_SEL_TOP | WCNSS_PMU_IRIS_XO_EN; 1808c2ecf20Sopenharmony_ci writel(val, wcnss->pmu_cfg); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci /* Clear XO_MODE */ 1838c2ecf20Sopenharmony_ci val &= ~WCNSS_PMU_XO_MODE_MASK; 1848c2ecf20Sopenharmony_ci if (wcnss->use_48mhz_xo) 1858c2ecf20Sopenharmony_ci val |= WCNSS_PMU_XO_MODE_48 << 1; 1868c2ecf20Sopenharmony_ci else 1878c2ecf20Sopenharmony_ci val |= WCNSS_PMU_XO_MODE_19p2 << 1; 1888c2ecf20Sopenharmony_ci writel(val, wcnss->pmu_cfg); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci /* Reset IRIS */ 1918c2ecf20Sopenharmony_ci val |= WCNSS_PMU_IRIS_RESET; 1928c2ecf20Sopenharmony_ci writel(val, wcnss->pmu_cfg); 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci /* Wait for PMU.iris_reg_reset_sts */ 1958c2ecf20Sopenharmony_ci while (readl(wcnss->pmu_cfg) & WCNSS_PMU_IRIS_RESET_STS) 1968c2ecf20Sopenharmony_ci cpu_relax(); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci /* Clear IRIS reset */ 1998c2ecf20Sopenharmony_ci val &= ~WCNSS_PMU_IRIS_RESET; 2008c2ecf20Sopenharmony_ci writel(val, wcnss->pmu_cfg); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci /* Start IRIS XO configuration */ 2038c2ecf20Sopenharmony_ci val |= WCNSS_PMU_IRIS_XO_CFG; 2048c2ecf20Sopenharmony_ci writel(val, wcnss->pmu_cfg); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci /* Wait for XO configuration to finish */ 2078c2ecf20Sopenharmony_ci while (readl(wcnss->pmu_cfg) & WCNSS_PMU_IRIS_XO_CFG_STS) 2088c2ecf20Sopenharmony_ci cpu_relax(); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci /* Stop IRIS XO configuration */ 2118c2ecf20Sopenharmony_ci val &= ~WCNSS_PMU_GC_BUS_MUX_SEL_TOP; 2128c2ecf20Sopenharmony_ci val &= ~WCNSS_PMU_IRIS_XO_CFG; 2138c2ecf20Sopenharmony_ci writel(val, wcnss->pmu_cfg); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci /* Add some delay for XO to settle */ 2168c2ecf20Sopenharmony_ci msleep(20); 2178c2ecf20Sopenharmony_ci} 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cistatic int wcnss_start(struct rproc *rproc) 2208c2ecf20Sopenharmony_ci{ 2218c2ecf20Sopenharmony_ci struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv; 2228c2ecf20Sopenharmony_ci int ret; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci mutex_lock(&wcnss->iris_lock); 2258c2ecf20Sopenharmony_ci if (!wcnss->iris) { 2268c2ecf20Sopenharmony_ci dev_err(wcnss->dev, "no iris registered\n"); 2278c2ecf20Sopenharmony_ci ret = -EINVAL; 2288c2ecf20Sopenharmony_ci goto release_iris_lock; 2298c2ecf20Sopenharmony_ci } 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci ret = regulator_bulk_enable(wcnss->num_vregs, wcnss->vregs); 2328c2ecf20Sopenharmony_ci if (ret) 2338c2ecf20Sopenharmony_ci goto release_iris_lock; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci ret = qcom_iris_enable(wcnss->iris); 2368c2ecf20Sopenharmony_ci if (ret) 2378c2ecf20Sopenharmony_ci goto disable_regulators; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci wcnss_indicate_nv_download(wcnss); 2408c2ecf20Sopenharmony_ci wcnss_configure_iris(wcnss); 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci ret = qcom_scm_pas_auth_and_reset(WCNSS_PAS_ID); 2438c2ecf20Sopenharmony_ci if (ret) { 2448c2ecf20Sopenharmony_ci dev_err(wcnss->dev, 2458c2ecf20Sopenharmony_ci "failed to authenticate image and release reset\n"); 2468c2ecf20Sopenharmony_ci goto disable_iris; 2478c2ecf20Sopenharmony_ci } 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci ret = wait_for_completion_timeout(&wcnss->start_done, 2508c2ecf20Sopenharmony_ci msecs_to_jiffies(5000)); 2518c2ecf20Sopenharmony_ci if (wcnss->ready_irq > 0 && ret == 0) { 2528c2ecf20Sopenharmony_ci /* We have a ready_irq, but it didn't fire in time. */ 2538c2ecf20Sopenharmony_ci dev_err(wcnss->dev, "start timed out\n"); 2548c2ecf20Sopenharmony_ci qcom_scm_pas_shutdown(WCNSS_PAS_ID); 2558c2ecf20Sopenharmony_ci ret = -ETIMEDOUT; 2568c2ecf20Sopenharmony_ci goto disable_iris; 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci ret = 0; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_cidisable_iris: 2628c2ecf20Sopenharmony_ci qcom_iris_disable(wcnss->iris); 2638c2ecf20Sopenharmony_cidisable_regulators: 2648c2ecf20Sopenharmony_ci regulator_bulk_disable(wcnss->num_vregs, wcnss->vregs); 2658c2ecf20Sopenharmony_cirelease_iris_lock: 2668c2ecf20Sopenharmony_ci mutex_unlock(&wcnss->iris_lock); 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci return ret; 2698c2ecf20Sopenharmony_ci} 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_cistatic int wcnss_stop(struct rproc *rproc) 2728c2ecf20Sopenharmony_ci{ 2738c2ecf20Sopenharmony_ci struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv; 2748c2ecf20Sopenharmony_ci int ret; 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci if (wcnss->state) { 2778c2ecf20Sopenharmony_ci qcom_smem_state_update_bits(wcnss->state, 2788c2ecf20Sopenharmony_ci BIT(wcnss->stop_bit), 2798c2ecf20Sopenharmony_ci BIT(wcnss->stop_bit)); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci ret = wait_for_completion_timeout(&wcnss->stop_done, 2828c2ecf20Sopenharmony_ci msecs_to_jiffies(5000)); 2838c2ecf20Sopenharmony_ci if (ret == 0) 2848c2ecf20Sopenharmony_ci dev_err(wcnss->dev, "timed out on wait\n"); 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci qcom_smem_state_update_bits(wcnss->state, 2878c2ecf20Sopenharmony_ci BIT(wcnss->stop_bit), 2888c2ecf20Sopenharmony_ci 0); 2898c2ecf20Sopenharmony_ci } 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci ret = qcom_scm_pas_shutdown(WCNSS_PAS_ID); 2928c2ecf20Sopenharmony_ci if (ret) 2938c2ecf20Sopenharmony_ci dev_err(wcnss->dev, "failed to shutdown: %d\n", ret); 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci return ret; 2968c2ecf20Sopenharmony_ci} 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_cistatic void *wcnss_da_to_va(struct rproc *rproc, u64 da, size_t len) 2998c2ecf20Sopenharmony_ci{ 3008c2ecf20Sopenharmony_ci struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv; 3018c2ecf20Sopenharmony_ci int offset; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci offset = da - wcnss->mem_reloc; 3048c2ecf20Sopenharmony_ci if (offset < 0 || offset + len > wcnss->mem_size) 3058c2ecf20Sopenharmony_ci return NULL; 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci return wcnss->mem_region + offset; 3088c2ecf20Sopenharmony_ci} 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_cistatic const struct rproc_ops wcnss_ops = { 3118c2ecf20Sopenharmony_ci .start = wcnss_start, 3128c2ecf20Sopenharmony_ci .stop = wcnss_stop, 3138c2ecf20Sopenharmony_ci .da_to_va = wcnss_da_to_va, 3148c2ecf20Sopenharmony_ci .parse_fw = qcom_register_dump_segments, 3158c2ecf20Sopenharmony_ci .load = wcnss_load, 3168c2ecf20Sopenharmony_ci}; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_cistatic irqreturn_t wcnss_wdog_interrupt(int irq, void *dev) 3198c2ecf20Sopenharmony_ci{ 3208c2ecf20Sopenharmony_ci struct qcom_wcnss *wcnss = dev; 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci rproc_report_crash(wcnss->rproc, RPROC_WATCHDOG); 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ci return IRQ_HANDLED; 3258c2ecf20Sopenharmony_ci} 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_cistatic irqreturn_t wcnss_fatal_interrupt(int irq, void *dev) 3288c2ecf20Sopenharmony_ci{ 3298c2ecf20Sopenharmony_ci struct qcom_wcnss *wcnss = dev; 3308c2ecf20Sopenharmony_ci size_t len; 3318c2ecf20Sopenharmony_ci char *msg; 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, WCNSS_CRASH_REASON_SMEM, &len); 3348c2ecf20Sopenharmony_ci if (!IS_ERR(msg) && len > 0 && msg[0]) 3358c2ecf20Sopenharmony_ci dev_err(wcnss->dev, "fatal error received: %s\n", msg); 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci rproc_report_crash(wcnss->rproc, RPROC_FATAL_ERROR); 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci return IRQ_HANDLED; 3408c2ecf20Sopenharmony_ci} 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_cistatic irqreturn_t wcnss_ready_interrupt(int irq, void *dev) 3438c2ecf20Sopenharmony_ci{ 3448c2ecf20Sopenharmony_ci struct qcom_wcnss *wcnss = dev; 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_ci complete(&wcnss->start_done); 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci return IRQ_HANDLED; 3498c2ecf20Sopenharmony_ci} 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_cistatic irqreturn_t wcnss_handover_interrupt(int irq, void *dev) 3528c2ecf20Sopenharmony_ci{ 3538c2ecf20Sopenharmony_ci /* 3548c2ecf20Sopenharmony_ci * XXX: At this point we're supposed to release the resources that we 3558c2ecf20Sopenharmony_ci * have been holding on behalf of the WCNSS. Unfortunately this 3568c2ecf20Sopenharmony_ci * interrupt comes way before the other side seems to be done. 3578c2ecf20Sopenharmony_ci * 3588c2ecf20Sopenharmony_ci * So we're currently relying on the ready interrupt firing later then 3598c2ecf20Sopenharmony_ci * this and we just disable the resources at the end of wcnss_start(). 3608c2ecf20Sopenharmony_ci */ 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci return IRQ_HANDLED; 3638c2ecf20Sopenharmony_ci} 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_cistatic irqreturn_t wcnss_stop_ack_interrupt(int irq, void *dev) 3668c2ecf20Sopenharmony_ci{ 3678c2ecf20Sopenharmony_ci struct qcom_wcnss *wcnss = dev; 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_ci complete(&wcnss->stop_done); 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci return IRQ_HANDLED; 3728c2ecf20Sopenharmony_ci} 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_cistatic int wcnss_init_regulators(struct qcom_wcnss *wcnss, 3758c2ecf20Sopenharmony_ci const struct wcnss_vreg_info *info, 3768c2ecf20Sopenharmony_ci int num_vregs) 3778c2ecf20Sopenharmony_ci{ 3788c2ecf20Sopenharmony_ci struct regulator_bulk_data *bulk; 3798c2ecf20Sopenharmony_ci int ret; 3808c2ecf20Sopenharmony_ci int i; 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci bulk = devm_kcalloc(wcnss->dev, 3838c2ecf20Sopenharmony_ci num_vregs, sizeof(struct regulator_bulk_data), 3848c2ecf20Sopenharmony_ci GFP_KERNEL); 3858c2ecf20Sopenharmony_ci if (!bulk) 3868c2ecf20Sopenharmony_ci return -ENOMEM; 3878c2ecf20Sopenharmony_ci 3888c2ecf20Sopenharmony_ci for (i = 0; i < num_vregs; i++) 3898c2ecf20Sopenharmony_ci bulk[i].supply = info[i].name; 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci ret = devm_regulator_bulk_get(wcnss->dev, num_vregs, bulk); 3928c2ecf20Sopenharmony_ci if (ret) 3938c2ecf20Sopenharmony_ci return ret; 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci for (i = 0; i < num_vregs; i++) { 3968c2ecf20Sopenharmony_ci if (info[i].max_voltage) 3978c2ecf20Sopenharmony_ci regulator_set_voltage(bulk[i].consumer, 3988c2ecf20Sopenharmony_ci info[i].min_voltage, 3998c2ecf20Sopenharmony_ci info[i].max_voltage); 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci if (info[i].load_uA) 4028c2ecf20Sopenharmony_ci regulator_set_load(bulk[i].consumer, info[i].load_uA); 4038c2ecf20Sopenharmony_ci } 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci wcnss->vregs = bulk; 4068c2ecf20Sopenharmony_ci wcnss->num_vregs = num_vregs; 4078c2ecf20Sopenharmony_ci 4088c2ecf20Sopenharmony_ci return 0; 4098c2ecf20Sopenharmony_ci} 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_cistatic int wcnss_request_irq(struct qcom_wcnss *wcnss, 4128c2ecf20Sopenharmony_ci struct platform_device *pdev, 4138c2ecf20Sopenharmony_ci const char *name, 4148c2ecf20Sopenharmony_ci bool optional, 4158c2ecf20Sopenharmony_ci irq_handler_t thread_fn) 4168c2ecf20Sopenharmony_ci{ 4178c2ecf20Sopenharmony_ci int ret; 4188c2ecf20Sopenharmony_ci int irq_number; 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_ci ret = platform_get_irq_byname(pdev, name); 4218c2ecf20Sopenharmony_ci if (ret < 0 && optional) { 4228c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "no %s IRQ defined, ignoring\n", name); 4238c2ecf20Sopenharmony_ci return 0; 4248c2ecf20Sopenharmony_ci } else if (ret < 0) { 4258c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "no %s IRQ defined\n", name); 4268c2ecf20Sopenharmony_ci return ret; 4278c2ecf20Sopenharmony_ci } 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_ci irq_number = ret; 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(&pdev->dev, ret, 4328c2ecf20Sopenharmony_ci NULL, thread_fn, 4338c2ecf20Sopenharmony_ci IRQF_TRIGGER_RISING | IRQF_ONESHOT, 4348c2ecf20Sopenharmony_ci "wcnss", wcnss); 4358c2ecf20Sopenharmony_ci if (ret) { 4368c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "request %s IRQ failed\n", name); 4378c2ecf20Sopenharmony_ci return ret; 4388c2ecf20Sopenharmony_ci } 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci /* Return the IRQ number if the IRQ was successfully acquired */ 4418c2ecf20Sopenharmony_ci return irq_number; 4428c2ecf20Sopenharmony_ci} 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_cistatic int wcnss_alloc_memory_region(struct qcom_wcnss *wcnss) 4458c2ecf20Sopenharmony_ci{ 4468c2ecf20Sopenharmony_ci struct device_node *node; 4478c2ecf20Sopenharmony_ci struct resource r; 4488c2ecf20Sopenharmony_ci int ret; 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_ci node = of_parse_phandle(wcnss->dev->of_node, "memory-region", 0); 4518c2ecf20Sopenharmony_ci if (!node) { 4528c2ecf20Sopenharmony_ci dev_err(wcnss->dev, "no memory-region specified\n"); 4538c2ecf20Sopenharmony_ci return -EINVAL; 4548c2ecf20Sopenharmony_ci } 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci ret = of_address_to_resource(node, 0, &r); 4578c2ecf20Sopenharmony_ci of_node_put(node); 4588c2ecf20Sopenharmony_ci if (ret) 4598c2ecf20Sopenharmony_ci return ret; 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci wcnss->mem_phys = wcnss->mem_reloc = r.start; 4628c2ecf20Sopenharmony_ci wcnss->mem_size = resource_size(&r); 4638c2ecf20Sopenharmony_ci wcnss->mem_region = devm_ioremap_wc(wcnss->dev, wcnss->mem_phys, wcnss->mem_size); 4648c2ecf20Sopenharmony_ci if (!wcnss->mem_region) { 4658c2ecf20Sopenharmony_ci dev_err(wcnss->dev, "unable to map memory region: %pa+%zx\n", 4668c2ecf20Sopenharmony_ci &r.start, wcnss->mem_size); 4678c2ecf20Sopenharmony_ci return -EBUSY; 4688c2ecf20Sopenharmony_ci } 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci return 0; 4718c2ecf20Sopenharmony_ci} 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_cistatic int wcnss_probe(struct platform_device *pdev) 4748c2ecf20Sopenharmony_ci{ 4758c2ecf20Sopenharmony_ci const struct wcnss_data *data; 4768c2ecf20Sopenharmony_ci struct qcom_wcnss *wcnss; 4778c2ecf20Sopenharmony_ci struct resource *res; 4788c2ecf20Sopenharmony_ci struct rproc *rproc; 4798c2ecf20Sopenharmony_ci void __iomem *mmio; 4808c2ecf20Sopenharmony_ci int ret; 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci data = of_device_get_match_data(&pdev->dev); 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_ci if (!qcom_scm_is_available()) 4858c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 4868c2ecf20Sopenharmony_ci 4878c2ecf20Sopenharmony_ci if (!qcom_scm_pas_supported(WCNSS_PAS_ID)) { 4888c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "PAS is not available for WCNSS\n"); 4898c2ecf20Sopenharmony_ci return -ENXIO; 4908c2ecf20Sopenharmony_ci } 4918c2ecf20Sopenharmony_ci 4928c2ecf20Sopenharmony_ci rproc = rproc_alloc(&pdev->dev, pdev->name, &wcnss_ops, 4938c2ecf20Sopenharmony_ci WCNSS_FIRMWARE_NAME, sizeof(*wcnss)); 4948c2ecf20Sopenharmony_ci if (!rproc) { 4958c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "unable to allocate remoteproc\n"); 4968c2ecf20Sopenharmony_ci return -ENOMEM; 4978c2ecf20Sopenharmony_ci } 4988c2ecf20Sopenharmony_ci rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE); 4998c2ecf20Sopenharmony_ci 5008c2ecf20Sopenharmony_ci wcnss = (struct qcom_wcnss *)rproc->priv; 5018c2ecf20Sopenharmony_ci wcnss->dev = &pdev->dev; 5028c2ecf20Sopenharmony_ci wcnss->rproc = rproc; 5038c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, wcnss); 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ci init_completion(&wcnss->start_done); 5068c2ecf20Sopenharmony_ci init_completion(&wcnss->stop_done); 5078c2ecf20Sopenharmony_ci 5088c2ecf20Sopenharmony_ci mutex_init(&wcnss->iris_lock); 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pmu"); 5118c2ecf20Sopenharmony_ci mmio = devm_ioremap_resource(&pdev->dev, res); 5128c2ecf20Sopenharmony_ci if (IS_ERR(mmio)) { 5138c2ecf20Sopenharmony_ci ret = PTR_ERR(mmio); 5148c2ecf20Sopenharmony_ci goto free_rproc; 5158c2ecf20Sopenharmony_ci }; 5168c2ecf20Sopenharmony_ci 5178c2ecf20Sopenharmony_ci ret = wcnss_alloc_memory_region(wcnss); 5188c2ecf20Sopenharmony_ci if (ret) 5198c2ecf20Sopenharmony_ci goto free_rproc; 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci wcnss->pmu_cfg = mmio + data->pmu_offset; 5228c2ecf20Sopenharmony_ci wcnss->spare_out = mmio + data->spare_offset; 5238c2ecf20Sopenharmony_ci 5248c2ecf20Sopenharmony_ci ret = wcnss_init_regulators(wcnss, data->vregs, data->num_vregs); 5258c2ecf20Sopenharmony_ci if (ret) 5268c2ecf20Sopenharmony_ci goto free_rproc; 5278c2ecf20Sopenharmony_ci 5288c2ecf20Sopenharmony_ci ret = wcnss_request_irq(wcnss, pdev, "wdog", false, wcnss_wdog_interrupt); 5298c2ecf20Sopenharmony_ci if (ret < 0) 5308c2ecf20Sopenharmony_ci goto free_rproc; 5318c2ecf20Sopenharmony_ci wcnss->wdog_irq = ret; 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci ret = wcnss_request_irq(wcnss, pdev, "fatal", false, wcnss_fatal_interrupt); 5348c2ecf20Sopenharmony_ci if (ret < 0) 5358c2ecf20Sopenharmony_ci goto free_rproc; 5368c2ecf20Sopenharmony_ci wcnss->fatal_irq = ret; 5378c2ecf20Sopenharmony_ci 5388c2ecf20Sopenharmony_ci ret = wcnss_request_irq(wcnss, pdev, "ready", true, wcnss_ready_interrupt); 5398c2ecf20Sopenharmony_ci if (ret < 0) 5408c2ecf20Sopenharmony_ci goto free_rproc; 5418c2ecf20Sopenharmony_ci wcnss->ready_irq = ret; 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci ret = wcnss_request_irq(wcnss, pdev, "handover", true, wcnss_handover_interrupt); 5448c2ecf20Sopenharmony_ci if (ret < 0) 5458c2ecf20Sopenharmony_ci goto free_rproc; 5468c2ecf20Sopenharmony_ci wcnss->handover_irq = ret; 5478c2ecf20Sopenharmony_ci 5488c2ecf20Sopenharmony_ci ret = wcnss_request_irq(wcnss, pdev, "stop-ack", true, wcnss_stop_ack_interrupt); 5498c2ecf20Sopenharmony_ci if (ret < 0) 5508c2ecf20Sopenharmony_ci goto free_rproc; 5518c2ecf20Sopenharmony_ci wcnss->stop_ack_irq = ret; 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_ci if (wcnss->stop_ack_irq) { 5548c2ecf20Sopenharmony_ci wcnss->state = qcom_smem_state_get(&pdev->dev, "stop", 5558c2ecf20Sopenharmony_ci &wcnss->stop_bit); 5568c2ecf20Sopenharmony_ci if (IS_ERR(wcnss->state)) { 5578c2ecf20Sopenharmony_ci ret = PTR_ERR(wcnss->state); 5588c2ecf20Sopenharmony_ci goto free_rproc; 5598c2ecf20Sopenharmony_ci } 5608c2ecf20Sopenharmony_ci } 5618c2ecf20Sopenharmony_ci 5628c2ecf20Sopenharmony_ci qcom_add_smd_subdev(rproc, &wcnss->smd_subdev); 5638c2ecf20Sopenharmony_ci wcnss->sysmon = qcom_add_sysmon_subdev(rproc, "wcnss", WCNSS_SSCTL_ID); 5648c2ecf20Sopenharmony_ci if (IS_ERR(wcnss->sysmon)) { 5658c2ecf20Sopenharmony_ci ret = PTR_ERR(wcnss->sysmon); 5668c2ecf20Sopenharmony_ci goto free_rproc; 5678c2ecf20Sopenharmony_ci } 5688c2ecf20Sopenharmony_ci 5698c2ecf20Sopenharmony_ci ret = rproc_add(rproc); 5708c2ecf20Sopenharmony_ci if (ret) 5718c2ecf20Sopenharmony_ci goto free_rproc; 5728c2ecf20Sopenharmony_ci 5738c2ecf20Sopenharmony_ci return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); 5748c2ecf20Sopenharmony_ci 5758c2ecf20Sopenharmony_cifree_rproc: 5768c2ecf20Sopenharmony_ci rproc_free(rproc); 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci return ret; 5798c2ecf20Sopenharmony_ci} 5808c2ecf20Sopenharmony_ci 5818c2ecf20Sopenharmony_cistatic int wcnss_remove(struct platform_device *pdev) 5828c2ecf20Sopenharmony_ci{ 5838c2ecf20Sopenharmony_ci struct qcom_wcnss *wcnss = platform_get_drvdata(pdev); 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci of_platform_depopulate(&pdev->dev); 5868c2ecf20Sopenharmony_ci 5878c2ecf20Sopenharmony_ci qcom_smem_state_put(wcnss->state); 5888c2ecf20Sopenharmony_ci rproc_del(wcnss->rproc); 5898c2ecf20Sopenharmony_ci 5908c2ecf20Sopenharmony_ci qcom_remove_sysmon_subdev(wcnss->sysmon); 5918c2ecf20Sopenharmony_ci qcom_remove_smd_subdev(wcnss->rproc, &wcnss->smd_subdev); 5928c2ecf20Sopenharmony_ci rproc_free(wcnss->rproc); 5938c2ecf20Sopenharmony_ci 5948c2ecf20Sopenharmony_ci return 0; 5958c2ecf20Sopenharmony_ci} 5968c2ecf20Sopenharmony_ci 5978c2ecf20Sopenharmony_cistatic const struct of_device_id wcnss_of_match[] = { 5988c2ecf20Sopenharmony_ci { .compatible = "qcom,riva-pil", &riva_data }, 5998c2ecf20Sopenharmony_ci { .compatible = "qcom,pronto-v1-pil", &pronto_v1_data }, 6008c2ecf20Sopenharmony_ci { .compatible = "qcom,pronto-v2-pil", &pronto_v2_data }, 6018c2ecf20Sopenharmony_ci { }, 6028c2ecf20Sopenharmony_ci}; 6038c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, wcnss_of_match); 6048c2ecf20Sopenharmony_ci 6058c2ecf20Sopenharmony_cistatic struct platform_driver wcnss_driver = { 6068c2ecf20Sopenharmony_ci .probe = wcnss_probe, 6078c2ecf20Sopenharmony_ci .remove = wcnss_remove, 6088c2ecf20Sopenharmony_ci .driver = { 6098c2ecf20Sopenharmony_ci .name = "qcom-wcnss-pil", 6108c2ecf20Sopenharmony_ci .of_match_table = wcnss_of_match, 6118c2ecf20Sopenharmony_ci }, 6128c2ecf20Sopenharmony_ci}; 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_cistatic int __init wcnss_init(void) 6158c2ecf20Sopenharmony_ci{ 6168c2ecf20Sopenharmony_ci int ret; 6178c2ecf20Sopenharmony_ci 6188c2ecf20Sopenharmony_ci ret = platform_driver_register(&wcnss_driver); 6198c2ecf20Sopenharmony_ci if (ret) 6208c2ecf20Sopenharmony_ci return ret; 6218c2ecf20Sopenharmony_ci 6228c2ecf20Sopenharmony_ci ret = platform_driver_register(&qcom_iris_driver); 6238c2ecf20Sopenharmony_ci if (ret) 6248c2ecf20Sopenharmony_ci platform_driver_unregister(&wcnss_driver); 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_ci return ret; 6278c2ecf20Sopenharmony_ci} 6288c2ecf20Sopenharmony_cimodule_init(wcnss_init); 6298c2ecf20Sopenharmony_ci 6308c2ecf20Sopenharmony_cistatic void __exit wcnss_exit(void) 6318c2ecf20Sopenharmony_ci{ 6328c2ecf20Sopenharmony_ci platform_driver_unregister(&qcom_iris_driver); 6338c2ecf20Sopenharmony_ci platform_driver_unregister(&wcnss_driver); 6348c2ecf20Sopenharmony_ci} 6358c2ecf20Sopenharmony_cimodule_exit(wcnss_exit); 6368c2ecf20Sopenharmony_ci 6378c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm Peripheral Image Loader for Wireless Subsystem"); 6388c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 639