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