From 5803201cab1a4166a6cec88b4c4178edffb86db0 Mon Sep 17 00:00:00 2001 From: Lijuan Gao Date: Fri, 8 Jan 2021 14:27:16 +0800 Subject: [PATCH] soc: qcom: add snapshot of MBA based modem PIL Add snapshot of mba based modem pil driver based on 4.19 to 5.4 kernel. This is snapshot of the MBA base modem PIL driver as of msm-4.19 'commit <4a976aa23020441b8137243eb58778c6d7487ab1> ("")'. Move to upstream scm call API. Handle irq registration inside pil-q6v5 driver. Change-Id: I4db1614a94cd24410f654ab68f4ce68e60d45cdb Signed-off-by: Lijuan Gao --- drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/pil-msa.c | 1008 +++++++++++++++++++++++++++++++ drivers/soc/qcom/pil-msa.h | 56 ++ drivers/soc/qcom/pil-q6v5-mss.c | 810 +++++++++++++++++++++++++ drivers/soc/qcom/pil-q6v5.c | 839 +++++++++++++++++++++++++ drivers/soc/qcom/pil-q6v5.h | 84 +++ 7 files changed, 2807 insertions(+) create mode 100644 drivers/soc/qcom/pil-msa.c create mode 100644 drivers/soc/qcom/pil-msa.h create mode 100644 drivers/soc/qcom/pil-q6v5-mss.c create mode 100644 drivers/soc/qcom/pil-q6v5.c create mode 100644 drivers/soc/qcom/pil-q6v5.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index e64a1ede29bc..04855f925da3 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -321,6 +321,15 @@ config MSM_PIL_SSR_GENERIC or a fatal error. Subsystems include LPASS, Venus, VPU, WCNSS and BCSS. +config MSM_PIL_MSS_QDSP6V5 + tristate "MSS QDSP6v5 (Hexagon) Boot Support" + depends on MSM_PIL && MSM_SUBSYSTEM_RESTART + help + Support for booting and shutting down QDSP6v5 (Hexagon) processors + in modem subsystems. If you would like to make or receive phone + calls then say Y here. + If unsure, say N. + config MSM_SERVICE_LOCATOR tristate "Service Locator" select QCOM_QMI_HELPERS diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 55133b4f9fa6..fc105b0abb0b 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_MSM_SUBSYSTEM_RESTART) += subsystem_restart.o obj-$(CONFIG_MSM_CDSP_LOADER) += qdsp6v2/ subsystem_restart-y := msm_subsystem_restart.o subsystem_notif.o ramdump.o sysmon-qmi.o obj-$(CONFIG_MSM_PIL_SSR_GENERIC) += subsys-pil-tz.o +obj-$(CONFIG_MSM_PIL_MSS_QDSP6V5) += pil-q6v5.o pil-msa.o pil-q6v5-mss.o obj-$(CONFIG_MSM_SERVICE_NOTIFIER) += service-notifier.o obj-$(CONFIG_MSM_SERVICE_LOCATOR) += service-locator.o obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o diff --git a/drivers/soc/qcom/pil-msa.c b/drivers/soc/qcom/pil-msa.c new file mode 100644 index 000000000000..5740ff1f09b0 --- /dev/null +++ b/drivers/soc/qcom/pil-msa.c @@ -0,0 +1,1008 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "peripheral-loader.h" +#include "pil-q6v5.h" +#include "pil-msa.h" + +/* Q6 Register Offsets */ +#define QDSP6SS_RST_EVB 0x010 +#define QDSP6SS_DBG_CFG 0x018 +#define QDSP6SS_NMI_CFG 0x40 + +/* AXI Halting Registers */ +#define MSS_Q6_HALT_BASE 0x180 +#define MSS_MODEM_HALT_BASE 0x200 +#define MSS_NC_HALT_BASE 0x280 + +/* RMB Status Register Values */ +#define STATUS_PBL_SUCCESS 0x1 +#define STATUS_XPU_UNLOCKED 0x1 +#define STATUS_XPU_UNLOCKED_SCRIBBLED 0x2 + +/* PBL/MBA interface registers */ +#define RMB_MBA_IMAGE 0x00 +#define RMB_PBL_STATUS 0x04 +#define RMB_MBA_COMMAND 0x08 +#define RMB_MBA_STATUS 0x0C +#define RMB_PMI_META_DATA 0x10 +#define RMB_PMI_CODE_START 0x14 +#define RMB_PMI_CODE_LENGTH 0x18 +#define RMB_PROTOCOL_VERSION 0x1C +#define RMB_MBA_DEBUG_INFORMATION 0x20 + +#define POLL_INTERVAL_US 50 + +#define CMD_META_DATA_READY 0x1 +#define CMD_LOAD_READY 0x2 +#define CMD_PILFAIL_NFY_MBA 0xffffdead + +#define STATUS_META_DATA_AUTH_SUCCESS 0x3 +#define STATUS_AUTH_COMPLETE 0x4 +#define STATUS_MBA_UNLOCKED 0x6 + +/* External BHS */ +#define EXTERNAL_BHS_ON BIT(0) +#define EXTERNAL_BHS_STATUS BIT(4) +#define BHS_TIMEOUT_US 50 + +#define MSS_RESTART_PARAM_ID 0x2 +#define MSS_RESTART_ID 0xA + +#define MSS_MAGIC 0XAABADEAD + +/* Timeout value for MBA boot when minidump is enabled */ +#define MBA_ENCRYPTION_TIMEOUT 3000 +enum scm_cmd { + PAS_MEM_SETUP_CMD = 2, +}; + +static int pbl_mba_boot_timeout_ms = 1000; +module_param(pbl_mba_boot_timeout_ms, int, 0644); + +static int modem_auth_timeout_ms = 10000; +module_param(modem_auth_timeout_ms, int, 0644); + +/* If set to 0xAABADEAD, MBA failures trigger a kernel panic */ +static uint modem_trigger_panic; +module_param(modem_trigger_panic, uint, 0644); + +/* To set the modem debug cookie in DBG_CFG register for debugging */ +static uint modem_dbg_cfg; +module_param(modem_dbg_cfg, uint, 0644); + +static void modem_log_rmb_regs(void __iomem *base) +{ + pr_err("RMB_MBA_IMAGE: %08x\n", readl_relaxed(base + RMB_MBA_IMAGE)); + pr_err("RMB_PBL_STATUS: %08x\n", readl_relaxed(base + RMB_PBL_STATUS)); + pr_err("RMB_MBA_COMMAND: %08x\n", + readl_relaxed(base + RMB_MBA_COMMAND)); + pr_err("RMB_MBA_STATUS: %08x\n", readl_relaxed(base + RMB_MBA_STATUS)); + pr_err("RMB_PMI_META_DATA: %08x\n", + readl_relaxed(base + RMB_PMI_META_DATA)); + pr_err("RMB_PMI_CODE_START: %08x\n", + readl_relaxed(base + RMB_PMI_CODE_START)); + pr_err("RMB_PMI_CODE_LENGTH: %08x\n", + readl_relaxed(base + RMB_PMI_CODE_LENGTH)); + pr_err("RMB_PROTOCOL_VERSION: %08x\n", + readl_relaxed(base + RMB_PROTOCOL_VERSION)); + pr_err("RMB_MBA_DEBUG_INFORMATION: %08x\n", + readl_relaxed(base + RMB_MBA_DEBUG_INFORMATION)); + + if (modem_trigger_panic == MSS_MAGIC) + panic("%s: System ramdump is needed!!!\n", __func__); +} + +static int pil_mss_power_up(struct q6v5_data *drv) +{ + int ret = 0; + u32 regval; + + if (drv->cxrail_bhs) { + regval = readl_relaxed(drv->cxrail_bhs); + regval |= EXTERNAL_BHS_ON; + writel_relaxed(regval, drv->cxrail_bhs); + + ret = readl_poll_timeout(drv->cxrail_bhs, regval, + regval & EXTERNAL_BHS_STATUS, 1, BHS_TIMEOUT_US); + } + + return ret; +} + +static int pil_mss_power_down(struct q6v5_data *drv) +{ + u32 regval; + + if (drv->cxrail_bhs) { + regval = readl_relaxed(drv->cxrail_bhs); + regval &= ~EXTERNAL_BHS_ON; + writel_relaxed(regval, drv->cxrail_bhs); + } + + return 0; +} + +static int pil_mss_enable_clks(struct q6v5_data *drv) +{ + int ret; + + ret = clk_prepare_enable(drv->ahb_clk); + if (ret) + goto err_ahb_clk; + ret = clk_prepare_enable(drv->axi_clk); + if (ret) + goto err_axi_clk; + ret = clk_prepare_enable(drv->rom_clk); + if (ret) + goto err_rom_clk; + ret = clk_prepare_enable(drv->gpll0_mss_clk); + if (ret) + goto err_gpll0_mss_clk; + ret = clk_prepare_enable(drv->snoc_axi_clk); + if (ret) + goto err_snoc_axi_clk; + ret = clk_prepare_enable(drv->mnoc_axi_clk); + if (ret) + goto err_mnoc_axi_clk; + return 0; +err_mnoc_axi_clk: + clk_disable_unprepare(drv->mnoc_axi_clk); +err_snoc_axi_clk: + clk_disable_unprepare(drv->snoc_axi_clk); +err_gpll0_mss_clk: + clk_disable_unprepare(drv->gpll0_mss_clk); +err_rom_clk: + clk_disable_unprepare(drv->rom_clk); +err_axi_clk: + clk_disable_unprepare(drv->axi_clk); +err_ahb_clk: + clk_disable_unprepare(drv->ahb_clk); + return ret; +} + +static void pil_mss_disable_clks(struct q6v5_data *drv) +{ + clk_disable_unprepare(drv->mnoc_axi_clk); + clk_disable_unprepare(drv->snoc_axi_clk); + clk_disable_unprepare(drv->gpll0_mss_clk); + clk_disable_unprepare(drv->rom_clk); + clk_disable_unprepare(drv->axi_clk); + if (!drv->ahb_clk_vote) + clk_disable_unprepare(drv->ahb_clk); +} + +static void pil_mss_pdc_sync(struct q6v5_data *drv, bool pdc_sync) +{ + u32 val = 0; + u32 mss_pdc_mask = BIT(drv->mss_pdc_offset); + + if (drv->pdc_sync) { + val = readl_relaxed(drv->pdc_sync); + if (pdc_sync) + val |= mss_pdc_mask; + else + val &= ~mss_pdc_mask; + writel_relaxed(val, drv->pdc_sync); + /* Ensure PDC is written before next write */ + wmb(); + udelay(2); + } +} + +static void pil_mss_alt_reset(struct q6v5_data *drv, u32 val) +{ + if (drv->alt_reset) { + writel_relaxed(val, drv->alt_reset); + /* Ensure alt reset is written before restart reg */ + wmb(); + udelay(2); + } +} + +static int pil_mss_restart_reg(struct q6v5_data *drv, u32 mss_restart) +{ + int ret = 0; + + if (drv->restart_reg && !drv->restart_reg_sec) { + writel_relaxed(mss_restart, drv->restart_reg); + /* Ensure physical address access is done before returning.*/ + mb(); + udelay(2); + } else if (drv->restart_reg_sec) { + ret = qcom_scm_pas_mss_reset(mss_restart); + if (ret) + pr_err("Secure MSS restart failed\n"); + } + + return ret; +} + +int pil_mss_assert_resets(struct q6v5_data *drv) +{ + int ret = 0; + + pil_mss_pdc_sync(drv, 1); + pil_mss_alt_reset(drv, 1); + if (drv->reset_clk) { + pil_mss_disable_clks(drv); + if (drv->ahb_clk_vote) + clk_disable_unprepare(drv->ahb_clk); + } + + ret = pil_mss_restart_reg(drv, true); + + return ret; +} + +int pil_mss_deassert_resets(struct q6v5_data *drv) +{ + int ret = 0; + + ret = pil_mss_restart_reg(drv, 0); + if (ret) + return ret; + /* Wait 6 32kHz sleep cycles for reset */ + udelay(200); + + if (drv->reset_clk) + pil_mss_enable_clks(drv); + pil_mss_alt_reset(drv, 0); + pil_mss_pdc_sync(drv, false); + + return ret; +} + +static int pil_msa_wait_for_mba_ready(struct q6v5_data *drv) +{ + struct device *dev = drv->desc.dev; + int ret; + u32 status; + u64 val; + + if (of_property_read_bool(dev->of_node, "qcom,minidump-id")) + pbl_mba_boot_timeout_ms = MBA_ENCRYPTION_TIMEOUT; + + val = pil_is_timeout_disabled() ? 0 : pbl_mba_boot_timeout_ms * 1000; + + /* Wait for PBL completion. */ + ret = readl_poll_timeout(drv->rmb_base + RMB_PBL_STATUS, status, + status != 0, POLL_INTERVAL_US, val); + if (ret) { + dev_err(dev, "PBL boot timed out (rc:%d)\n", ret); + return ret; + } + if (status != STATUS_PBL_SUCCESS) { + dev_err(dev, "PBL returned unexpected status %d\n", status); + return -EINVAL; + } + + /* Wait for MBA completion. */ + ret = readl_poll_timeout(drv->rmb_base + RMB_MBA_STATUS, status, + status != 0, POLL_INTERVAL_US, val); + if (ret) { + dev_err(dev, "MBA boot timed out (rc:%d)\n", ret); + return ret; + } + if (status != STATUS_XPU_UNLOCKED && + status != STATUS_XPU_UNLOCKED_SCRIBBLED) { + dev_err(dev, "MBA returned unexpected status %d\n", status); + return -EINVAL; + } + + return 0; +} + +int pil_mss_shutdown(struct pil_desc *pil) +{ + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + int ret = 0; + + if (drv->axi_halt_base) { + pil_q6v5_halt_axi_port(pil, + drv->axi_halt_base + MSS_Q6_HALT_BASE); + pil_q6v5_halt_axi_port(pil, + drv->axi_halt_base + MSS_MODEM_HALT_BASE); + pil_q6v5_halt_axi_port(pil, + drv->axi_halt_base + MSS_NC_HALT_BASE); + } + + if (drv->axi_halt_q6) + pil_q6v5_halt_axi_port(pil, drv->axi_halt_q6); + if (drv->axi_halt_mss) + pil_q6v5_halt_axi_port(pil, drv->axi_halt_mss); + if (drv->axi_halt_nc) + pil_q6v5_halt_axi_port(pil, drv->axi_halt_nc); + + /* + * Software workaround to avoid high MX current during LPASS/MSS + * restart. + */ + if (drv->mx_spike_wa && drv->ahb_clk_vote) { + ret = clk_prepare_enable(drv->ahb_clk); + if (!ret) + assert_clamps(pil); + else + dev_err(pil->dev, "error turning ON AHB clock(rc:%d)\n", + ret); + } + + pil_mss_pdc_sync(drv, true); + /* Wait 6 32kHz sleep cycles for PDC SYNC true */ + udelay(200); + pil_mss_restart_reg(drv, 1); + /* Wait 6 32kHz sleep cycles for reset */ + udelay(200); + ret = pil_mss_restart_reg(drv, 0); + /* Wait 6 32kHz sleep cycles for reset false */ + udelay(200); + pil_mss_pdc_sync(drv, false); + + if (drv->is_booted) { + pil_mss_disable_clks(drv); + pil_mss_power_down(drv); + drv->is_booted = false; + } + + return ret; +} + +int __pil_mss_deinit_image(struct pil_desc *pil, bool err_path) +{ + struct modem_data *drv = dev_get_drvdata(pil->dev); + struct q6v5_data *q6_drv = container_of(pil, struct q6v5_data, desc); + int ret = 0; + struct device *dma_dev = drv->mba_mem_dev_fixed ?: &drv->mba_mem_dev; + s32 status; + u64 val = pil_is_timeout_disabled() ? 0 : pbl_mba_boot_timeout_ms * 1000; + + if (err_path) { + writel_relaxed(CMD_PILFAIL_NFY_MBA, + drv->rmb_base + RMB_MBA_COMMAND); + ret = readl_poll_timeout(drv->rmb_base + RMB_MBA_STATUS, status, + status == STATUS_MBA_UNLOCKED || status < 0, + POLL_INTERVAL_US, val); + if (ret) + dev_err(pil->dev, "MBA region unlock timed out(rc:%d)\n", + ret); + else if (status < 0) + dev_err(pil->dev, "MBA unlock returned err status: %d\n", + status); + } + + ret = pil_mss_shutdown(pil); + + if (q6_drv->ahb_clk_vote) + clk_disable_unprepare(q6_drv->ahb_clk); + + /* In case of any failure where reclaiming MBA and DP memory + * could not happen, free the memory here + */ + if (drv->q6->mba_dp_virt && !drv->mba_mem_dev_fixed) { + if (pil->subsys_vmid > 0) + pil_assign_mem_to_linux(pil, drv->q6->mba_dp_phys, + drv->q6->mba_dp_size); + dma_free_attrs(dma_dev, drv->q6->mba_dp_size, + drv->q6->mba_dp_virt, drv->q6->mba_dp_phys, + drv->attrs_dma); + drv->q6->mba_dp_virt = NULL; + } + + return ret; +} + +int pil_mss_deinit_image(struct pil_desc *pil) +{ + return __pil_mss_deinit_image(pil, true); +} + +int pil_mss_make_proxy_votes(struct pil_desc *pil) +{ + int ret; + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + int uv = 0; + + ret = of_property_read_u32(pil->dev->of_node, "vdd_mx-uV", &uv); + if (ret) { + dev_err(pil->dev, "missing vdd_mx-uV property(rc:%d)\n", ret); + return ret; + } + + ret = regulator_set_voltage(drv->vreg_mx, uv, INT_MAX); + if (ret) { + dev_err(pil->dev, "Failed to request vreg_mx voltage(rc:%d)\n", + ret); + return ret; + } + + ret = regulator_enable(drv->vreg_mx); + if (ret) { + dev_err(pil->dev, "Failed to enable vreg_mx(rc:%d)\n", ret); + regulator_set_voltage(drv->vreg_mx, 0, INT_MAX); + return ret; + } + + if (drv->vreg) { + ret = of_property_read_u32(pil->dev->of_node, "vdd_mss-uV", + &uv); + if (ret) { + dev_err(pil->dev, + "missing vdd_mss-uV property(rc:%d)\n", ret); + goto out; + } + + ret = regulator_set_voltage(drv->vreg, uv, + INT_MAX); + if (ret) { + dev_err(pil->dev, "Failed to set vreg voltage(rc:%d)\n", + ret); + goto out; + } + + ret = regulator_set_load(drv->vreg, 100000); + if (ret < 0) { + dev_err(pil->dev, "Failed to set vreg mode(rc:%d)\n", + ret); + goto out; + } + ret = regulator_enable(drv->vreg); + if (ret) { + dev_err(pil->dev, "Failed to enable vreg(rc:%d)\n", + ret); + regulator_set_voltage(drv->vreg, 0, INT_MAX); + goto out; + } + } + + ret = pil_q6v5_make_proxy_votes(pil); + if (ret && drv->vreg) { + regulator_disable(drv->vreg); + regulator_set_voltage(drv->vreg, 0, INT_MAX); + } +out: + if (ret) { + regulator_disable(drv->vreg_mx); + regulator_set_voltage(drv->vreg_mx, 0, INT_MAX); + } + + return ret; +} + +void pil_mss_remove_proxy_votes(struct pil_desc *pil) +{ + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + + pil_q6v5_remove_proxy_votes(pil); + regulator_disable(drv->vreg_mx); + regulator_set_voltage(drv->vreg_mx, 0, INT_MAX); + if (drv->vreg) { + regulator_disable(drv->vreg); + regulator_set_voltage(drv->vreg, 0, INT_MAX); + } +} + +static int pil_mss_mem_setup(struct pil_desc *pil, + phys_addr_t addr, size_t size) +{ + struct modem_data *md = dev_get_drvdata(pil->dev); + + if (!md->subsys_desc.pil_mss_memsetup) + return 0; + return qcom_scm_pas_mem_setup(md->pas_id, addr, size); +} + +static int pil_mss_reset(struct pil_desc *pil) +{ + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + phys_addr_t start_addr = pil_get_entry_addr(pil); + u32 debug_val = 0; + int ret; + + trace_pil_func(__func__); + if (drv->mba_dp_phys) + start_addr = drv->mba_dp_phys; + + /* + * Bring subsystem out of reset and enable required + * regulators and clocks. + */ + ret = pil_mss_power_up(drv); + if (ret) + goto err_power; + + ret = pil_mss_enable_clks(drv); + if (ret) + goto err_clks; + + if (!pil->minidump_ss || !pil->modem_ssr) { + /* Save state of modem debug register before full reset */ + debug_val = readl_relaxed(drv->reg_base + QDSP6SS_DBG_CFG); + } + + /* Assert reset to subsystem */ + pil_mss_assert_resets(drv); + /* Wait 6 32kHz sleep cycles for reset */ + udelay(200); + ret = pil_mss_deassert_resets(drv); + if (ret) + goto err_restart; + + if (!pil->minidump_ss || !pil->modem_ssr) { + writel_relaxed(debug_val, drv->reg_base + QDSP6SS_DBG_CFG); + if (modem_dbg_cfg) + writel_relaxed(modem_dbg_cfg, + drv->reg_base + QDSP6SS_DBG_CFG); + } + + /* Program Image Address */ + if (drv->self_auth) { + writel_relaxed(start_addr, drv->rmb_base + RMB_MBA_IMAGE); + /* + * Ensure write to RMB base occurs before reset + * is released. + */ + mb(); + } else { + writel_relaxed((start_addr >> 4) & 0x0FFFFFF0, + drv->reg_base + QDSP6SS_RST_EVB); + } + + /* Program DP Address */ + if (drv->dp_size) { + writel_relaxed(start_addr + SZ_1M, drv->rmb_base + + RMB_PMI_CODE_START); + writel_relaxed(drv->dp_size, drv->rmb_base + + RMB_PMI_CODE_LENGTH); + } else { + writel_relaxed(0, drv->rmb_base + RMB_PMI_CODE_START); + writel_relaxed(0, drv->rmb_base + RMB_PMI_CODE_LENGTH); + } + /* Make sure RMB regs are written before bringing modem out of reset */ + mb(); + + ret = pil_q6v5_reset(pil); + if (ret) + goto err_q6v5_reset; + + /* Wait for MBA to start. Check for PBL and MBA errors while waiting. */ + if (drv->self_auth) { + ret = pil_msa_wait_for_mba_ready(drv); + if (ret) + goto err_q6v5_reset; + } + + dev_info(pil->dev, "MBA boot done\n"); + drv->is_booted = true; + + return 0; + +err_q6v5_reset: + modem_log_rmb_regs(drv->rmb_base); +err_restart: + pil_mss_disable_clks(drv); + if (drv->ahb_clk_vote) + clk_disable_unprepare(drv->ahb_clk); +err_clks: + pil_mss_power_down(drv); +err_power: + return ret; +} + +int pil_mss_reset_load_mba(struct pil_desc *pil) +{ + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + struct modem_data *md = dev_get_drvdata(pil->dev); + const struct firmware *fw = NULL, *dp_fw = NULL; + char fw_name_legacy[10] = "mba.b00"; + char fw_name[10] = "mba.mbn"; + char *dp_name = "msadp"; + char *fw_name_p; + void *mba_dp_virt; + dma_addr_t mba_dp_phys, mba_dp_phys_end; + int ret; + const u8 *data; + struct device *dma_dev = md->mba_mem_dev_fixed ?: &md->mba_mem_dev; + + trace_pil_func(__func__); + if (drv->mba_dp_virt && md->mba_mem_dev_fixed) + goto mss_reset; + fw_name_p = drv->non_elf_image ? fw_name_legacy : fw_name; + ret = request_firmware(&fw, fw_name_p, pil->dev); + if (ret) { + dev_err(pil->dev, "Failed to locate %s (rc:%d)\n", + fw_name_p, ret); + return ret; + } + + data = fw ? fw->data : NULL; + if (!data) { + dev_err(pil->dev, "MBA data is NULL\n"); + ret = -ENOMEM; + goto err_invalid_fw; + } + + drv->mba_dp_size = SZ_1M; + + arch_setup_dma_ops(dma_dev, 0, 0, NULL, 0); + + dma_dev->coherent_dma_mask = DMA_BIT_MASK(sizeof(dma_addr_t) * 8); + + md->attrs_dma = 0; + md->attrs_dma |= DMA_ATTR_SKIP_ZEROING; + + ret = request_firmware(&dp_fw, dp_name, pil->dev); + if (ret) { + dev_warn(pil->dev, "Debug policy not present - %s. Continue.\n", + dp_name); + } else { + if (!dp_fw || !dp_fw->data) { + dev_err(pil->dev, "Invalid DP firmware\n"); + ret = -ENOMEM; + goto err_invalid_fw; + } + drv->dp_size = dp_fw->size; + drv->mba_dp_size += drv->dp_size; + drv->mba_dp_size = ALIGN(drv->mba_dp_size, SZ_4K); + } + + mba_dp_virt = dma_alloc_attrs(dma_dev, drv->mba_dp_size, &mba_dp_phys, + GFP_KERNEL, md->attrs_dma); + if (!mba_dp_virt) { + dev_err(pil->dev, "%s MBA/DP buffer allocation %zx bytes failed\n", + __func__, drv->mba_dp_size); + ret = -ENOMEM; + goto err_invalid_fw; + } + + /* Make sure there are no mappings in PKMAP and fixmap */ + kmap_flush_unused(); + + drv->mba_dp_phys = mba_dp_phys; + drv->mba_dp_virt = mba_dp_virt; + mba_dp_phys_end = mba_dp_phys + drv->mba_dp_size; + + dev_info(pil->dev, "Loading MBA and DP (if present) from %pa to %pa\n", + &mba_dp_phys, &mba_dp_phys_end); + + /* Load the MBA image into memory */ + if (fw->size <= SZ_1M) { + /* Ensures memcpy is done for max 1MB fw size */ + memcpy(mba_dp_virt, data, fw->size); + } else { + dev_err(pil->dev, "%s fw image loading into memory is failed due to fw size overflow\n", + __func__); + ret = -EINVAL; + goto err_mba_data; + } + /* Ensure memcpy of the MBA memory is done before loading the DP */ + wmb(); + + /* Load the DP image into memory */ + if (drv->mba_dp_size > SZ_1M) { + memcpy(mba_dp_virt + SZ_1M, dp_fw->data, dp_fw->size); + /* Ensure memcpy is done before powering up modem */ + wmb(); + } + + if (pil->subsys_vmid > 0) { + ret = pil_assign_mem_to_subsys(pil, drv->mba_dp_phys, + drv->mba_dp_size); + if (ret) { + pr_err("scm_call to unprotect MBA and DP mem failed(rc:%d)\n", + ret); + goto err_mba_data; + } + } + if (dp_fw) + release_firmware(dp_fw); + release_firmware(fw); + dp_fw = NULL; + fw = NULL; + +mss_reset: + ret = pil_mss_reset(pil); + if (ret) { + dev_err(pil->dev, "MBA boot failed(rc:%d)\n", ret); + goto err_mss_reset; + } + + return 0; + +err_mss_reset: + if (pil->subsys_vmid > 0) + pil_assign_mem_to_linux(pil, drv->mba_dp_phys, + drv->mba_dp_size); +err_mba_data: + dma_free_attrs(dma_dev, drv->mba_dp_size, drv->mba_dp_virt, + drv->mba_dp_phys, md->attrs_dma); +err_invalid_fw: + if (dp_fw) + release_firmware(dp_fw); + if (fw) + release_firmware(fw); + drv->mba_dp_virt = NULL; + return ret; +} + +int pil_mss_debug_reset(struct pil_desc *pil) +{ + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + u32 encryption_status; + int ret; + + + if (!pil->minidump_ss) + return 0; + + encryption_status = pil->minidump_ss->encryption_status; + + if ((pil->minidump_ss->md_ss_enable_status != MD_SS_ENABLED) || + encryption_status == MD_SS_ENCR_NOTREQ) + return 0; + + /* + * Bring subsystem out of reset and enable required + * regulators and clocks. + */ + ret = pil_mss_enable_clks(drv); + if (ret) + return ret; + + if (pil->minidump_ss) { + writel_relaxed(0x1, drv->reg_base + QDSP6SS_NMI_CFG); + /* Let write complete before proceeding */ + mb(); + udelay(2); + } + /* Assert reset to subsystem */ + pil_mss_restart_reg(drv, true); + /* Wait 6 32kHz sleep cycles for reset */ + udelay(200); + ret = pil_mss_restart_reg(drv, false); + if (ret) + goto err_restart; + /* Let write complete before proceeding */ + mb(); + udelay(200); + ret = pil_q6v5_reset(pil); + /* + * Need to Wait for timeout for debug reset sequence to + * complete before returning + */ + pr_info("Minidump: waiting encryption to complete\n"); + msleep(13000); + if (pil->minidump_ss) { + writel_relaxed(0x2, drv->reg_base + QDSP6SS_NMI_CFG); + /* Let write complete before proceeding */ + mb(); + udelay(200); + } + if (ret) + goto err_restart; + return 0; +err_restart: + pil_mss_disable_clks(drv); + if (drv->ahb_clk_vote) + clk_disable_unprepare(drv->ahb_clk); + return ret; +} + +static int pil_msa_auth_modem_mdt(struct pil_desc *pil, const u8 *metadata, + size_t size) +{ + struct modem_data *drv = dev_get_drvdata(pil->dev); + void *mdata_virt; + dma_addr_t mdata_phys; + s32 status; + int ret; + u64 val = pil_is_timeout_disabled() ? 0 : modem_auth_timeout_ms * 1000; + struct device *dma_dev = drv->mba_mem_dev_fixed ?: &drv->mba_mem_dev; + unsigned long attrs = 0; + + trace_pil_func(__func__); + dma_dev->coherent_dma_mask = DMA_BIT_MASK(sizeof(dma_addr_t) * 8); + attrs |= DMA_ATTR_SKIP_ZEROING; + /* Make metadata physically contiguous and 4K aligned. */ + mdata_virt = dma_alloc_attrs(dma_dev, size, &mdata_phys, + GFP_KERNEL, attrs); + if (!mdata_virt) { + dev_err(pil->dev, "MBA metadata buffer allocation failed\n"); + ret = -ENOMEM; + goto fail; + } + memcpy(mdata_virt, metadata, size); + /* wmb() ensures copy completes prior to starting authentication. */ + wmb(); + + if (pil->subsys_vmid > 0) { + ret = pil_assign_mem_to_subsys(pil, mdata_phys, + ALIGN(size, SZ_4K)); + if (ret) { + pr_err("scm_call to unprotect modem metadata mem failed(rc:%d)\n", + ret); + dma_free_attrs(dma_dev, size, mdata_virt, mdata_phys, + attrs); + goto fail; + } + } + + /* Initialize length counter to 0 */ + writel_relaxed(0, drv->rmb_base + RMB_PMI_CODE_LENGTH); + + /* Pass address of meta-data to the MBA and perform authentication */ + writel_relaxed(mdata_phys, drv->rmb_base + RMB_PMI_META_DATA); + writel_relaxed(CMD_META_DATA_READY, drv->rmb_base + RMB_MBA_COMMAND); + ret = readl_poll_timeout(drv->rmb_base + RMB_MBA_STATUS, status, + status == STATUS_META_DATA_AUTH_SUCCESS || status < 0, + POLL_INTERVAL_US, val); + if (ret) { + dev_err(pil->dev, "MBA authentication of headers timed out(rc:%d)\n", + ret); + } else if (status < 0) { + dev_err(pil->dev, "MBA returned error %d for headers\n", + status); + ret = -EINVAL; + } + + if (pil->subsys_vmid > 0) + pil_assign_mem_to_linux(pil, mdata_phys, ALIGN(size, SZ_4K)); + + dma_free_attrs(dma_dev, size, mdata_virt, mdata_phys, attrs); + + if (!ret) + return ret; + +fail: + modem_log_rmb_regs(drv->rmb_base); + if (drv->q6) { + pil_mss_shutdown(pil); + if (pil->subsys_vmid > 0) + pil_assign_mem_to_linux(pil, drv->q6->mba_dp_phys, + drv->q6->mba_dp_size); + if (drv->q6->mba_dp_virt && !drv->mba_mem_dev_fixed) { + dma_free_attrs(dma_dev, drv->q6->mba_dp_size, + drv->q6->mba_dp_virt, drv->q6->mba_dp_phys, + drv->attrs_dma); + drv->q6->mba_dp_virt = NULL; + } + + } + return ret; +} + +static int pil_msa_mss_reset_mba_load_auth_mdt(struct pil_desc *pil, + const u8 *metadata, size_t size) +{ + int ret; + + ret = pil_mss_reset_load_mba(pil); + if (ret) + return ret; + + return pil_msa_auth_modem_mdt(pil, metadata, size); +} + +static int pil_msa_mba_verify_blob(struct pil_desc *pil, phys_addr_t phy_addr, + size_t size) +{ + struct modem_data *drv = dev_get_drvdata(pil->dev); + s32 status; + u32 img_length = readl_relaxed(drv->rmb_base + RMB_PMI_CODE_LENGTH); + + /* Begin image authentication */ + if (img_length == 0) { + writel_relaxed(phy_addr, drv->rmb_base + RMB_PMI_CODE_START); + writel_relaxed(CMD_LOAD_READY, drv->rmb_base + RMB_MBA_COMMAND); + } + /* Increment length counter */ + img_length += size; + writel_relaxed(img_length, drv->rmb_base + RMB_PMI_CODE_LENGTH); + + status = readl_relaxed(drv->rmb_base + RMB_MBA_STATUS); + if (status < 0) { + dev_err(pil->dev, "MBA returned error %d\n", status); + modem_log_rmb_regs(drv->rmb_base); + return -EINVAL; + } + + return 0; +} + +static int pil_msa_mba_auth(struct pil_desc *pil) +{ + struct modem_data *drv = dev_get_drvdata(pil->dev); + struct q6v5_data *q6_drv = container_of(pil, struct q6v5_data, desc); + int ret; + struct device *dma_dev = drv->mba_mem_dev_fixed ?: &drv->mba_mem_dev; + s32 status; + u64 val = pil_is_timeout_disabled() ? 0 : modem_auth_timeout_ms * 1000; + + /* Wait for all segments to be authenticated or an error to occur */ + ret = readl_poll_timeout(drv->rmb_base + RMB_MBA_STATUS, status, + status == STATUS_AUTH_COMPLETE || status < 0, 50, val); + if (ret) { + dev_err(pil->dev, "MBA authentication of image timed out(rc:%d)\n", + ret); + } else if (status < 0) { + dev_err(pil->dev, "MBA returned error %d for image\n", status); + ret = -EINVAL; + } + + if (drv->q6) { + if (drv->q6->mba_dp_virt && !drv->mba_mem_dev_fixed) { + /* Reclaim MBA and DP (if allocated) memory. */ + if (pil->subsys_vmid > 0) + pil_assign_mem_to_linux(pil, + drv->q6->mba_dp_phys, + drv->q6->mba_dp_size); + dma_free_attrs(dma_dev, drv->q6->mba_dp_size, + drv->q6->mba_dp_virt, drv->q6->mba_dp_phys, + drv->attrs_dma); + + drv->q6->mba_dp_virt = NULL; + } + } + if (ret) + modem_log_rmb_regs(drv->rmb_base); + if (q6_drv->ahb_clk_vote) + clk_disable_unprepare(q6_drv->ahb_clk); + + return ret; +} + +/* + * To be used only if self-auth is disabled, or if the + * MBA image is loaded as segments and not in init_image. + */ +struct pil_reset_ops pil_msa_mss_ops = { + .proxy_vote = pil_mss_make_proxy_votes, + .proxy_unvote = pil_mss_remove_proxy_votes, + .auth_and_reset = pil_mss_reset, + .shutdown = pil_mss_shutdown, +}; + +/* + * To be used if self-auth is enabled and the MBA is to be loaded + * in init_image and the modem headers are also to be authenticated + * in init_image. Modem segments authenticated in auth_and_reset. + */ +struct pil_reset_ops pil_msa_mss_ops_selfauth = { + .init_image = pil_msa_mss_reset_mba_load_auth_mdt, + .proxy_vote = pil_mss_make_proxy_votes, + .proxy_unvote = pil_mss_remove_proxy_votes, + .mem_setup = pil_mss_mem_setup, + .verify_blob = pil_msa_mba_verify_blob, + .auth_and_reset = pil_msa_mba_auth, + .deinit_image = pil_mss_deinit_image, + .shutdown = pil_mss_shutdown, +}; + +/* + * To be used if the modem headers are to be authenticated + * in init_image, and the modem segments in auth_and_reset. + */ +struct pil_reset_ops pil_msa_femto_mba_ops = { + .init_image = pil_msa_auth_modem_mdt, + .verify_blob = pil_msa_mba_verify_blob, + .auth_and_reset = pil_msa_mba_auth, +}; diff --git a/drivers/soc/qcom/pil-msa.h b/drivers/soc/qcom/pil-msa.h new file mode 100644 index 000000000000..2bc66d8baed9 --- /dev/null +++ b/drivers/soc/qcom/pil-msa.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __MSM_PIL_MSA_H +#define __MSM_PIL_MSA_H + +#include + +#include "peripheral-loader.h" + +struct modem_data { + struct device *dev; + struct q6v5_data *q6; + struct subsys_device *subsys; + struct subsys_desc subsys_desc; + void *ramdump_dev; + void *minidump_dev; + bool crash_shutdown; + u32 pas_id; + bool ignore_errors; + struct completion err_ready; + struct completion stop_ack; + void __iomem *rmb_base; + struct clk *xo; + struct pil_desc desc; + struct device mba_mem_dev; + struct device *mba_mem_dev_fixed; + unsigned long attrs_dma; + int is_not_loadable; + unsigned int err_fatal_irq; + unsigned int err_ready_irq; + unsigned int stop_ack_irq; + unsigned int wdog_bite_irq; + unsigned int generic_irq; + int ramdump_disable_irq; + int shutdown_ack_irq; + int force_stop_bit; + struct qcom_smem_state *state; +}; + +extern struct pil_reset_ops pil_msa_mss_ops; +extern struct pil_reset_ops pil_msa_mss_ops_selfauth; +extern struct pil_reset_ops pil_msa_femto_mba_ops; + +int pil_mss_reset_load_mba(struct pil_desc *pil); +int pil_mss_make_proxy_votes(struct pil_desc *pil); +void pil_mss_remove_proxy_votes(struct pil_desc *pil); +int pil_mss_shutdown(struct pil_desc *pil); +int pil_mss_deinit_image(struct pil_desc *pil); +int __pil_mss_deinit_image(struct pil_desc *pil, bool err_path); +int pil_mss_assert_resets(struct q6v5_data *drv); +int pil_mss_deassert_resets(struct q6v5_data *drv); +int pil_mss_debug_reset(struct pil_desc *pil); +#endif diff --git a/drivers/soc/qcom/pil-q6v5-mss.c b/drivers/soc/qcom/pil-q6v5-mss.c new file mode 100644 index 000000000000..276f9807b612 --- /dev/null +++ b/drivers/soc/qcom/pil-q6v5-mss.c @@ -0,0 +1,810 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "peripheral-loader.h" +#include "pil-q6v5.h" +#include "pil-msa.h" + +#define PROXY_TIMEOUT_MS 10000 +#define MAX_SSR_REASON_LEN 256U +#define STOP_ACK_TIMEOUT_MS 1000 + +static int enable_debug; +module_param(enable_debug, int, 0644); + +#define subsys_to_drv(d) container_of(d, struct modem_data, subsys_desc) + +static void pil_enable_all_irqs(struct modem_data *drv); +static void pil_disable_all_irqs(struct modem_data *drv); + +static int wait_for_err_ready(struct modem_data *drv) +{ + int ret; + + /* + * If subsys is using generic_irq in which case err_ready_irq will be 0, + * don't return. + */ + if ((drv->generic_irq <= 0 && !drv->err_ready_irq) || + enable_debug == 1 || pil_is_timeout_disabled()) + return 0; + + ret = wait_for_completion_interruptible_timeout(&drv->err_ready, + msecs_to_jiffies(10000)); + if (!ret) { + pr_err("[%s]: Error ready timed out\n", drv->desc.name); + return -ETIMEDOUT; + } + + return 0; +} + +static void log_modem_sfr(struct modem_data *drv) +{ + size_t size; + char *smem_reason, reason[MAX_SSR_REASON_LEN]; + + if (drv->q6->smem_id == -1) + return; + + smem_reason = qcom_smem_get(QCOM_SMEM_HOST_ANY, drv->q6->smem_id, + &size); + if (IS_ERR(smem_reason) || !size) { + pr_err("modem SFR: (unknown, qcom_smem_get failed).\n"); + return; + } + if (!smem_reason[0]) { + pr_err("modem SFR: (unknown, empty string found).\n"); + return; + } + + strlcpy(reason, smem_reason, min(size, (size_t)MAX_SSR_REASON_LEN)); + pr_err("modem subsystem failure reason: %s.\n", reason); +} + +static void restart_modem(struct modem_data *drv) +{ + log_modem_sfr(drv); + drv->ignore_errors = true; + subsystem_restart_dev(drv->subsys); +} + +static irqreturn_t modem_err_ready_intr_handler(int irq, void *drv_data) +{ + struct modem_data *drv = drv_data; + + pr_info("Subsystem error monitoring/handling services are up from%s\n", + drv->subsys_desc.name); + complete(&drv->err_ready); + return IRQ_HANDLED; +} + +static irqreturn_t modem_err_fatal_intr_handler(int irq, void *drv_data) +{ + struct modem_data *drv = drv_data; + + /* Ignore if we're the one that set the force stop BIT */ + if (drv->crash_shutdown) + return IRQ_HANDLED; + + pr_err("Fatal error on the modem.\n"); + subsys_set_crash_status(drv->subsys, CRASH_STATUS_ERR_FATAL); + restart_modem(drv); + return IRQ_HANDLED; +} + +static irqreturn_t modem_stop_ack_intr_handler(int irq, void *drv_data) +{ + struct modem_data *drv = drv_data; + + pr_info("Received stop ack interrupt from modem\n"); + complete(&drv->stop_ack); + return IRQ_HANDLED; +} + +static irqreturn_t modem_shutdown_ack_intr_handler(int irq, void *drv_data) +{ + struct modem_data *drv = drv_data; + + pr_info("Received stop shutdown interrupt from modem\n"); + complete_shutdown_ack(&drv->subsys_desc); + return IRQ_HANDLED; +} + +static irqreturn_t modem_ramdump_disable_intr_handler(int irq, void *drv_data) +{ + struct modem_data *drv = drv_data; + + pr_info("Received ramdump disable interrupt from modem\n"); + drv->subsys_desc.ramdump_disable = 1; + return IRQ_HANDLED; +} + +static int modem_shutdown(const struct subsys_desc *subsys, bool force_stop) +{ + struct modem_data *drv = subsys_to_drv(subsys); + unsigned long ret; + + if (drv->is_not_loadable) + return 0; + + if (!subsys_get_crash_status(drv->subsys) && force_stop && + drv->force_stop_bit) { + qcom_smem_state_update_bits(drv->state, + BIT(drv->force_stop_bit), 1); + ret = wait_for_completion_timeout(&drv->stop_ack, + msecs_to_jiffies(STOP_ACK_TIMEOUT_MS)); + if (!ret) + pr_warn("Timed out on stop ack from modem.\n"); + qcom_smem_state_update_bits(drv->state, + BIT(subsys->force_stop_bit), 0); + } + + if (drv->ramdump_disable_irq) { + pr_warn("Ramdump disable value is %d\n", + drv->subsys_desc.ramdump_disable); + } + + pil_shutdown(&drv->q6->desc); + pil_disable_all_irqs(drv); + return 0; +} + +static int modem_powerup(const struct subsys_desc *subsys) +{ + struct modem_data *drv = subsys_to_drv(subsys); + int ret = 0; + + if (drv->is_not_loadable) + return 0; + /* + * At this time, the modem is shutdown. Therefore this function cannot + * run concurrently with the watchdog bite error handler, making it safe + * to unset the flag below. + */ + reinit_completion(&drv->stop_ack); + drv->subsys_desc.ramdump_disable = 0; + drv->ignore_errors = false; + drv->q6->desc.fw_name = subsys->fw_name; + ret = pil_boot(&drv->q6->desc); + if (ret) { + pr_err("pil_boot failed for %s\n", drv->subsys_desc.name); + return ret; + } + + pr_info("pil_boot is successful from %s and waiting for error ready\n", + drv->subsys_desc.name); + pil_enable_all_irqs(drv); + ret = wait_for_err_ready(drv); + if (ret) { + pr_err("%s failed to get error ready for %s\n", __func__, + drv->subsys_desc.name); + pil_shutdown(&drv->q6->desc); + pil_disable_all_irqs(drv); + } +} + +static void modem_crash_shutdown(const struct subsys_desc *subsys) +{ + struct modem_data *drv = subsys_to_drv(subsys); + + drv->crash_shutdown = true; + if (!subsys_get_crash_status(drv->subsys) && + subsys->force_stop_bit) { + qcom_smem_state_update_bits(subsys->state, + BIT(subsys->force_stop_bit), 1); + msleep(STOP_ACK_TIMEOUT_MS); + } +} + +static int modem_ramdump(int enable, const struct subsys_desc *subsys) +{ + struct modem_data *drv = subsys_to_drv(subsys); + int ret; + + if (!enable) + return 0; + + ret = pil_mss_make_proxy_votes(&drv->q6->desc); + if (ret) + return ret; + + ret = pil_mss_debug_reset(&drv->q6->desc); + if (ret) + return ret; + + pil_mss_remove_proxy_votes(&drv->q6->desc); + ret = pil_mss_make_proxy_votes(&drv->q6->desc); + if (ret) + return ret; + + ret = pil_mss_reset_load_mba(&drv->q6->desc); + if (ret) + return ret; + + ret = pil_do_ramdump(&drv->q6->desc, + drv->ramdump_dev, drv->minidump_dev); + if (ret < 0) + pr_err("Unable to dump modem fw memory (rc = %d).\n", ret); + + ret = __pil_mss_deinit_image(&drv->q6->desc, false); + if (ret < 0) + pr_err("Unable to free up resources (rc = %d).\n", ret); + + pil_mss_remove_proxy_votes(&drv->q6->desc); + return ret; +} + +static irqreturn_t modem_wdog_bite_intr_handler(int irq, void *dev_data) +{ + struct modem_data *drv = subsys_to_drv(dev_data); + + if (drv->ignore_errors) + return IRQ_HANDLED; + + pr_err("Watchdog bite received from modem software!\n"); + if (drv->subsys_desc.system_debug) + panic("%s: System ramdump requested. Triggering device restart!\n", + __func__); + subsys_set_crash_status(drv->subsys, CRASH_STATUS_WDOG_BITE); + restart_modem(drv); + return IRQ_HANDLED; +} + +static void pil_enable_all_irqs(struct modem_data *drv) +{ + if (drv->err_ready_irq) + enable_irq(drv->err_ready_irq); + if (drv->wdog_bite_irq) { + enable_irq(drv->wdog_bite_irq); + irq_set_irq_wake(drv->wdog_bite_irq, 1); + } + if (drv->err_fatal_irq) + enable_irq(drv->err_fatal_irq); + if (drv->stop_ack_irq) + enable_irq(drv->stop_ack_irq); + if (drv->shutdown_ack_irq) + enable_irq(drv->shutdown_ack_irq); + if (drv->ramdump_disable_irq) + enable_irq(drv->ramdump_disable_irq); + if (drv->generic_irq) { + enable_irq(drv->generic_irq); + irq_set_irq_wake(drv->generic_irq, 1); + } +} + +static void pil_disable_all_irqs(struct modem_data *drv) +{ + if (drv->err_ready_irq) + disable_irq(drv->err_ready_irq); + if (drv->wdog_bite_irq) { + disable_irq(drv->wdog_bite_irq); + irq_set_irq_wake(drv->wdog_bite_irq, 0); + } + if (drv->err_fatal_irq) + disable_irq(drv->err_fatal_irq); + if (drv->stop_ack_irq) + disable_irq(drv->stop_ack_irq); + if (drv->shutdown_ack_irq) + disable_irq(drv->shutdown_ack_irq); + if (drv->generic_irq) { + disable_irq(drv->generic_irq); + irq_set_irq_wake(drv->generic_irq, 0); + } +} + +static int __get_irq(struct platform_device *pdev, const char *prop, + unsigned int *irq) +{ + int irql = 0; + struct device_node *dnode = pdev->dev.of_node; + + if (of_property_match_string(dnode, "interrupt-names", prop) < 0) + return -ENOENT; + + irql = of_irq_get_byname(dnode, prop); + if (irql < 0) { + pr_err("[%s]: Error getting IRQ \"%s\"\n", pdev->name, + prop); + return irql; + } + *irq = irql; + return 0; +} + +static int __get_smem_state(struct modem_data *drv, const char *prop, + int *smem_bit) +{ + struct device_node *dnode = drv->dev->of_node; + + if (of_find_property(dnode, "qcom,smem-states", NULL)) { + drv->state = qcom_smem_state_get(drv->dev, prop, smem_bit); + if (IS_ERR_OR_NULL(drv->state)) { + pr_err("Could not get smem-states %s\n", prop); + return PTR_ERR(drv->state); + } + return 0; + } + return -ENOENT; +} + +static int pil_parse_irqs(struct platform_device *pdev) +{ + int ret; + struct modem_data *drv = platform_get_drvdata(pdev); + + ret = __get_irq(pdev, "qcom,err-fatal", &drv->err_fatal_irq); + if (ret && ret != -ENOENT) + return ret; + + ret = __get_irq(pdev, "qcom,err-ready", &drv->err_ready_irq); + if (ret && ret != -ENOENT) + return ret; + + ret = __get_irq(pdev, "qcom,stop-ack", &drv->stop_ack_irq); + if (ret && ret != -ENOENT) + return ret; + + ret = __get_irq(pdev, "qcom,ramdump-disabled", + &drv->ramdump_disable_irq); + if (ret && ret != -ENOENT) + return ret; + + ret = __get_irq(pdev, "qcom,shutdown-ack", &drv->shutdown_ack_irq); + if (ret && ret != -ENOENT) + return ret; + + ret = __get_irq(pdev, "qcom,wdog", &drv->wdog_bite_irq); + if (ret && ret != -ENOENT) + return ret; + + ret = __get_smem_state(drv, "qcom,force-stop", &drv->force_stop_bit); + if (ret && ret != -ENOENT) + return ret; + + if (of_property_read_bool(pdev->dev.of_node, + "qcom,pil-generic-irq-handler")) { + ret = platform_get_irq(pdev, 0); + if (ret > 0) + drv->generic_irq = ret; + } + + return 0; +} + +static int pil_setup_irqs(struct platform_device *pdev) +{ + int ret; + struct modem_data *drv = platform_get_drvdata(pdev); + + if (drv->err_fatal_irq) { + ret = devm_request_threaded_irq(&pdev->dev, drv->err_fatal_irq, + NULL, modem_err_fatal_intr_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + drv->desc.name, drv); + if (ret < 0) { + dev_err(&pdev->dev, "[%s]: Unable to register error fatal IRQ handler: %d, irq is %d\n", + drv->desc.name, ret, drv->err_fatal_irq); + return ret; + } + disable_irq(drv->err_fatal_irq); + } + + if (drv->stop_ack_irq) { + ret = devm_request_threaded_irq(&pdev->dev, drv->stop_ack_irq, + NULL, modem_stop_ack_intr_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + drv->desc.name, drv); + if (ret < 0) { + dev_err(&pdev->dev, "[%s]: Unable to register stop ack handler: %d\n", + drv->desc.name, ret); + return ret; + } + disable_irq(drv->stop_ack_irq); + } + + if (drv->wdog_bite_irq) { + ret = devm_request_irq(&pdev->dev, drv->wdog_bite_irq, + modem_wdog_bite_intr_handler, + IRQF_TRIGGER_RISING, drv->desc.name, drv); + if (ret < 0) { + dev_err(&pdev->dev, "[%s]: Unable to register wdog bite handler: %d\n", + drv->desc.name, ret); + return ret; + } + disable_irq(drv->wdog_bite_irq); + } + + if (drv->shutdown_ack_irq) { + ret = devm_request_threaded_irq(&pdev->dev, + drv->shutdown_ack_irq, + NULL, modem_shutdown_ack_intr_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + drv->desc.name, drv); + if (ret < 0) { + dev_err(&pdev->dev, "[%s]: Unable to register shutdown ack handler: %d\n", + drv->desc.name, ret); + return ret; + } + disable_irq(drv->shutdown_ack_irq); + } + + if (drv->ramdump_disable_irq) { + ret = devm_request_threaded_irq(drv->dev, + drv->ramdump_disable_irq, + NULL, modem_ramdump_disable_intr_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + drv->desc.name, drv); + if (ret < 0) { + dev_err(&pdev->dev, "[%s]: Unable to register shutdown ack handler: %d\n", + drv->desc.name, ret); + return ret; + } + disable_irq(drv->ramdump_disable_irq); + } + + if (drv->err_ready_irq) { + ret = devm_request_threaded_irq(drv->dev, + drv->err_ready_irq, + NULL, modem_err_ready_intr_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "error_ready_interrupt", drv); + if (ret < 0) { + dev_err(&pdev->dev, + "[%s]: Unable to register err ready handler\n", + drv->desc.name); + return ret; + } + disable_irq(drv->err_ready_irq); + } + + return 0; +} + +static int pil_subsys_init(struct modem_data *drv, + struct platform_device *pdev) +{ + int ret = -EINVAL; + + drv->dev = &pdev->dev; + drv->subsys_desc.name = "modem"; + drv->subsys_desc.dev = &pdev->dev; + drv->subsys_desc.owner = THIS_MODULE; + drv->subsys_desc.shutdown = modem_shutdown; + drv->subsys_desc.powerup = modem_powerup; + drv->subsys_desc.ramdump = modem_ramdump; + drv->subsys_desc.crash_shutdown = modem_crash_shutdown; + + if (IS_ERR_OR_NULL(drv->q6)) { + ret = PTR_ERR(drv->q6); + dev_err(&pdev->dev, "Pil q6 data is err %pK %d!!!\n", + drv->q6, ret); + goto err_subsys; + } + + drv->q6->desc.modem_ssr = false; + drv->q6->desc.signal_aop = of_property_read_bool(pdev->dev.of_node, + "qcom,signal-aop"); + if (drv->q6->desc.signal_aop) { + drv->q6->desc.cl.dev = &pdev->dev; + drv->q6->desc.cl.tx_block = true; + drv->q6->desc.cl.tx_tout = 1000; + drv->q6->desc.cl.knows_txdone = false; + drv->q6->desc.mbox = mbox_request_channel(&drv->q6->desc.cl, 0); + if (IS_ERR(drv->q6->desc.mbox)) { + ret = PTR_ERR(drv->q6->desc.mbox); + dev_err(&pdev->dev, "Failed to get mailbox channel %pK %d\n", + drv->q6->desc.mbox, ret); + goto err_subsys; + } + } + + drv->subsys = subsys_register(&drv->subsys_desc); + if (IS_ERR(drv->subsys)) { + ret = PTR_ERR(drv->subsys); + goto err_subsys; + } + + ret = pil_parse_irqs(pdev); + if (ret) { + subsys_unregister(drv->subsys); + goto err_subsys; + } + + drv->ramdump_dev = create_ramdump_device("modem", &pdev->dev); + if (!drv->ramdump_dev) { + pr_err("%s: Unable to create a modem ramdump device.\n", + __func__); + ret = -ENOMEM; + goto err_ramdump; + } + drv->minidump_dev = create_ramdump_device("md_modem", &pdev->dev); + if (!drv->minidump_dev) { + pr_err("%s: Unable to create a modem minidump device.\n", + __func__); + ret = -ENOMEM; + goto err_minidump; + } + + return 0; + +err_minidump: + destroy_ramdump_device(drv->ramdump_dev); +err_ramdump: + subsys_unregister(drv->subsys); +err_subsys: + return ret; +} + +static int pil_mss_loadable_init(struct modem_data *drv, + struct platform_device *pdev) +{ + struct q6v5_data *q6; + struct pil_desc *q6_desc; + struct resource *res; + struct property *prop; + int ret; + + q6 = pil_q6v5_init(pdev); + if (IS_ERR_OR_NULL(q6)) + return PTR_ERR(q6); + drv->q6 = q6; + drv->xo = q6->xo; + + q6_desc = &q6->desc; + q6_desc->owner = THIS_MODULE; + q6_desc->proxy_timeout = PROXY_TIMEOUT_MS; + + q6_desc->ops = &pil_msa_mss_ops; + + q6->reset_clk = of_property_read_bool(pdev->dev.of_node, + "qcom,reset-clk"); + q6->self_auth = of_property_read_bool(pdev->dev.of_node, + "qcom,pil-self-auth"); + if (q6->self_auth) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "rmb_base"); + q6->rmb_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(q6->rmb_base)) + return PTR_ERR(q6->rmb_base); + drv->rmb_base = q6->rmb_base; + q6_desc->ops = &pil_msa_mss_ops_selfauth; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "restart_reg"); + if (!res) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "restart_reg_sec"); + if (!res) { + dev_err(&pdev->dev, "No restart register defined\n"); + return -ENOMEM; + } + q6->restart_reg_sec = true; + } + + q6->restart_reg = devm_ioremap(&pdev->dev, + res->start, resource_size(res)); + if (!q6->restart_reg) + return -ENOMEM; + + q6->pdc_sync = NULL; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pdc_sync"); + if (res) { + q6->pdc_sync = devm_ioremap(&pdev->dev, + res->start, resource_size(res)); + if (of_property_read_u32(pdev->dev.of_node, + "qcom,mss_pdc_offset", &q6->mss_pdc_offset)) { + dev_err(&pdev->dev, + "Offset for MSS PDC not specified\n"); + return -EINVAL; + } + + } + + q6->alt_reset = NULL; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "alt_reset"); + if (res) { + q6->alt_reset = devm_ioremap(&pdev->dev, + res->start, resource_size(res)); + } + + q6->vreg = NULL; + + prop = of_find_property(pdev->dev.of_node, "vdd_mss-supply", NULL); + if (prop) { + q6->vreg = devm_regulator_get(&pdev->dev, "vdd_mss"); + if (IS_ERR(q6->vreg)) + return PTR_ERR(q6->vreg); + } + + q6->vreg_mx = devm_regulator_get(&pdev->dev, "vdd_mx"); + if (IS_ERR(q6->vreg_mx)) + return PTR_ERR(q6->vreg_mx); + prop = of_find_property(pdev->dev.of_node, "vdd_mx-uV", NULL); + if (!prop) { + dev_err(&pdev->dev, "Missing vdd_mx-uV property\n"); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "cxrail_bhs_reg"); + if (res) + q6->cxrail_bhs = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + + q6->ahb_clk = devm_clk_get(&pdev->dev, "iface_clk"); + if (IS_ERR(q6->ahb_clk)) + return PTR_ERR(q6->ahb_clk); + + q6->axi_clk = devm_clk_get(&pdev->dev, "bus_clk"); + if (IS_ERR(q6->axi_clk)) + return PTR_ERR(q6->axi_clk); + + q6->rom_clk = devm_clk_get(&pdev->dev, "mem_clk"); + if (IS_ERR(q6->rom_clk)) + return PTR_ERR(q6->rom_clk); + + ret = of_property_read_u32(pdev->dev.of_node, + "qcom,pas-id", &drv->pas_id); + if (ret) + dev_info(&pdev->dev, "No pas_id found.\n"); + + drv->subsys_desc.pil_mss_memsetup = + of_property_read_bool(pdev->dev.of_node, "qcom,pil-mss-memsetup"); + + /* Optional. */ + if (of_property_match_string(pdev->dev.of_node, + "qcom,active-clock-names", "gpll0_mss_clk") >= 0) + q6->gpll0_mss_clk = devm_clk_get(&pdev->dev, "gpll0_mss_clk"); + + if (of_property_match_string(pdev->dev.of_node, + "qcom,active-clock-names", "snoc_axi_clk") >= 0) + q6->snoc_axi_clk = devm_clk_get(&pdev->dev, "snoc_axi_clk"); + + if (of_property_match_string(pdev->dev.of_node, + "qcom,active-clock-names", "mnoc_axi_clk") >= 0) + q6->mnoc_axi_clk = devm_clk_get(&pdev->dev, "mnoc_axi_clk"); + + /* Defaulting smem_id to be not present */ + q6->smem_id = -1; + + if (of_find_property(pdev->dev.of_node, "qcom,smem-id", NULL)) { + ret = of_property_read_u32(pdev->dev.of_node, "qcom,smem-id", + &q6->smem_id); + if (ret) { + dev_err(&pdev->dev, "Failed to get the smem_id(ret:%d)\n", + ret); + return ret; + } + } + + ret = pil_desc_init(q6_desc); + + return ret; +} + +static int pil_mss_driver_probe(struct platform_device *pdev) +{ + struct modem_data *drv; + int ret; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + platform_set_drvdata(pdev, drv); + + ret = of_property_read_bool(pdev->dev.of_node, + "qcom,is-not-loadable"); + if (ret) { + drv->is_not_loadable = 1; + } else { + ret = pil_mss_loadable_init(drv, pdev); + if (ret) + return ret; + } + init_completion(&drv->stop_ack); + init_completion(&drv->err_ready); + + /* Probe the MBA mem device if present */ + ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); + if (ret) + return ret; + + return pil_subsys_init(drv, pdev); +} + +static int pil_mss_driver_exit(struct platform_device *pdev) +{ + struct modem_data *drv = platform_get_drvdata(pdev); + + subsys_unregister(drv->subsys); + destroy_ramdump_device(drv->ramdump_dev); + destroy_ramdump_device(drv->minidump_dev); + pil_desc_release(&drv->q6->desc); + return 0; +} + +static int pil_mba_mem_driver_probe(struct platform_device *pdev) +{ + struct modem_data *drv; + + if (!pdev->dev.parent) { + pr_err("No parent found.\n"); + return -EINVAL; + } + drv = dev_get_drvdata(pdev->dev.parent); + drv->mba_mem_dev_fixed = &pdev->dev; + return 0; +} + +static const struct of_device_id mba_mem_match_table[] = { + { .compatible = "qcom,pil-mba-mem" }, + {} +}; + +static struct platform_driver pil_mba_mem_driver = { + .probe = pil_mba_mem_driver_probe, + .driver = { + .name = "pil-mba-mem", + .of_match_table = mba_mem_match_table, + }, +}; + +static const struct of_device_id mss_match_table[] = { + { .compatible = "qcom,pil-q6v5-mss" }, + { .compatible = "qcom,pil-q6v55-mss" }, + { .compatible = "qcom,pil-q6v56-mss" }, + {} +}; + +static struct platform_driver pil_mss_driver = { + .probe = pil_mss_driver_probe, + .remove = pil_mss_driver_exit, + .driver = { + .name = "pil-q6v5-mss", + .of_match_table = mss_match_table, + }, +}; + +static int __init pil_mss_init(void) +{ + int ret; + + ret = platform_driver_register(&pil_mba_mem_driver); + if (!ret) + ret = platform_driver_register(&pil_mss_driver); + return ret; +} +module_init(pil_mss_init); + +static void __exit pil_mss_exit(void) +{ + platform_driver_unregister(&pil_mss_driver); +} +module_exit(pil_mss_exit); + +MODULE_DESCRIPTION("Support for booting modem subsystems with QDSP6v5 Hexagon processors"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/pil-q6v5.c b/drivers/soc/qcom/pil-q6v5.c new file mode 100644 index 000000000000..fb64cac6e73e --- /dev/null +++ b/drivers/soc/qcom/pil-q6v5.c @@ -0,0 +1,839 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "peripheral-loader.h" +#include "pil-msa.h" +#include "pil-q6v5.h" + +/* QDSP6SS Register Offsets */ +#define QDSP6SS_RESET 0x014 +#define QDSP6SS_GFMUX_CTL 0x020 +#define QDSP6SS_PWR_CTL 0x030 +#define QDSP6V6SS_MEM_PWR_CTL 0x034 +#define QDSP6SS_BHS_STATUS 0x078 +#define QDSP6SS_MEM_PWR_CTL 0x0B0 +#define QDSP6SS_STRAP_ACC 0x110 +#define QDSP6V62SS_BHS_STATUS 0x0C4 + +/* AXI Halt Register Offsets */ +#define AXI_HALTREQ 0x0 +#define AXI_HALTACK 0x4 +#define AXI_IDLE 0x8 + +#define HALT_ACK_TIMEOUT_US 100000 + +/* QDSP6SS_RESET */ +#define Q6SS_STOP_CORE BIT(0) +#define Q6SS_CORE_ARES BIT(1) +#define Q6SS_BUS_ARES_ENA BIT(2) + +/* QDSP6SS_GFMUX_CTL */ +#define Q6SS_CLK_ENA BIT(1) +#define Q6SS_CLK_SRC_SEL_C BIT(3) +#define Q6SS_CLK_SRC_SEL_FIELD 0xC +#define Q6SS_CLK_SRC_SWITCH_CLK_OVR BIT(8) + +/* QDSP6SS_PWR_CTL */ +#define Q6SS_L2DATA_SLP_NRET_N_0 BIT(0) +#define Q6SS_L2DATA_SLP_NRET_N_1 BIT(1) +#define Q6SS_L2DATA_SLP_NRET_N_2 BIT(2) +#define Q6SS_L2TAG_SLP_NRET_N BIT(16) +#define Q6SS_ETB_SLP_NRET_N BIT(17) +#define Q6SS_L2DATA_STBY_N BIT(18) +#define Q6SS_SLP_RET_N BIT(19) +#define Q6SS_CLAMP_IO BIT(20) +#define QDSS_BHS_ON BIT(21) +#define QDSS_LDO_BYP BIT(22) + +/* QDSP6v55 parameters */ +#define QDSP6v55_LDO_ON BIT(26) +#define QDSP6v55_LDO_BYP BIT(25) +#define QDSP6v55_BHS_ON BIT(24) +#define QDSP6v55_CLAMP_WL BIT(21) +#define QDSP6v55_CLAMP_QMC_MEM BIT(22) +#define L1IU_SLP_NRET_N BIT(15) +#define L1DU_SLP_NRET_N BIT(14) +#define L2PLRU_SLP_NRET_N BIT(13) +#define QDSP6v55_BHS_EN_REST_ACK BIT(0) + +#define HALT_CHECK_MAX_LOOPS (200) +#define BHS_CHECK_MAX_LOOPS (200) +#define QDSP6SS_XO_CBCR (0x0038) + +/* QDSP6v65 parameters */ +#define QDSP6SS_BOOT_CORE_START (0x400) +#define QDSP6SS_BOOT_CMD (0x404) +#define MSS_STATUS (0x40) +#define QDSP6SS_SLEEP (0x3C) +#define SLEEP_CHECK_MAX_LOOPS (200) +#define BOOT_FSM_TIMEOUT (10000) + +#define QDSP6SS_ACC_OVERRIDE_VAL 0x20 + +int pil_q6v5_make_proxy_votes(struct pil_desc *pil) +{ + int ret; + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + int uv; + + ret = of_property_read_u32(pil->dev->of_node, "vdd_cx-voltage", &uv); + if (ret) { + dev_err(pil->dev, "missing vdd_cx-voltage property(rc:%d)\n", + ret); + return ret; + } + + ret = clk_prepare_enable(drv->xo); + if (ret) { + dev_err(pil->dev, "Failed to vote for XO(rc:%d)\n", ret); + goto out; + } + + ret = clk_prepare_enable(drv->pnoc_clk); + if (ret) { + dev_err(pil->dev, "Failed to vote for pnoc(rc:%d)\n", ret); + goto err_pnoc_vote; + } + + ret = clk_prepare_enable(drv->qdss_clk); + if (ret) { + dev_err(pil->dev, "Failed to vote for qdss(rc:%d)\n", ret); + goto err_qdss_vote; + } + + ret = clk_prepare_enable(drv->prng_clk); + if (ret) { + dev_err(pil->dev, "Failed to vote for prng(rc:%d)\n", ret); + goto err_prng_vote; + } + + ret = clk_prepare_enable(drv->axis2_clk); + if (ret) { + dev_err(pil->dev, "Failed to vote for axis2(rc:%d)\n", ret); + goto err_axis2_vote; + } + + ret = regulator_set_voltage(drv->vreg_cx, uv, INT_MAX); + if (ret) { + dev_err(pil->dev, "Failed to request vdd_cx voltage(rc:%d)\n", + ret); + goto err_cx_voltage; + } + + ret = regulator_set_load(drv->vreg_cx, 100000); + if (ret < 0) { + dev_err(pil->dev, "Failed to set vdd_cx mode(rc:%d)\n", ret); + goto err_cx_mode; + } + + ret = regulator_enable(drv->vreg_cx); + if (ret) { + dev_err(pil->dev, "Failed to vote for vdd_cx(rc:%d)\n", ret); + goto err_cx_enable; + } + + if (drv->vreg_pll) { + ret = regulator_enable(drv->vreg_pll); + if (ret) { + dev_err(pil->dev, "Failed to vote for vdd_pll(rc:%d)\n", + ret); + goto err_vreg_pll; + } + } + + return 0; + +err_vreg_pll: + regulator_disable(drv->vreg_cx); +err_cx_enable: + regulator_set_load(drv->vreg_cx, 0); +err_cx_mode: + regulator_set_voltage(drv->vreg_cx, 0, INT_MAX); +err_cx_voltage: + clk_disable_unprepare(drv->axis2_clk); +err_axis2_vote: + clk_disable_unprepare(drv->prng_clk); +err_prng_vote: + clk_disable_unprepare(drv->qdss_clk); +err_qdss_vote: + clk_disable_unprepare(drv->pnoc_clk); +err_pnoc_vote: + clk_disable_unprepare(drv->xo); +out: + return ret; +} +EXPORT_SYMBOL(pil_q6v5_make_proxy_votes); + +void pil_q6v5_remove_proxy_votes(struct pil_desc *pil) +{ + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + int uv, ret = 0; + + ret = of_property_read_u32(pil->dev->of_node, "vdd_cx-voltage", &uv); + if (ret) { + dev_err(pil->dev, "missing vdd_cx-voltage property(rc:%d)\n", + ret); + return; + } + + if (drv->vreg_pll) { + regulator_disable(drv->vreg_pll); + regulator_set_load(drv->vreg_pll, 0); + } + regulator_disable(drv->vreg_cx); + regulator_set_load(drv->vreg_cx, 0); + regulator_set_voltage(drv->vreg_cx, 0, INT_MAX); + clk_disable_unprepare(drv->xo); + clk_disable_unprepare(drv->pnoc_clk); + clk_disable_unprepare(drv->qdss_clk); + clk_disable_unprepare(drv->prng_clk); + clk_disable_unprepare(drv->axis2_clk); +} +EXPORT_SYMBOL(pil_q6v5_remove_proxy_votes); + +void pil_q6v5_halt_axi_port(struct pil_desc *pil, void __iomem *halt_base) +{ + int ret; + u32 status; + + /* Assert halt request */ + writel_relaxed(1, halt_base + AXI_HALTREQ); + + /* Wait for halt */ + ret = readl_poll_timeout(halt_base + AXI_HALTACK, + status, status != 0, 50, HALT_ACK_TIMEOUT_US); + if (ret) + dev_warn(pil->dev, "Port %pK halt timeout\n", halt_base); + else if (!readl_relaxed(halt_base + AXI_IDLE)) + dev_warn(pil->dev, "Port %pK halt failed\n", halt_base); + + /* Clear halt request (port will remain halted until reset) */ + writel_relaxed(0, halt_base + AXI_HALTREQ); +} +EXPORT_SYMBOL(pil_q6v5_halt_axi_port); + +void assert_clamps(struct pil_desc *pil) +{ + u32 val; + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + + /* + * Assert QDSP6 I/O clamp, memory wordline clamp, and compiler memory + * clamp as a software workaround to avoid high MX current during + * LPASS/MSS restart. + */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val |= (Q6SS_CLAMP_IO | QDSP6v55_CLAMP_WL | + QDSP6v55_CLAMP_QMC_MEM); + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + /* To make sure asserting clamps is done before MSS restart*/ + mb(); +} + +static void __pil_q6v5_shutdown(struct pil_desc *pil) +{ + u32 val; + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + + /* Turn off core clock */ + val = readl_relaxed(drv->reg_base + QDSP6SS_GFMUX_CTL); + val &= ~Q6SS_CLK_ENA; + writel_relaxed(val, drv->reg_base + QDSP6SS_GFMUX_CTL); + + /* Clamp IO */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val |= Q6SS_CLAMP_IO; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Turn off Q6 memories */ + val &= ~(Q6SS_L2DATA_SLP_NRET_N_0 | Q6SS_L2DATA_SLP_NRET_N_1 | + Q6SS_L2DATA_SLP_NRET_N_2 | Q6SS_SLP_RET_N | + Q6SS_L2TAG_SLP_NRET_N | Q6SS_ETB_SLP_NRET_N | + Q6SS_L2DATA_STBY_N); + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Assert Q6 resets */ + val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); + val |= (Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENA); + writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); + + /* Kill power at block headswitch */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val &= ~QDSS_BHS_ON; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); +} + +void pil_q6v5_shutdown(struct pil_desc *pil) +{ + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + + if (drv->qdsp6v55) { + /* Subsystem driver expected to halt bus and assert reset */ + return; + } + __pil_q6v5_shutdown(pil); +} +EXPORT_SYMBOL(pil_q6v5_shutdown); + +static int __pil_q6v5_reset(struct pil_desc *pil) +{ + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + u32 val; + + /* Assert resets, stop core */ + val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); + val |= (Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENA | Q6SS_STOP_CORE); + writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); + + /* Enable power block headswitch, and wait for it to stabilize */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val |= QDSS_BHS_ON | QDSS_LDO_BYP; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Ensure physical memory access is done*/ + mb(); + udelay(1); + + /* + * Turn on memories. L2 banks should be done individually + * to minimize inrush current. + */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val |= Q6SS_SLP_RET_N | Q6SS_L2TAG_SLP_NRET_N | + Q6SS_ETB_SLP_NRET_N | Q6SS_L2DATA_STBY_N; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + val |= Q6SS_L2DATA_SLP_NRET_N_2; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + val |= Q6SS_L2DATA_SLP_NRET_N_1; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + val |= Q6SS_L2DATA_SLP_NRET_N_0; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Remove IO clamp */ + val &= ~Q6SS_CLAMP_IO; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Bring core out of reset */ + val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); + val &= ~Q6SS_CORE_ARES; + writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); + + /* Turn on core clock */ + val = readl_relaxed(drv->reg_base + QDSP6SS_GFMUX_CTL); + val |= Q6SS_CLK_ENA; + + /* Need a different clock source for v5.2.0 */ + if (drv->qdsp6v5_2_0) { + val &= ~Q6SS_CLK_SRC_SEL_FIELD; + val |= Q6SS_CLK_SRC_SEL_C; + } + + /* force clock on during source switch */ + if (drv->qdsp6v56) + val |= Q6SS_CLK_SRC_SWITCH_CLK_OVR; + + writel_relaxed(val, drv->reg_base + QDSP6SS_GFMUX_CTL); + + /* Start core execution */ + val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); + val &= ~Q6SS_STOP_CORE; + writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); + + return 0; +} + +static int q6v55_branch_clk_enable(struct q6v5_data *drv) +{ + u32 val, count; + void __iomem *cbcr_reg = drv->reg_base + QDSP6SS_XO_CBCR; + + val = readl_relaxed(cbcr_reg); + val |= 0x1; + writel_relaxed(val, cbcr_reg); + + for (count = HALT_CHECK_MAX_LOOPS; count > 0; count--) { + val = readl_relaxed(cbcr_reg); + if (!(val & BIT(31))) + return 0; + udelay(1); + } + + dev_err(drv->desc.dev, "Failed to enable xo branch clock.\n"); + return -EINVAL; +} + +static int __pil_q6v65_reset(struct pil_desc *pil) +{ + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + u32 val, count; + int ret; + + val = readl_relaxed(drv->reg_base + QDSP6SS_SLEEP); + val |= 0x1; + writel_relaxed(val, drv->reg_base + QDSP6SS_SLEEP); + for (count = SLEEP_CHECK_MAX_LOOPS; count > 0; count--) { + val = readl_relaxed(drv->reg_base + QDSP6SS_SLEEP); + if (!(val & BIT(31))) + break; + udelay(1); + } + + if (!count) { + dev_err(drv->desc.dev, "Sleep clock did not come on in time\n"); + return -ETIMEDOUT; + } + + /* De-assert QDSP6 stop core */ + writel_relaxed(1, drv->reg_base + QDSP6SS_BOOT_CORE_START); + /* De-assert stop core before starting boot FSM */ + mb(); + /* Trigger boot FSM */ + writel_relaxed(1, drv->reg_base + QDSP6SS_BOOT_CMD); + + /* Wait for boot FSM to complete */ + ret = readl_poll_timeout(drv->rmb_base + MSS_STATUS, val, + (val & BIT(0)) != 0, 10, BOOT_FSM_TIMEOUT); + + if (ret) { + dev_err(drv->desc.dev, "Boot FSM failed to complete.\n"); + /* Reset the modem so that boot FSM is in reset state */ + pil_mss_assert_resets(drv); + /* Wait 6 32kHz sleep cycles for reset */ + udelay(200); + pil_mss_deassert_resets(drv); + } + + return ret; +} + +static int __pil_q6v55_reset(struct pil_desc *pil) +{ + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + u32 val; + int i; + + trace_pil_func(__func__); + /* Override the ACC value if required */ + if (drv->override_acc) + writel_relaxed(QDSP6SS_ACC_OVERRIDE_VAL, + drv->reg_base + QDSP6SS_STRAP_ACC); + + /* Override the ACC value with input value */ + if (!of_property_read_u32(pil->dev->of_node, "qcom,override-acc-1", + &drv->override_acc_1)) + writel_relaxed(drv->override_acc_1, + drv->reg_base + QDSP6SS_STRAP_ACC); + + /* Assert resets, stop core */ + val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); + val |= (Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENA | Q6SS_STOP_CORE); + writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); + + /* BHS require xo cbcr to be enabled */ + i = q6v55_branch_clk_enable(drv); + if (i) + return i; + + /* Enable power block headswitch, and wait for it to stabilize */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val |= QDSP6v55_BHS_ON; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Ensure physical memory access is done*/ + mb(); + udelay(1); + + if (drv->qdsp6v62_1_2 || drv->qdsp6v62_1_5 || drv->qdsp6v62_1_4) { + for (i = BHS_CHECK_MAX_LOOPS; i > 0; i--) { + if (readl_relaxed(drv->reg_base + QDSP6V62SS_BHS_STATUS) + & QDSP6v55_BHS_EN_REST_ACK) + break; + udelay(1); + } + if (!i) { + pr_err("%s: BHS_EN_REST_ACK not set!\n", __func__); + return -ETIMEDOUT; + } + } + + if (drv->qdsp6v61_1_1) { + for (i = BHS_CHECK_MAX_LOOPS; i > 0; i--) { + if (readl_relaxed(drv->reg_base + QDSP6SS_BHS_STATUS) + & QDSP6v55_BHS_EN_REST_ACK) + break; + udelay(1); + } + if (!i) { + pr_err("%s: BHS_EN_REST_ACK not set!\n", __func__); + return -ETIMEDOUT; + } + } + + /* Put LDO in bypass mode */ + val |= QDSP6v55_LDO_BYP; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + if (drv->qdsp6v56_1_3) { + /* Deassert memory peripheral sleep and L2 memory standby */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val |= (Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N); + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Turn on L1, L2 and ETB memories 1 at a time */ + for (i = 17; i >= 0; i--) { + val |= BIT(i); + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + udelay(1); + } + } else if (drv->qdsp6v56_1_5 || drv->qdsp6v56_1_8 + || drv->qdsp6v56_1_10) { + /* Deassert QDSP6 compiler memory clamp */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val &= ~QDSP6v55_CLAMP_QMC_MEM; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Deassert memory peripheral sleep and L2 memory standby */ + val |= (Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N); + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Turn on L1, L2, ETB and JU memories 1 at a time */ + val = readl_relaxed(drv->reg_base + QDSP6SS_MEM_PWR_CTL); + for (i = 19; i >= 0; i--) { + val |= BIT(i); + writel_relaxed(val, drv->reg_base + + QDSP6SS_MEM_PWR_CTL); + val |= readl_relaxed(drv->reg_base + + QDSP6SS_MEM_PWR_CTL); + /* + * Wait for 1us for both memory peripheral and + * data array to turn on. + */ + udelay(1); + } + } else if (drv->qdsp6v56_1_8_inrush_current) { + /* Deassert QDSP6 compiler memory clamp */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val &= ~QDSP6v55_CLAMP_QMC_MEM; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Deassert memory peripheral sleep and L2 memory standby */ + val |= (Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N); + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Turn on L1, L2, ETB and JU memories 1 at a time */ + val = readl_relaxed(drv->reg_base + QDSP6SS_MEM_PWR_CTL); + for (i = 19; i >= 6; i--) { + val |= BIT(i); + writel_relaxed(val, drv->reg_base + + QDSP6SS_MEM_PWR_CTL); + /* + * Wait for 1us for both memory peripheral and + * data array to turn on. + */ + udelay(1); + } + + for (i = 0 ; i <= 5 ; i++) { + val |= BIT(i); + writel_relaxed(val, drv->reg_base + + QDSP6SS_MEM_PWR_CTL); + /* + * Wait for 1us for both memory peripheral and + * data array to turn on. + */ + udelay(1); + } + } else if (drv->qdsp6v61_1_1 || drv->qdsp6v62_1_2 || + drv->qdsp6v62_1_4 || drv->qdsp6v62_1_5) { + /* Deassert QDSP6 compiler memory clamp */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val &= ~QDSP6v55_CLAMP_QMC_MEM; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Deassert memory peripheral sleep and L2 memory standby */ + val |= (Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N); + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Turn on L1, L2, ETB and JU memories 1 at a time */ + val = readl_relaxed(drv->reg_base + + QDSP6V6SS_MEM_PWR_CTL); + + if (drv->qdsp6v62_1_4 || drv->qdsp6v62_1_5) + i = 29; + else + i = 28; + + for ( ; i >= 0; i--) { + val |= BIT(i); + writel_relaxed(val, drv->reg_base + + QDSP6V6SS_MEM_PWR_CTL); + /* + * Wait for 1us for both memory peripheral and + * data array to turn on. + */ + udelay(1); + } + } else { + /* Turn on memories. */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val |= 0xFFF00; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Turn on L2 banks 1 at a time */ + for (i = 0; i <= 7; i++) { + val |= BIT(i); + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + } + } + + /* Remove word line clamp */ + val = readl_relaxed(drv->reg_base + QDSP6SS_PWR_CTL); + val &= ~QDSP6v55_CLAMP_WL; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Remove IO clamp */ + val &= ~Q6SS_CLAMP_IO; + writel_relaxed(val, drv->reg_base + QDSP6SS_PWR_CTL); + + /* Bring core out of reset */ + val = readl_relaxed(drv->reg_base + QDSP6SS_RESET); + val &= ~(Q6SS_CORE_ARES | Q6SS_STOP_CORE); + writel_relaxed(val, drv->reg_base + QDSP6SS_RESET); + + /* Turn on core clock */ + val = readl_relaxed(drv->reg_base + QDSP6SS_GFMUX_CTL); + val |= Q6SS_CLK_ENA; + writel_relaxed(val, drv->reg_base + QDSP6SS_GFMUX_CTL); + + return 0; +} + +int pil_q6v5_reset(struct pil_desc *pil) +{ + struct q6v5_data *drv = container_of(pil, struct q6v5_data, desc); + + + if (drv->qdsp6v65_1_0) + return __pil_q6v65_reset(pil); + else if (drv->qdsp6v55) + return __pil_q6v55_reset(pil); + else + return __pil_q6v5_reset(pil); +} +EXPORT_SYMBOL(pil_q6v5_reset); + +struct q6v5_data *pil_q6v5_init(struct platform_device *pdev) +{ + struct q6v5_data *drv; + struct resource *res; + struct pil_desc *desc; + struct property *prop; + int ret, vdd_pll; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return ERR_PTR(-ENOMEM); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qdsp6_base"); + drv->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(drv->reg_base)) + return drv->reg_base; + + desc = &drv->desc; + ret = of_property_read_string(pdev->dev.of_node, "qcom,firmware-name", + &desc->name); + if (ret) + return ERR_PTR(ret); + + desc->clear_fw_region = false; + desc->dev = &pdev->dev; + + drv->qdsp6v5_2_0 = of_device_is_compatible(pdev->dev.of_node, + "qcom,pil-femto-modem"); + + if (drv->qdsp6v5_2_0) + return drv; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "halt_base"); + if (res) { + drv->axi_halt_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!drv->axi_halt_base) { + dev_err(&pdev->dev, "Failed to map axi_halt_base.\n"); + return ERR_PTR(-ENOMEM); + } + } + + if (!drv->axi_halt_base) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "halt_q6"); + if (res) { + drv->axi_halt_q6 = devm_ioremap(&pdev->dev, + res->start, resource_size(res)); + if (!drv->axi_halt_q6) { + dev_err(&pdev->dev, "Failed to map axi_halt_q6.\n"); + return ERR_PTR(-ENOMEM); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "halt_modem"); + if (res) { + drv->axi_halt_mss = devm_ioremap(&pdev->dev, + res->start, resource_size(res)); + if (!drv->axi_halt_mss) { + dev_err(&pdev->dev, "Failed to map axi_halt_mss.\n"); + return ERR_PTR(-ENOMEM); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "halt_nc"); + if (res) { + drv->axi_halt_nc = devm_ioremap(&pdev->dev, + res->start, resource_size(res)); + if (!drv->axi_halt_nc) { + dev_err(&pdev->dev, "Failed to map axi_halt_nc.\n"); + return ERR_PTR(-ENOMEM); + } + } + } + + if (!(drv->axi_halt_base || (drv->axi_halt_q6 && drv->axi_halt_mss + && drv->axi_halt_nc))) { + dev_err(&pdev->dev, "halt bases for Q6 are not defined.\n"); + return ERR_PTR(-EINVAL); + } + + drv->qdsp6v55 = of_device_is_compatible(pdev->dev.of_node, + "qcom,pil-q6v55-mss"); + drv->qdsp6v56 = of_device_is_compatible(pdev->dev.of_node, + "qcom,pil-q6v56-mss"); + + drv->qdsp6v56_1_3 = of_property_read_bool(pdev->dev.of_node, + "qcom,qdsp6v56-1-3"); + drv->qdsp6v56_1_5 = of_property_read_bool(pdev->dev.of_node, + "qcom,qdsp6v56-1-5"); + + drv->qdsp6v56_1_8 = of_property_read_bool(pdev->dev.of_node, + "qcom,qdsp6v56-1-8"); + drv->qdsp6v56_1_10 = of_property_read_bool(pdev->dev.of_node, + "qcom,qdsp6v56-1-10"); + + drv->qdsp6v56_1_8_inrush_current = of_property_read_bool( + pdev->dev.of_node, + "qcom,qdsp6v56-1-8-inrush-current"); + + drv->qdsp6v61_1_1 = of_property_read_bool(pdev->dev.of_node, + "qcom,qdsp6v61-1-1"); + + drv->qdsp6v62_1_2 = of_property_read_bool(pdev->dev.of_node, + "qcom,qdsp6v62-1-2"); + + drv->qdsp6v62_1_4 = of_property_read_bool(pdev->dev.of_node, + "qcom,qdsp6v62-1-4"); + + drv->qdsp6v62_1_5 = of_property_read_bool(pdev->dev.of_node, + "qcom,qdsp6v62-1-5"); + + drv->qdsp6v65_1_0 = of_property_read_bool(pdev->dev.of_node, + "qcom,qdsp6v65-1-0"); + + drv->non_elf_image = of_property_read_bool(pdev->dev.of_node, + "qcom,mba-image-is-not-elf"); + + drv->override_acc = of_property_read_bool(pdev->dev.of_node, + "qcom,override-acc"); + + drv->ahb_clk_vote = of_property_read_bool(pdev->dev.of_node, + "qcom,ahb-clk-vote"); + drv->mx_spike_wa = of_property_read_bool(pdev->dev.of_node, + "qcom,mx-spike-wa"); + + drv->xo = devm_clk_get(&pdev->dev, "xo"); + if (IS_ERR(drv->xo)) + return ERR_CAST(drv->xo); + + if (of_property_read_bool(pdev->dev.of_node, "qcom,pnoc-clk-vote")) { + drv->pnoc_clk = devm_clk_get(&pdev->dev, "pnoc_clk"); + if (IS_ERR(drv->pnoc_clk)) + return ERR_CAST(drv->pnoc_clk); + } else { + drv->pnoc_clk = NULL; + } + + if (of_property_match_string(pdev->dev.of_node, + "qcom,proxy-clock-names", "qdss_clk") >= 0) { + drv->qdss_clk = devm_clk_get(&pdev->dev, "qdss_clk"); + if (IS_ERR(drv->qdss_clk)) + return ERR_CAST(drv->qdss_clk); + } else { + drv->qdss_clk = NULL; + } + + if (of_property_match_string(pdev->dev.of_node, + "qcom,proxy-clock-names", "prng_clk") >= 0) { + drv->prng_clk = devm_clk_get(&pdev->dev, "prng_clk"); + if (IS_ERR(drv->prng_clk)) + return ERR_CAST(drv->prng_clk); + } else { + drv->prng_clk = NULL; + } + + if (of_property_match_string(pdev->dev.of_node, + "qcom,proxy-clock-names", "axis2_clk") >= 0) { + drv->axis2_clk = devm_clk_get(&pdev->dev, "axis2_clk"); + if (IS_ERR(drv->axis2_clk)) + return ERR_CAST(drv->axis2_clk); + } else { + drv->axis2_clk = NULL; + } + + drv->vreg_cx = devm_regulator_get(&pdev->dev, "vdd_cx"); + if (IS_ERR(drv->vreg_cx)) + return ERR_CAST(drv->vreg_cx); + prop = of_find_property(pdev->dev.of_node, "vdd_cx-voltage", NULL); + if (!prop) { + dev_err(&pdev->dev, "Missing vdd_cx-voltage property\n"); + return ERR_CAST(prop); + } + + ret = of_property_read_u32(pdev->dev.of_node, "qcom,vdd_pll", + &vdd_pll); + if (!ret) { + drv->vreg_pll = devm_regulator_get(&pdev->dev, "vdd_pll"); + if (!IS_ERR_OR_NULL(drv->vreg_pll)) { + ret = regulator_set_voltage(drv->vreg_pll, vdd_pll, + vdd_pll); + if (ret) { + dev_err(&pdev->dev, "Failed to set vdd_pll voltage(rc:%d)\n", + ret); + return ERR_PTR(ret); + } + + ret = regulator_set_load(drv->vreg_pll, 10000); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to set vdd_pll mode(rc:%d)\n", + ret); + return ERR_PTR(ret); + } + } else + drv->vreg_pll = NULL; + } + + return drv; +} +EXPORT_SYMBOL(pil_q6v5_init); diff --git a/drivers/soc/qcom/pil-q6v5.h b/drivers/soc/qcom/pil-q6v5.h new file mode 100644 index 000000000000..175ad8faeb6e --- /dev/null +++ b/drivers/soc/qcom/pil-q6v5.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + */ +#ifndef __MSM_PIL_Q6V5_H +#define __MSM_PIL_Q6V5_H + +#include "peripheral-loader.h" + +struct regulator; +struct clk; +struct pil_device; +struct platform_device; + +struct q6v5_data { + void __iomem *reg_base; + void __iomem *rmb_base; + void __iomem *cxrail_bhs; /* External BHS register */ + struct clk *xo; /* XO clock source */ + struct clk *pnoc_clk; /* PNOC bus clock source */ + struct clk *ahb_clk; /* PIL access to registers */ + struct clk *axi_clk; /* CPU access to memory */ + struct clk *core_clk; /* CPU core */ + struct clk *reg_clk; /* CPU access registers */ + struct clk *gpll0_mss_clk; /* GPLL0 to MSS connection */ + struct clk *rom_clk; /* Boot ROM */ + struct clk *snoc_axi_clk; + struct clk *mnoc_axi_clk; + struct clk *qdss_clk; + struct clk *prng_clk; + struct clk *axis2_clk; + void __iomem *axi_halt_base; /* Halt base of q6, mss, + * nc are in same 4K page + */ + void __iomem *axi_halt_q6; + void __iomem *axi_halt_mss; + void __iomem *axi_halt_nc; + void __iomem *restart_reg; + void __iomem *pdc_sync; + void __iomem *alt_reset; + struct regulator *vreg; + struct regulator *vreg_cx; + struct regulator *vreg_mx; + struct regulator *vreg_pll; + bool is_booted; + struct pil_desc desc; + bool self_auth; + phys_addr_t mba_dp_phys; + void *mba_dp_virt; + size_t mba_dp_size; + size_t dp_size; + bool qdsp6v55; + bool qdsp6v5_2_0; + bool qdsp6v56; + bool qdsp6v56_1_3; + bool qdsp6v56_1_5; + bool qdsp6v56_1_8; + bool qdsp6v56_1_8_inrush_current; + bool qdsp6v56_1_10; + bool qdsp6v61_1_1; + bool qdsp6v62_1_2; + bool qdsp6v62_1_4; + bool qdsp6v62_1_5; + bool qdsp6v65_1_0; + bool non_elf_image; + bool restart_reg_sec; + bool override_acc; + int override_acc_1; + int mss_pdc_offset; + int smem_id; + bool ahb_clk_vote; + bool mx_spike_wa; + bool reset_clk; +}; + +int pil_q6v5_make_proxy_votes(struct pil_desc *pil); +void pil_q6v5_remove_proxy_votes(struct pil_desc *pil); +void pil_q6v5_halt_axi_port(struct pil_desc *pil, void __iomem *halt_base); +void pil_q6v5_shutdown(struct pil_desc *pil); +int pil_q6v5_reset(struct pil_desc *pil); +void assert_clamps(struct pil_desc *pil); +struct q6v5_data *pil_q6v5_init(struct platform_device *pdev); + +#endif