162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Qualcomm Wireless Connectivity Subsystem Peripheral Image Loader 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2016 Linaro Ltd 662306a36Sopenharmony_ci * Copyright (C) 2014 Sony Mobile Communications AB 762306a36Sopenharmony_ci * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/clk.h> 1162306a36Sopenharmony_ci#include <linux/delay.h> 1262306a36Sopenharmony_ci#include <linux/firmware.h> 1362306a36Sopenharmony_ci#include <linux/interrupt.h> 1462306a36Sopenharmony_ci#include <linux/kernel.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/io.h> 1762306a36Sopenharmony_ci#include <linux/of.h> 1862306a36Sopenharmony_ci#include <linux/of_reserved_mem.h> 1962306a36Sopenharmony_ci#include <linux/platform_device.h> 2062306a36Sopenharmony_ci#include <linux/pm_domain.h> 2162306a36Sopenharmony_ci#include <linux/pm_runtime.h> 2262306a36Sopenharmony_ci#include <linux/firmware/qcom/qcom_scm.h> 2362306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 2462306a36Sopenharmony_ci#include <linux/remoteproc.h> 2562306a36Sopenharmony_ci#include <linux/soc/qcom/mdt_loader.h> 2662306a36Sopenharmony_ci#include <linux/soc/qcom/smem.h> 2762306a36Sopenharmony_ci#include <linux/soc/qcom/smem_state.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#include "qcom_common.h" 3062306a36Sopenharmony_ci#include "remoteproc_internal.h" 3162306a36Sopenharmony_ci#include "qcom_pil_info.h" 3262306a36Sopenharmony_ci#include "qcom_wcnss.h" 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define WCNSS_CRASH_REASON_SMEM 422 3562306a36Sopenharmony_ci#define WCNSS_FIRMWARE_NAME "wcnss.mdt" 3662306a36Sopenharmony_ci#define WCNSS_PAS_ID 6 3762306a36Sopenharmony_ci#define WCNSS_SSCTL_ID 0x13 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#define WCNSS_SPARE_NVBIN_DLND BIT(25) 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci#define WCNSS_PMU_IRIS_XO_CFG BIT(3) 4262306a36Sopenharmony_ci#define WCNSS_PMU_IRIS_XO_EN BIT(4) 4362306a36Sopenharmony_ci#define WCNSS_PMU_GC_BUS_MUX_SEL_TOP BIT(5) 4462306a36Sopenharmony_ci#define WCNSS_PMU_IRIS_XO_CFG_STS BIT(6) /* 1: in progress, 0: done */ 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#define WCNSS_PMU_IRIS_RESET BIT(7) 4762306a36Sopenharmony_ci#define WCNSS_PMU_IRIS_RESET_STS BIT(8) /* 1: in progress, 0: done */ 4862306a36Sopenharmony_ci#define WCNSS_PMU_IRIS_XO_READ BIT(9) 4962306a36Sopenharmony_ci#define WCNSS_PMU_IRIS_XO_READ_STS BIT(10) 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci#define WCNSS_PMU_XO_MODE_MASK GENMASK(2, 1) 5262306a36Sopenharmony_ci#define WCNSS_PMU_XO_MODE_19p2 0 5362306a36Sopenharmony_ci#define WCNSS_PMU_XO_MODE_48 3 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci#define WCNSS_MAX_PDS 2 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistruct wcnss_data { 5862306a36Sopenharmony_ci size_t pmu_offset; 5962306a36Sopenharmony_ci size_t spare_offset; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci const char *pd_names[WCNSS_MAX_PDS]; 6262306a36Sopenharmony_ci const struct wcnss_vreg_info *vregs; 6362306a36Sopenharmony_ci size_t num_vregs, num_pd_vregs; 6462306a36Sopenharmony_ci}; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistruct qcom_wcnss { 6762306a36Sopenharmony_ci struct device *dev; 6862306a36Sopenharmony_ci struct rproc *rproc; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci void __iomem *pmu_cfg; 7162306a36Sopenharmony_ci void __iomem *spare_out; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci bool use_48mhz_xo; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci int wdog_irq; 7662306a36Sopenharmony_ci int fatal_irq; 7762306a36Sopenharmony_ci int ready_irq; 7862306a36Sopenharmony_ci int handover_irq; 7962306a36Sopenharmony_ci int stop_ack_irq; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci struct qcom_smem_state *state; 8262306a36Sopenharmony_ci unsigned stop_bit; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci struct mutex iris_lock; 8562306a36Sopenharmony_ci struct qcom_iris *iris; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci struct device *pds[WCNSS_MAX_PDS]; 8862306a36Sopenharmony_ci size_t num_pds; 8962306a36Sopenharmony_ci struct regulator_bulk_data *vregs; 9062306a36Sopenharmony_ci size_t num_vregs; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci struct completion start_done; 9362306a36Sopenharmony_ci struct completion stop_done; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci phys_addr_t mem_phys; 9662306a36Sopenharmony_ci phys_addr_t mem_reloc; 9762306a36Sopenharmony_ci void *mem_region; 9862306a36Sopenharmony_ci size_t mem_size; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci struct qcom_rproc_subdev smd_subdev; 10162306a36Sopenharmony_ci struct qcom_sysmon *sysmon; 10262306a36Sopenharmony_ci}; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic const struct wcnss_data riva_data = { 10562306a36Sopenharmony_ci .pmu_offset = 0x28, 10662306a36Sopenharmony_ci .spare_offset = 0xb4, 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci .vregs = (struct wcnss_vreg_info[]) { 10962306a36Sopenharmony_ci { "vddmx", 1050000, 1150000, 0 }, 11062306a36Sopenharmony_ci { "vddcx", 1050000, 1150000, 0 }, 11162306a36Sopenharmony_ci { "vddpx", 1800000, 1800000, 0 }, 11262306a36Sopenharmony_ci }, 11362306a36Sopenharmony_ci .num_vregs = 3, 11462306a36Sopenharmony_ci}; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cistatic const struct wcnss_data pronto_v1_data = { 11762306a36Sopenharmony_ci .pmu_offset = 0x1004, 11862306a36Sopenharmony_ci .spare_offset = 0x1088, 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci .pd_names = { "mx", "cx" }, 12162306a36Sopenharmony_ci .vregs = (struct wcnss_vreg_info[]) { 12262306a36Sopenharmony_ci { "vddmx", 950000, 1150000, 0 }, 12362306a36Sopenharmony_ci { "vddcx", .super_turbo = true}, 12462306a36Sopenharmony_ci { "vddpx", 1800000, 1800000, 0 }, 12562306a36Sopenharmony_ci }, 12662306a36Sopenharmony_ci .num_pd_vregs = 2, 12762306a36Sopenharmony_ci .num_vregs = 1, 12862306a36Sopenharmony_ci}; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic const struct wcnss_data pronto_v2_data = { 13162306a36Sopenharmony_ci .pmu_offset = 0x1004, 13262306a36Sopenharmony_ci .spare_offset = 0x1088, 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci .pd_names = { "mx", "cx" }, 13562306a36Sopenharmony_ci .vregs = (struct wcnss_vreg_info[]) { 13662306a36Sopenharmony_ci { "vddmx", 1287500, 1287500, 0 }, 13762306a36Sopenharmony_ci { "vddcx", .super_turbo = true }, 13862306a36Sopenharmony_ci { "vddpx", 1800000, 1800000, 0 }, 13962306a36Sopenharmony_ci }, 14062306a36Sopenharmony_ci .num_pd_vregs = 2, 14162306a36Sopenharmony_ci .num_vregs = 1, 14262306a36Sopenharmony_ci}; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic const struct wcnss_data pronto_v3_data = { 14562306a36Sopenharmony_ci .pmu_offset = 0x1004, 14662306a36Sopenharmony_ci .spare_offset = 0x1088, 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci .pd_names = { "mx", "cx" }, 14962306a36Sopenharmony_ci .vregs = (struct wcnss_vreg_info[]) { 15062306a36Sopenharmony_ci { "vddpx", 1800000, 1800000, 0 }, 15162306a36Sopenharmony_ci }, 15262306a36Sopenharmony_ci .num_vregs = 1, 15362306a36Sopenharmony_ci}; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_cistatic int wcnss_load(struct rproc *rproc, const struct firmware *fw) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci struct qcom_wcnss *wcnss = rproc->priv; 15862306a36Sopenharmony_ci int ret; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci ret = qcom_mdt_load(wcnss->dev, fw, rproc->firmware, WCNSS_PAS_ID, 16162306a36Sopenharmony_ci wcnss->mem_region, wcnss->mem_phys, 16262306a36Sopenharmony_ci wcnss->mem_size, &wcnss->mem_reloc); 16362306a36Sopenharmony_ci if (ret) 16462306a36Sopenharmony_ci return ret; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci qcom_pil_info_store("wcnss", wcnss->mem_phys, wcnss->mem_size); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci return 0; 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic void wcnss_indicate_nv_download(struct qcom_wcnss *wcnss) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci u32 val; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* Indicate NV download capability */ 17662306a36Sopenharmony_ci val = readl(wcnss->spare_out); 17762306a36Sopenharmony_ci val |= WCNSS_SPARE_NVBIN_DLND; 17862306a36Sopenharmony_ci writel(val, wcnss->spare_out); 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic void wcnss_configure_iris(struct qcom_wcnss *wcnss) 18262306a36Sopenharmony_ci{ 18362306a36Sopenharmony_ci u32 val; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci /* Clear PMU cfg register */ 18662306a36Sopenharmony_ci writel(0, wcnss->pmu_cfg); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci val = WCNSS_PMU_GC_BUS_MUX_SEL_TOP | WCNSS_PMU_IRIS_XO_EN; 18962306a36Sopenharmony_ci writel(val, wcnss->pmu_cfg); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci /* Clear XO_MODE */ 19262306a36Sopenharmony_ci val &= ~WCNSS_PMU_XO_MODE_MASK; 19362306a36Sopenharmony_ci if (wcnss->use_48mhz_xo) 19462306a36Sopenharmony_ci val |= WCNSS_PMU_XO_MODE_48 << 1; 19562306a36Sopenharmony_ci else 19662306a36Sopenharmony_ci val |= WCNSS_PMU_XO_MODE_19p2 << 1; 19762306a36Sopenharmony_ci writel(val, wcnss->pmu_cfg); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci /* Reset IRIS */ 20062306a36Sopenharmony_ci val |= WCNSS_PMU_IRIS_RESET; 20162306a36Sopenharmony_ci writel(val, wcnss->pmu_cfg); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci /* Wait for PMU.iris_reg_reset_sts */ 20462306a36Sopenharmony_ci while (readl(wcnss->pmu_cfg) & WCNSS_PMU_IRIS_RESET_STS) 20562306a36Sopenharmony_ci cpu_relax(); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci /* Clear IRIS reset */ 20862306a36Sopenharmony_ci val &= ~WCNSS_PMU_IRIS_RESET; 20962306a36Sopenharmony_ci writel(val, wcnss->pmu_cfg); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci /* Start IRIS XO configuration */ 21262306a36Sopenharmony_ci val |= WCNSS_PMU_IRIS_XO_CFG; 21362306a36Sopenharmony_ci writel(val, wcnss->pmu_cfg); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci /* Wait for XO configuration to finish */ 21662306a36Sopenharmony_ci while (readl(wcnss->pmu_cfg) & WCNSS_PMU_IRIS_XO_CFG_STS) 21762306a36Sopenharmony_ci cpu_relax(); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci /* Stop IRIS XO configuration */ 22062306a36Sopenharmony_ci val &= ~WCNSS_PMU_GC_BUS_MUX_SEL_TOP; 22162306a36Sopenharmony_ci val &= ~WCNSS_PMU_IRIS_XO_CFG; 22262306a36Sopenharmony_ci writel(val, wcnss->pmu_cfg); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci /* Add some delay for XO to settle */ 22562306a36Sopenharmony_ci msleep(20); 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cistatic int wcnss_start(struct rproc *rproc) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci struct qcom_wcnss *wcnss = rproc->priv; 23162306a36Sopenharmony_ci int ret, i; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci mutex_lock(&wcnss->iris_lock); 23462306a36Sopenharmony_ci if (!wcnss->iris) { 23562306a36Sopenharmony_ci dev_err(wcnss->dev, "no iris registered\n"); 23662306a36Sopenharmony_ci ret = -EINVAL; 23762306a36Sopenharmony_ci goto release_iris_lock; 23862306a36Sopenharmony_ci } 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci for (i = 0; i < wcnss->num_pds; i++) { 24162306a36Sopenharmony_ci dev_pm_genpd_set_performance_state(wcnss->pds[i], INT_MAX); 24262306a36Sopenharmony_ci ret = pm_runtime_get_sync(wcnss->pds[i]); 24362306a36Sopenharmony_ci if (ret < 0) { 24462306a36Sopenharmony_ci pm_runtime_put_noidle(wcnss->pds[i]); 24562306a36Sopenharmony_ci goto disable_pds; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci ret = regulator_bulk_enable(wcnss->num_vregs, wcnss->vregs); 25062306a36Sopenharmony_ci if (ret) 25162306a36Sopenharmony_ci goto disable_pds; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci ret = qcom_iris_enable(wcnss->iris); 25462306a36Sopenharmony_ci if (ret) 25562306a36Sopenharmony_ci goto disable_regulators; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci wcnss_indicate_nv_download(wcnss); 25862306a36Sopenharmony_ci wcnss_configure_iris(wcnss); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci ret = qcom_scm_pas_auth_and_reset(WCNSS_PAS_ID); 26162306a36Sopenharmony_ci if (ret) { 26262306a36Sopenharmony_ci dev_err(wcnss->dev, 26362306a36Sopenharmony_ci "failed to authenticate image and release reset\n"); 26462306a36Sopenharmony_ci goto disable_iris; 26562306a36Sopenharmony_ci } 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci ret = wait_for_completion_timeout(&wcnss->start_done, 26862306a36Sopenharmony_ci msecs_to_jiffies(5000)); 26962306a36Sopenharmony_ci if (wcnss->ready_irq > 0 && ret == 0) { 27062306a36Sopenharmony_ci /* We have a ready_irq, but it didn't fire in time. */ 27162306a36Sopenharmony_ci dev_err(wcnss->dev, "start timed out\n"); 27262306a36Sopenharmony_ci qcom_scm_pas_shutdown(WCNSS_PAS_ID); 27362306a36Sopenharmony_ci ret = -ETIMEDOUT; 27462306a36Sopenharmony_ci goto disable_iris; 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci ret = 0; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cidisable_iris: 28062306a36Sopenharmony_ci qcom_iris_disable(wcnss->iris); 28162306a36Sopenharmony_cidisable_regulators: 28262306a36Sopenharmony_ci regulator_bulk_disable(wcnss->num_vregs, wcnss->vregs); 28362306a36Sopenharmony_cidisable_pds: 28462306a36Sopenharmony_ci for (i--; i >= 0; i--) { 28562306a36Sopenharmony_ci pm_runtime_put(wcnss->pds[i]); 28662306a36Sopenharmony_ci dev_pm_genpd_set_performance_state(wcnss->pds[i], 0); 28762306a36Sopenharmony_ci } 28862306a36Sopenharmony_cirelease_iris_lock: 28962306a36Sopenharmony_ci mutex_unlock(&wcnss->iris_lock); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci return ret; 29262306a36Sopenharmony_ci} 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_cistatic int wcnss_stop(struct rproc *rproc) 29562306a36Sopenharmony_ci{ 29662306a36Sopenharmony_ci struct qcom_wcnss *wcnss = rproc->priv; 29762306a36Sopenharmony_ci int ret; 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci if (wcnss->state) { 30062306a36Sopenharmony_ci qcom_smem_state_update_bits(wcnss->state, 30162306a36Sopenharmony_ci BIT(wcnss->stop_bit), 30262306a36Sopenharmony_ci BIT(wcnss->stop_bit)); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci ret = wait_for_completion_timeout(&wcnss->stop_done, 30562306a36Sopenharmony_ci msecs_to_jiffies(5000)); 30662306a36Sopenharmony_ci if (ret == 0) 30762306a36Sopenharmony_ci dev_err(wcnss->dev, "timed out on wait\n"); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci qcom_smem_state_update_bits(wcnss->state, 31062306a36Sopenharmony_ci BIT(wcnss->stop_bit), 31162306a36Sopenharmony_ci 0); 31262306a36Sopenharmony_ci } 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci ret = qcom_scm_pas_shutdown(WCNSS_PAS_ID); 31562306a36Sopenharmony_ci if (ret) 31662306a36Sopenharmony_ci dev_err(wcnss->dev, "failed to shutdown: %d\n", ret); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci return ret; 31962306a36Sopenharmony_ci} 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_cistatic void *wcnss_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem) 32262306a36Sopenharmony_ci{ 32362306a36Sopenharmony_ci struct qcom_wcnss *wcnss = rproc->priv; 32462306a36Sopenharmony_ci int offset; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci offset = da - wcnss->mem_reloc; 32762306a36Sopenharmony_ci if (offset < 0 || offset + len > wcnss->mem_size) 32862306a36Sopenharmony_ci return NULL; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci return wcnss->mem_region + offset; 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_cistatic const struct rproc_ops wcnss_ops = { 33462306a36Sopenharmony_ci .start = wcnss_start, 33562306a36Sopenharmony_ci .stop = wcnss_stop, 33662306a36Sopenharmony_ci .da_to_va = wcnss_da_to_va, 33762306a36Sopenharmony_ci .parse_fw = qcom_register_dump_segments, 33862306a36Sopenharmony_ci .load = wcnss_load, 33962306a36Sopenharmony_ci}; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_cistatic irqreturn_t wcnss_wdog_interrupt(int irq, void *dev) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci struct qcom_wcnss *wcnss = dev; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci rproc_report_crash(wcnss->rproc, RPROC_WATCHDOG); 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci return IRQ_HANDLED; 34862306a36Sopenharmony_ci} 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_cistatic irqreturn_t wcnss_fatal_interrupt(int irq, void *dev) 35162306a36Sopenharmony_ci{ 35262306a36Sopenharmony_ci struct qcom_wcnss *wcnss = dev; 35362306a36Sopenharmony_ci size_t len; 35462306a36Sopenharmony_ci char *msg; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, WCNSS_CRASH_REASON_SMEM, &len); 35762306a36Sopenharmony_ci if (!IS_ERR(msg) && len > 0 && msg[0]) 35862306a36Sopenharmony_ci dev_err(wcnss->dev, "fatal error received: %s\n", msg); 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci rproc_report_crash(wcnss->rproc, RPROC_FATAL_ERROR); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci return IRQ_HANDLED; 36362306a36Sopenharmony_ci} 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_cistatic irqreturn_t wcnss_ready_interrupt(int irq, void *dev) 36662306a36Sopenharmony_ci{ 36762306a36Sopenharmony_ci struct qcom_wcnss *wcnss = dev; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci complete(&wcnss->start_done); 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci return IRQ_HANDLED; 37262306a36Sopenharmony_ci} 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_cistatic irqreturn_t wcnss_handover_interrupt(int irq, void *dev) 37562306a36Sopenharmony_ci{ 37662306a36Sopenharmony_ci /* 37762306a36Sopenharmony_ci * XXX: At this point we're supposed to release the resources that we 37862306a36Sopenharmony_ci * have been holding on behalf of the WCNSS. Unfortunately this 37962306a36Sopenharmony_ci * interrupt comes way before the other side seems to be done. 38062306a36Sopenharmony_ci * 38162306a36Sopenharmony_ci * So we're currently relying on the ready interrupt firing later then 38262306a36Sopenharmony_ci * this and we just disable the resources at the end of wcnss_start(). 38362306a36Sopenharmony_ci */ 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci return IRQ_HANDLED; 38662306a36Sopenharmony_ci} 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_cistatic irqreturn_t wcnss_stop_ack_interrupt(int irq, void *dev) 38962306a36Sopenharmony_ci{ 39062306a36Sopenharmony_ci struct qcom_wcnss *wcnss = dev; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci complete(&wcnss->stop_done); 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci return IRQ_HANDLED; 39562306a36Sopenharmony_ci} 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_cistatic int wcnss_init_pds(struct qcom_wcnss *wcnss, 39862306a36Sopenharmony_ci const char * const pd_names[WCNSS_MAX_PDS]) 39962306a36Sopenharmony_ci{ 40062306a36Sopenharmony_ci int i, ret; 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci for (i = 0; i < WCNSS_MAX_PDS; i++) { 40362306a36Sopenharmony_ci if (!pd_names[i]) 40462306a36Sopenharmony_ci break; 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci wcnss->pds[i] = dev_pm_domain_attach_by_name(wcnss->dev, pd_names[i]); 40762306a36Sopenharmony_ci if (IS_ERR_OR_NULL(wcnss->pds[i])) { 40862306a36Sopenharmony_ci ret = PTR_ERR(wcnss->pds[i]) ? : -ENODATA; 40962306a36Sopenharmony_ci for (i--; i >= 0; i--) 41062306a36Sopenharmony_ci dev_pm_domain_detach(wcnss->pds[i], false); 41162306a36Sopenharmony_ci return ret; 41262306a36Sopenharmony_ci } 41362306a36Sopenharmony_ci } 41462306a36Sopenharmony_ci wcnss->num_pds = i; 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci return 0; 41762306a36Sopenharmony_ci} 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_cistatic void wcnss_release_pds(struct qcom_wcnss *wcnss) 42062306a36Sopenharmony_ci{ 42162306a36Sopenharmony_ci int i; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci for (i = 0; i < wcnss->num_pds; i++) 42462306a36Sopenharmony_ci dev_pm_domain_detach(wcnss->pds[i], false); 42562306a36Sopenharmony_ci} 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_cistatic int wcnss_init_regulators(struct qcom_wcnss *wcnss, 42862306a36Sopenharmony_ci const struct wcnss_vreg_info *info, 42962306a36Sopenharmony_ci int num_vregs, int num_pd_vregs) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci struct regulator_bulk_data *bulk; 43262306a36Sopenharmony_ci int ret; 43362306a36Sopenharmony_ci int i; 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci /* 43662306a36Sopenharmony_ci * If attaching the power domains suceeded we can skip requesting 43762306a36Sopenharmony_ci * the regulators for the power domains. For old device trees we need to 43862306a36Sopenharmony_ci * reserve extra space to manage them through the regulator interface. 43962306a36Sopenharmony_ci */ 44062306a36Sopenharmony_ci if (wcnss->num_pds) 44162306a36Sopenharmony_ci info += num_pd_vregs; 44262306a36Sopenharmony_ci else 44362306a36Sopenharmony_ci num_vregs += num_pd_vregs; 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci bulk = devm_kcalloc(wcnss->dev, 44662306a36Sopenharmony_ci num_vregs, sizeof(struct regulator_bulk_data), 44762306a36Sopenharmony_ci GFP_KERNEL); 44862306a36Sopenharmony_ci if (!bulk) 44962306a36Sopenharmony_ci return -ENOMEM; 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci for (i = 0; i < num_vregs; i++) 45262306a36Sopenharmony_ci bulk[i].supply = info[i].name; 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci ret = devm_regulator_bulk_get(wcnss->dev, num_vregs, bulk); 45562306a36Sopenharmony_ci if (ret) 45662306a36Sopenharmony_ci return ret; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci for (i = 0; i < num_vregs; i++) { 45962306a36Sopenharmony_ci if (info[i].max_voltage) 46062306a36Sopenharmony_ci regulator_set_voltage(bulk[i].consumer, 46162306a36Sopenharmony_ci info[i].min_voltage, 46262306a36Sopenharmony_ci info[i].max_voltage); 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci if (info[i].load_uA) 46562306a36Sopenharmony_ci regulator_set_load(bulk[i].consumer, info[i].load_uA); 46662306a36Sopenharmony_ci } 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci wcnss->vregs = bulk; 46962306a36Sopenharmony_ci wcnss->num_vregs = num_vregs; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci return 0; 47262306a36Sopenharmony_ci} 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_cistatic int wcnss_request_irq(struct qcom_wcnss *wcnss, 47562306a36Sopenharmony_ci struct platform_device *pdev, 47662306a36Sopenharmony_ci const char *name, 47762306a36Sopenharmony_ci bool optional, 47862306a36Sopenharmony_ci irq_handler_t thread_fn) 47962306a36Sopenharmony_ci{ 48062306a36Sopenharmony_ci int ret; 48162306a36Sopenharmony_ci int irq_number; 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci ret = platform_get_irq_byname(pdev, name); 48462306a36Sopenharmony_ci if (ret < 0 && optional) { 48562306a36Sopenharmony_ci dev_dbg(&pdev->dev, "no %s IRQ defined, ignoring\n", name); 48662306a36Sopenharmony_ci return 0; 48762306a36Sopenharmony_ci } else if (ret < 0) { 48862306a36Sopenharmony_ci dev_err(&pdev->dev, "no %s IRQ defined\n", name); 48962306a36Sopenharmony_ci return ret; 49062306a36Sopenharmony_ci } 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci irq_number = ret; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci ret = devm_request_threaded_irq(&pdev->dev, ret, 49562306a36Sopenharmony_ci NULL, thread_fn, 49662306a36Sopenharmony_ci IRQF_TRIGGER_RISING | IRQF_ONESHOT, 49762306a36Sopenharmony_ci "wcnss", wcnss); 49862306a36Sopenharmony_ci if (ret) { 49962306a36Sopenharmony_ci dev_err(&pdev->dev, "request %s IRQ failed\n", name); 50062306a36Sopenharmony_ci return ret; 50162306a36Sopenharmony_ci } 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci /* Return the IRQ number if the IRQ was successfully acquired */ 50462306a36Sopenharmony_ci return irq_number; 50562306a36Sopenharmony_ci} 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_cistatic int wcnss_alloc_memory_region(struct qcom_wcnss *wcnss) 50862306a36Sopenharmony_ci{ 50962306a36Sopenharmony_ci struct reserved_mem *rmem = NULL; 51062306a36Sopenharmony_ci struct device_node *node; 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci node = of_parse_phandle(wcnss->dev->of_node, "memory-region", 0); 51362306a36Sopenharmony_ci if (node) 51462306a36Sopenharmony_ci rmem = of_reserved_mem_lookup(node); 51562306a36Sopenharmony_ci of_node_put(node); 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci if (!rmem) { 51862306a36Sopenharmony_ci dev_err(wcnss->dev, "unable to resolve memory-region\n"); 51962306a36Sopenharmony_ci return -EINVAL; 52062306a36Sopenharmony_ci } 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci wcnss->mem_phys = wcnss->mem_reloc = rmem->base; 52362306a36Sopenharmony_ci wcnss->mem_size = rmem->size; 52462306a36Sopenharmony_ci wcnss->mem_region = devm_ioremap_wc(wcnss->dev, wcnss->mem_phys, wcnss->mem_size); 52562306a36Sopenharmony_ci if (!wcnss->mem_region) { 52662306a36Sopenharmony_ci dev_err(wcnss->dev, "unable to map memory region: %pa+%zx\n", 52762306a36Sopenharmony_ci &rmem->base, wcnss->mem_size); 52862306a36Sopenharmony_ci return -EBUSY; 52962306a36Sopenharmony_ci } 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci return 0; 53262306a36Sopenharmony_ci} 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_cistatic int wcnss_probe(struct platform_device *pdev) 53562306a36Sopenharmony_ci{ 53662306a36Sopenharmony_ci const char *fw_name = WCNSS_FIRMWARE_NAME; 53762306a36Sopenharmony_ci const struct wcnss_data *data; 53862306a36Sopenharmony_ci struct qcom_wcnss *wcnss; 53962306a36Sopenharmony_ci struct rproc *rproc; 54062306a36Sopenharmony_ci void __iomem *mmio; 54162306a36Sopenharmony_ci int ret; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci data = of_device_get_match_data(&pdev->dev); 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci if (!qcom_scm_is_available()) 54662306a36Sopenharmony_ci return -EPROBE_DEFER; 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci if (!qcom_scm_pas_supported(WCNSS_PAS_ID)) { 54962306a36Sopenharmony_ci dev_err(&pdev->dev, "PAS is not available for WCNSS\n"); 55062306a36Sopenharmony_ci return -ENXIO; 55162306a36Sopenharmony_ci } 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci ret = of_property_read_string(pdev->dev.of_node, "firmware-name", 55462306a36Sopenharmony_ci &fw_name); 55562306a36Sopenharmony_ci if (ret < 0 && ret != -EINVAL) 55662306a36Sopenharmony_ci return ret; 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci rproc = rproc_alloc(&pdev->dev, pdev->name, &wcnss_ops, 55962306a36Sopenharmony_ci fw_name, sizeof(*wcnss)); 56062306a36Sopenharmony_ci if (!rproc) { 56162306a36Sopenharmony_ci dev_err(&pdev->dev, "unable to allocate remoteproc\n"); 56262306a36Sopenharmony_ci return -ENOMEM; 56362306a36Sopenharmony_ci } 56462306a36Sopenharmony_ci rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE); 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci wcnss = rproc->priv; 56762306a36Sopenharmony_ci wcnss->dev = &pdev->dev; 56862306a36Sopenharmony_ci wcnss->rproc = rproc; 56962306a36Sopenharmony_ci platform_set_drvdata(pdev, wcnss); 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci init_completion(&wcnss->start_done); 57262306a36Sopenharmony_ci init_completion(&wcnss->stop_done); 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci mutex_init(&wcnss->iris_lock); 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci mmio = devm_platform_ioremap_resource_byname(pdev, "pmu"); 57762306a36Sopenharmony_ci if (IS_ERR(mmio)) { 57862306a36Sopenharmony_ci ret = PTR_ERR(mmio); 57962306a36Sopenharmony_ci goto free_rproc; 58062306a36Sopenharmony_ci } 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci ret = wcnss_alloc_memory_region(wcnss); 58362306a36Sopenharmony_ci if (ret) 58462306a36Sopenharmony_ci goto free_rproc; 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci wcnss->pmu_cfg = mmio + data->pmu_offset; 58762306a36Sopenharmony_ci wcnss->spare_out = mmio + data->spare_offset; 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci /* 59062306a36Sopenharmony_ci * We might need to fallback to regulators instead of power domains 59162306a36Sopenharmony_ci * for old device trees. Don't report an error in that case. 59262306a36Sopenharmony_ci */ 59362306a36Sopenharmony_ci ret = wcnss_init_pds(wcnss, data->pd_names); 59462306a36Sopenharmony_ci if (ret && (ret != -ENODATA || !data->num_pd_vregs)) 59562306a36Sopenharmony_ci goto free_rproc; 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci ret = wcnss_init_regulators(wcnss, data->vregs, data->num_vregs, 59862306a36Sopenharmony_ci data->num_pd_vregs); 59962306a36Sopenharmony_ci if (ret) 60062306a36Sopenharmony_ci goto detach_pds; 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci ret = wcnss_request_irq(wcnss, pdev, "wdog", false, wcnss_wdog_interrupt); 60362306a36Sopenharmony_ci if (ret < 0) 60462306a36Sopenharmony_ci goto detach_pds; 60562306a36Sopenharmony_ci wcnss->wdog_irq = ret; 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_ci ret = wcnss_request_irq(wcnss, pdev, "fatal", false, wcnss_fatal_interrupt); 60862306a36Sopenharmony_ci if (ret < 0) 60962306a36Sopenharmony_ci goto detach_pds; 61062306a36Sopenharmony_ci wcnss->fatal_irq = ret; 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci ret = wcnss_request_irq(wcnss, pdev, "ready", true, wcnss_ready_interrupt); 61362306a36Sopenharmony_ci if (ret < 0) 61462306a36Sopenharmony_ci goto detach_pds; 61562306a36Sopenharmony_ci wcnss->ready_irq = ret; 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci ret = wcnss_request_irq(wcnss, pdev, "handover", true, wcnss_handover_interrupt); 61862306a36Sopenharmony_ci if (ret < 0) 61962306a36Sopenharmony_ci goto detach_pds; 62062306a36Sopenharmony_ci wcnss->handover_irq = ret; 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci ret = wcnss_request_irq(wcnss, pdev, "stop-ack", true, wcnss_stop_ack_interrupt); 62362306a36Sopenharmony_ci if (ret < 0) 62462306a36Sopenharmony_ci goto detach_pds; 62562306a36Sopenharmony_ci wcnss->stop_ack_irq = ret; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci if (wcnss->stop_ack_irq) { 62862306a36Sopenharmony_ci wcnss->state = devm_qcom_smem_state_get(&pdev->dev, "stop", 62962306a36Sopenharmony_ci &wcnss->stop_bit); 63062306a36Sopenharmony_ci if (IS_ERR(wcnss->state)) { 63162306a36Sopenharmony_ci ret = PTR_ERR(wcnss->state); 63262306a36Sopenharmony_ci goto detach_pds; 63362306a36Sopenharmony_ci } 63462306a36Sopenharmony_ci } 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci qcom_add_smd_subdev(rproc, &wcnss->smd_subdev); 63762306a36Sopenharmony_ci wcnss->sysmon = qcom_add_sysmon_subdev(rproc, "wcnss", WCNSS_SSCTL_ID); 63862306a36Sopenharmony_ci if (IS_ERR(wcnss->sysmon)) { 63962306a36Sopenharmony_ci ret = PTR_ERR(wcnss->sysmon); 64062306a36Sopenharmony_ci goto detach_pds; 64162306a36Sopenharmony_ci } 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci wcnss->iris = qcom_iris_probe(&pdev->dev, &wcnss->use_48mhz_xo); 64462306a36Sopenharmony_ci if (IS_ERR(wcnss->iris)) { 64562306a36Sopenharmony_ci ret = PTR_ERR(wcnss->iris); 64662306a36Sopenharmony_ci goto detach_pds; 64762306a36Sopenharmony_ci } 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci ret = rproc_add(rproc); 65062306a36Sopenharmony_ci if (ret) 65162306a36Sopenharmony_ci goto remove_iris; 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci return 0; 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ciremove_iris: 65662306a36Sopenharmony_ci qcom_iris_remove(wcnss->iris); 65762306a36Sopenharmony_cidetach_pds: 65862306a36Sopenharmony_ci wcnss_release_pds(wcnss); 65962306a36Sopenharmony_cifree_rproc: 66062306a36Sopenharmony_ci rproc_free(rproc); 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci return ret; 66362306a36Sopenharmony_ci} 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_cistatic void wcnss_remove(struct platform_device *pdev) 66662306a36Sopenharmony_ci{ 66762306a36Sopenharmony_ci struct qcom_wcnss *wcnss = platform_get_drvdata(pdev); 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci qcom_iris_remove(wcnss->iris); 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci rproc_del(wcnss->rproc); 67262306a36Sopenharmony_ci 67362306a36Sopenharmony_ci qcom_remove_sysmon_subdev(wcnss->sysmon); 67462306a36Sopenharmony_ci qcom_remove_smd_subdev(wcnss->rproc, &wcnss->smd_subdev); 67562306a36Sopenharmony_ci wcnss_release_pds(wcnss); 67662306a36Sopenharmony_ci rproc_free(wcnss->rproc); 67762306a36Sopenharmony_ci} 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_cistatic const struct of_device_id wcnss_of_match[] = { 68062306a36Sopenharmony_ci { .compatible = "qcom,riva-pil", &riva_data }, 68162306a36Sopenharmony_ci { .compatible = "qcom,pronto-v1-pil", &pronto_v1_data }, 68262306a36Sopenharmony_ci { .compatible = "qcom,pronto-v2-pil", &pronto_v2_data }, 68362306a36Sopenharmony_ci { .compatible = "qcom,pronto-v3-pil", &pronto_v3_data }, 68462306a36Sopenharmony_ci { }, 68562306a36Sopenharmony_ci}; 68662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, wcnss_of_match); 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_cistatic struct platform_driver wcnss_driver = { 68962306a36Sopenharmony_ci .probe = wcnss_probe, 69062306a36Sopenharmony_ci .remove_new = wcnss_remove, 69162306a36Sopenharmony_ci .driver = { 69262306a36Sopenharmony_ci .name = "qcom-wcnss-pil", 69362306a36Sopenharmony_ci .of_match_table = wcnss_of_match, 69462306a36Sopenharmony_ci }, 69562306a36Sopenharmony_ci}; 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_cimodule_platform_driver(wcnss_driver); 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm Peripheral Image Loader for Wireless Subsystem"); 70062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 701