From ef73c22fa025fe1db47605a9440767de993d1628 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 24 Sep 2018 16:45:26 -0700 Subject: remoteproc: qcom: Rename Hexagon v5 modem driver The qcom_q6v5_pil implements support for the self-authenticating modem subsystem. With the introduction of other q6v5 based non-TZ based remoteproc driver the current name is cause for confusion, so rename it to be more specific. No functional change. Reviewed-by: Niklas Cassel Signed-off-by: Bjorn Andersson --- drivers/remoteproc/Kconfig | 14 +- drivers/remoteproc/Makefile | 2 +- drivers/remoteproc/qcom_q6v5_mss.c | 1378 ++++++++++++++++++++++++++++++++++++ drivers/remoteproc/qcom_q6v5_pil.c | 1378 ------------------------------------ 4 files changed, 1386 insertions(+), 1386 deletions(-) create mode 100644 drivers/remoteproc/qcom_q6v5_mss.c delete mode 100644 drivers/remoteproc/qcom_q6v5_pil.c (limited to 'drivers/remoteproc') diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index 425e502c6471..8894935583e2 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -92,30 +92,30 @@ config QCOM_Q6V5_COMMON depends on ARCH_QCOM depends on QCOM_SMEM -config QCOM_Q6V5_PAS - tristate "Qualcomm Hexagon v5 Peripheral Authentication Service support" +config QCOM_Q6V5_MSS + tristate "Qualcomm Hexagon V5 self-authenticating modem subsystem support" depends on OF && ARCH_QCOM depends on QCOM_SMEM depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n) depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n depends on QCOM_SYSMON || QCOM_SYSMON=n select MFD_SYSCON - select QCOM_MDT_LOADER select QCOM_Q6V5_COMMON select QCOM_RPROC_COMMON select QCOM_SCM help - Say y here to support the TrustZone based Peripherial Image Loader - for the Qualcomm Hexagon v5 based remote processors. + Say y here to support the Qualcomm self-authenticating modem + subsystem based on Hexagon V5. -config QCOM_Q6V5_PIL - tristate "Qualcomm Hexagon V5 Peripherial Image Loader" +config QCOM_Q6V5_PAS + tristate "Qualcomm Hexagon v5 Peripheral Authentication Service support" depends on OF && ARCH_QCOM depends on QCOM_SMEM depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n) depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n depends on QCOM_SYSMON || QCOM_SYSMON=n select MFD_SYSCON + select QCOM_MDT_LOADER select QCOM_Q6V5_COMMON select QCOM_RPROC_COMMON select QCOM_SCM diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index eb86c8ba5a87..050f41ac93b0 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -16,8 +16,8 @@ obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o obj-$(CONFIG_KEYSTONE_REMOTEPROC) += keystone_remoteproc.o obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o obj-$(CONFIG_QCOM_Q6V5_COMMON) += qcom_q6v5.o +obj-$(CONFIG_QCOM_Q6V5_MSS) += qcom_q6v5_mss.o obj-$(CONFIG_QCOM_Q6V5_PAS) += qcom_q6v5_pas.o -obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o obj-$(CONFIG_QCOM_Q6V5_WCSS) += qcom_q6v5_wcss.o obj-$(CONFIG_QCOM_SYSMON) += qcom_sysmon.o obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss_pil.o diff --git a/drivers/remoteproc/qcom_q6v5_mss.c b/drivers/remoteproc/qcom_q6v5_mss.c new file mode 100644 index 000000000000..9ebbb9339295 --- /dev/null +++ b/drivers/remoteproc/qcom_q6v5_mss.c @@ -0,0 +1,1378 @@ +/* + * Qualcomm self-authenticating modem subsystem remoteproc driver + * + * Copyright (C) 2016 Linaro Ltd. + * Copyright (C) 2014 Sony Mobile Communications AB + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "remoteproc_internal.h" +#include "qcom_common.h" +#include "qcom_q6v5.h" + +#include + +#define MPSS_CRASH_REASON_SMEM 421 + +/* RMB Status Register Values */ +#define RMB_PBL_SUCCESS 0x1 + +#define RMB_MBA_XPU_UNLOCKED 0x1 +#define RMB_MBA_XPU_UNLOCKED_SCRIBBLED 0x2 +#define RMB_MBA_META_DATA_AUTH_SUCCESS 0x3 +#define RMB_MBA_AUTH_COMPLETE 0x4 + +/* PBL/MBA interface registers */ +#define RMB_MBA_IMAGE_REG 0x00 +#define RMB_PBL_STATUS_REG 0x04 +#define RMB_MBA_COMMAND_REG 0x08 +#define RMB_MBA_STATUS_REG 0x0C +#define RMB_PMI_META_DATA_REG 0x10 +#define RMB_PMI_CODE_START_REG 0x14 +#define RMB_PMI_CODE_LENGTH_REG 0x18 +#define RMB_MBA_MSS_STATUS 0x40 +#define RMB_MBA_ALT_RESET 0x44 + +#define RMB_CMD_META_DATA_READY 0x1 +#define RMB_CMD_LOAD_READY 0x2 + +/* QDSP6SS Register Offsets */ +#define QDSP6SS_RESET_REG 0x014 +#define QDSP6SS_GFMUX_CTL_REG 0x020 +#define QDSP6SS_PWR_CTL_REG 0x030 +#define QDSP6SS_MEM_PWR_CTL 0x0B0 +#define QDSP6SS_STRAP_ACC 0x110 + +/* AXI Halt Register Offsets */ +#define AXI_HALTREQ_REG 0x0 +#define AXI_HALTACK_REG 0x4 +#define AXI_IDLE_REG 0x8 + +#define HALT_ACK_TIMEOUT_MS 100 + +/* QDSP6SS_RESET */ +#define Q6SS_STOP_CORE BIT(0) +#define Q6SS_CORE_ARES BIT(1) +#define Q6SS_BUS_ARES_ENABLE BIT(2) + +/* QDSP6SS_GFMUX_CTL */ +#define Q6SS_CLK_ENABLE BIT(1) + +/* 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) + +/* QDSP6v56 parameters */ +#define QDSP6v56_LDO_BYP BIT(25) +#define QDSP6v56_BHS_ON BIT(24) +#define QDSP6v56_CLAMP_WL BIT(21) +#define QDSP6v56_CLAMP_QMC_MEM BIT(22) +#define HALT_CHECK_MAX_LOOPS 200 +#define QDSP6SS_XO_CBCR 0x0038 +#define QDSP6SS_ACC_OVERRIDE_VAL 0x20 + +/* QDSP6v65 parameters */ +#define QDSP6SS_SLEEP 0x3C +#define QDSP6SS_BOOT_CORE_START 0x400 +#define QDSP6SS_BOOT_CMD 0x404 +#define SLEEP_CHECK_MAX_LOOPS 200 +#define BOOT_FSM_TIMEOUT 10000 + +struct reg_info { + struct regulator *reg; + int uV; + int uA; +}; + +struct qcom_mss_reg_res { + const char *supply; + int uV; + int uA; +}; + +struct rproc_hexagon_res { + const char *hexagon_mba_image; + struct qcom_mss_reg_res *proxy_supply; + struct qcom_mss_reg_res *active_supply; + char **proxy_clk_names; + char **reset_clk_names; + char **active_clk_names; + int version; + bool need_mem_protection; + bool has_alt_reset; +}; + +struct q6v5 { + struct device *dev; + struct rproc *rproc; + + void __iomem *reg_base; + void __iomem *rmb_base; + + struct regmap *halt_map; + u32 halt_q6; + u32 halt_modem; + u32 halt_nc; + + struct reset_control *mss_restart; + + struct qcom_q6v5 q6v5; + + struct clk *active_clks[8]; + struct clk *reset_clks[4]; + struct clk *proxy_clks[4]; + int active_clk_count; + int reset_clk_count; + int proxy_clk_count; + + struct reg_info active_regs[1]; + struct reg_info proxy_regs[3]; + int active_reg_count; + int proxy_reg_count; + + bool running; + + phys_addr_t mba_phys; + void *mba_region; + size_t mba_size; + + phys_addr_t mpss_phys; + phys_addr_t mpss_reloc; + void *mpss_region; + size_t mpss_size; + + struct qcom_rproc_glink glink_subdev; + struct qcom_rproc_subdev smd_subdev; + struct qcom_rproc_ssr ssr_subdev; + struct qcom_sysmon *sysmon; + bool need_mem_protection; + bool has_alt_reset; + int mpss_perm; + int mba_perm; + int version; +}; + +enum { + MSS_MSM8916, + MSS_MSM8974, + MSS_MSM8996, + MSS_SDM845, +}; + +static int q6v5_regulator_init(struct device *dev, struct reg_info *regs, + const struct qcom_mss_reg_res *reg_res) +{ + int rc; + int i; + + if (!reg_res) + return 0; + + for (i = 0; reg_res[i].supply; i++) { + regs[i].reg = devm_regulator_get(dev, reg_res[i].supply); + if (IS_ERR(regs[i].reg)) { + rc = PTR_ERR(regs[i].reg); + if (rc != -EPROBE_DEFER) + dev_err(dev, "Failed to get %s\n regulator", + reg_res[i].supply); + return rc; + } + + regs[i].uV = reg_res[i].uV; + regs[i].uA = reg_res[i].uA; + } + + return i; +} + +static int q6v5_regulator_enable(struct q6v5 *qproc, + struct reg_info *regs, int count) +{ + int ret; + int i; + + for (i = 0; i < count; i++) { + if (regs[i].uV > 0) { + ret = regulator_set_voltage(regs[i].reg, + regs[i].uV, INT_MAX); + if (ret) { + dev_err(qproc->dev, + "Failed to request voltage for %d.\n", + i); + goto err; + } + } + + if (regs[i].uA > 0) { + ret = regulator_set_load(regs[i].reg, + regs[i].uA); + if (ret < 0) { + dev_err(qproc->dev, + "Failed to set regulator mode\n"); + goto err; + } + } + + ret = regulator_enable(regs[i].reg); + if (ret) { + dev_err(qproc->dev, "Regulator enable failed\n"); + goto err; + } + } + + return 0; +err: + for (; i >= 0; i--) { + if (regs[i].uV > 0) + regulator_set_voltage(regs[i].reg, 0, INT_MAX); + + if (regs[i].uA > 0) + regulator_set_load(regs[i].reg, 0); + + regulator_disable(regs[i].reg); + } + + return ret; +} + +static void q6v5_regulator_disable(struct q6v5 *qproc, + struct reg_info *regs, int count) +{ + int i; + + for (i = 0; i < count; i++) { + if (regs[i].uV > 0) + regulator_set_voltage(regs[i].reg, 0, INT_MAX); + + if (regs[i].uA > 0) + regulator_set_load(regs[i].reg, 0); + + regulator_disable(regs[i].reg); + } +} + +static int q6v5_clk_enable(struct device *dev, + struct clk **clks, int count) +{ + int rc; + int i; + + for (i = 0; i < count; i++) { + rc = clk_prepare_enable(clks[i]); + if (rc) { + dev_err(dev, "Clock enable failed\n"); + goto err; + } + } + + return 0; +err: + for (i--; i >= 0; i--) + clk_disable_unprepare(clks[i]); + + return rc; +} + +static void q6v5_clk_disable(struct device *dev, + struct clk **clks, int count) +{ + int i; + + for (i = 0; i < count; i++) + clk_disable_unprepare(clks[i]); +} + +static int q6v5_xfer_mem_ownership(struct q6v5 *qproc, int *current_perm, + bool remote_owner, phys_addr_t addr, + size_t size) +{ + struct qcom_scm_vmperm next; + + if (!qproc->need_mem_protection) + return 0; + if (remote_owner && *current_perm == BIT(QCOM_SCM_VMID_MSS_MSA)) + return 0; + if (!remote_owner && *current_perm == BIT(QCOM_SCM_VMID_HLOS)) + return 0; + + next.vmid = remote_owner ? QCOM_SCM_VMID_MSS_MSA : QCOM_SCM_VMID_HLOS; + next.perm = remote_owner ? QCOM_SCM_PERM_RW : QCOM_SCM_PERM_RWX; + + return qcom_scm_assign_mem(addr, ALIGN(size, SZ_4K), + current_perm, &next, 1); +} + +static int q6v5_load(struct rproc *rproc, const struct firmware *fw) +{ + struct q6v5 *qproc = rproc->priv; + + memcpy(qproc->mba_region, fw->data, fw->size); + + return 0; +} + +static int q6v5_reset_assert(struct q6v5 *qproc) +{ + if (qproc->has_alt_reset) + return reset_control_reset(qproc->mss_restart); + else + return reset_control_assert(qproc->mss_restart); +} + +static int q6v5_reset_deassert(struct q6v5 *qproc) +{ + int ret; + + if (qproc->has_alt_reset) { + writel(1, qproc->rmb_base + RMB_MBA_ALT_RESET); + ret = reset_control_reset(qproc->mss_restart); + writel(0, qproc->rmb_base + RMB_MBA_ALT_RESET); + } else { + ret = reset_control_deassert(qproc->mss_restart); + } + + return ret; +} + +static int q6v5_rmb_pbl_wait(struct q6v5 *qproc, int ms) +{ + unsigned long timeout; + s32 val; + + timeout = jiffies + msecs_to_jiffies(ms); + for (;;) { + val = readl(qproc->rmb_base + RMB_PBL_STATUS_REG); + if (val) + break; + + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + + msleep(1); + } + + return val; +} + +static int q6v5_rmb_mba_wait(struct q6v5 *qproc, u32 status, int ms) +{ + + unsigned long timeout; + s32 val; + + timeout = jiffies + msecs_to_jiffies(ms); + for (;;) { + val = readl(qproc->rmb_base + RMB_MBA_STATUS_REG); + if (val < 0) + break; + + if (!status && val) + break; + else if (status && val == status) + break; + + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + + msleep(1); + } + + return val; +} + +static int q6v5proc_reset(struct q6v5 *qproc) +{ + u32 val; + int ret; + int i; + + if (qproc->version == MSS_SDM845) { + val = readl(qproc->reg_base + QDSP6SS_SLEEP); + val |= 0x1; + writel(val, qproc->reg_base + QDSP6SS_SLEEP); + + ret = readl_poll_timeout(qproc->reg_base + QDSP6SS_SLEEP, + val, !(val & BIT(31)), 1, + SLEEP_CHECK_MAX_LOOPS); + if (ret) { + dev_err(qproc->dev, "QDSP6SS Sleep clock timed out\n"); + return -ETIMEDOUT; + } + + /* De-assert QDSP6 stop core */ + writel(1, qproc->reg_base + QDSP6SS_BOOT_CORE_START); + /* Trigger boot FSM */ + writel(1, qproc->reg_base + QDSP6SS_BOOT_CMD); + + ret = readl_poll_timeout(qproc->rmb_base + RMB_MBA_MSS_STATUS, + val, (val & BIT(0)) != 0, 10, BOOT_FSM_TIMEOUT); + if (ret) { + dev_err(qproc->dev, "Boot FSM failed to complete.\n"); + /* Reset the modem so that boot FSM is in reset state */ + q6v5_reset_deassert(qproc); + return ret; + } + + goto pbl_wait; + } else if (qproc->version == MSS_MSM8996) { + /* Override the ACC value if required */ + writel(QDSP6SS_ACC_OVERRIDE_VAL, + qproc->reg_base + QDSP6SS_STRAP_ACC); + + /* Assert resets, stop core */ + val = readl(qproc->reg_base + QDSP6SS_RESET_REG); + val |= Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENABLE | Q6SS_STOP_CORE; + writel(val, qproc->reg_base + QDSP6SS_RESET_REG); + + /* BHS require xo cbcr to be enabled */ + val = readl(qproc->reg_base + QDSP6SS_XO_CBCR); + val |= 0x1; + writel(val, qproc->reg_base + QDSP6SS_XO_CBCR); + + /* Read CLKOFF bit to go low indicating CLK is enabled */ + ret = readl_poll_timeout(qproc->reg_base + QDSP6SS_XO_CBCR, + val, !(val & BIT(31)), 1, + HALT_CHECK_MAX_LOOPS); + if (ret) { + dev_err(qproc->dev, + "xo cbcr enabling timed out (rc:%d)\n", ret); + return ret; + } + /* Enable power block headswitch and wait for it to stabilize */ + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val |= QDSP6v56_BHS_ON; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val |= readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); + udelay(1); + + /* Put LDO in bypass mode */ + val |= QDSP6v56_LDO_BYP; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + + /* Deassert QDSP6 compiler memory clamp */ + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val &= ~QDSP6v56_CLAMP_QMC_MEM; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + + /* Deassert memory peripheral sleep and L2 memory standby */ + val |= Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + + /* Turn on L1, L2, ETB and JU memories 1 at a time */ + val = readl(qproc->reg_base + QDSP6SS_MEM_PWR_CTL); + for (i = 19; i >= 0; i--) { + val |= BIT(i); + writel(val, qproc->reg_base + + QDSP6SS_MEM_PWR_CTL); + /* + * Read back value to ensure the write is done then + * wait for 1us for both memory peripheral and data + * array to turn on. + */ + val |= readl(qproc->reg_base + QDSP6SS_MEM_PWR_CTL); + udelay(1); + } + /* Remove word line clamp */ + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val &= ~QDSP6v56_CLAMP_WL; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + } else { + /* Assert resets, stop core */ + val = readl(qproc->reg_base + QDSP6SS_RESET_REG); + val |= Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENABLE | Q6SS_STOP_CORE; + writel(val, qproc->reg_base + QDSP6SS_RESET_REG); + + /* Enable power block headswitch and wait for it to stabilize */ + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val |= QDSS_BHS_ON | QDSS_LDO_BYP; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val |= readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); + udelay(1); + /* + * Turn on memories. L2 banks should be done individually + * to minimize inrush current. + */ + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val |= Q6SS_SLP_RET_N | Q6SS_L2TAG_SLP_NRET_N | + Q6SS_ETB_SLP_NRET_N | Q6SS_L2DATA_STBY_N; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val |= Q6SS_L2DATA_SLP_NRET_N_2; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val |= Q6SS_L2DATA_SLP_NRET_N_1; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val |= Q6SS_L2DATA_SLP_NRET_N_0; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + } + /* Remove IO clamp */ + val &= ~Q6SS_CLAMP_IO; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + + /* Bring core out of reset */ + val = readl(qproc->reg_base + QDSP6SS_RESET_REG); + val &= ~Q6SS_CORE_ARES; + writel(val, qproc->reg_base + QDSP6SS_RESET_REG); + + /* Turn on core clock */ + val = readl(qproc->reg_base + QDSP6SS_GFMUX_CTL_REG); + val |= Q6SS_CLK_ENABLE; + writel(val, qproc->reg_base + QDSP6SS_GFMUX_CTL_REG); + + /* Start core execution */ + val = readl(qproc->reg_base + QDSP6SS_RESET_REG); + val &= ~Q6SS_STOP_CORE; + writel(val, qproc->reg_base + QDSP6SS_RESET_REG); + +pbl_wait: + /* Wait for PBL status */ + ret = q6v5_rmb_pbl_wait(qproc, 1000); + if (ret == -ETIMEDOUT) { + dev_err(qproc->dev, "PBL boot timed out\n"); + } else if (ret != RMB_PBL_SUCCESS) { + dev_err(qproc->dev, "PBL returned unexpected status %d\n", ret); + ret = -EINVAL; + } else { + ret = 0; + } + + return ret; +} + +static void q6v5proc_halt_axi_port(struct q6v5 *qproc, + struct regmap *halt_map, + u32 offset) +{ + unsigned long timeout; + unsigned int val; + int ret; + + /* Check if we're already idle */ + ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); + if (!ret && val) + return; + + /* Assert halt request */ + regmap_write(halt_map, offset + AXI_HALTREQ_REG, 1); + + /* Wait for halt */ + timeout = jiffies + msecs_to_jiffies(HALT_ACK_TIMEOUT_MS); + for (;;) { + ret = regmap_read(halt_map, offset + AXI_HALTACK_REG, &val); + if (ret || val || time_after(jiffies, timeout)) + break; + + msleep(1); + } + + ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); + if (ret || !val) + dev_err(qproc->dev, "port failed halt\n"); + + /* Clear halt request (port will remain halted until reset) */ + regmap_write(halt_map, offset + AXI_HALTREQ_REG, 0); +} + +static int q6v5_mpss_init_image(struct q6v5 *qproc, const struct firmware *fw) +{ + unsigned long dma_attrs = DMA_ATTR_FORCE_CONTIGUOUS; + dma_addr_t phys; + int mdata_perm; + int xferop_ret; + void *ptr; + int ret; + + ptr = dma_alloc_attrs(qproc->dev, fw->size, &phys, GFP_KERNEL, dma_attrs); + if (!ptr) { + dev_err(qproc->dev, "failed to allocate mdt buffer\n"); + return -ENOMEM; + } + + memcpy(ptr, fw->data, fw->size); + + /* Hypervisor mapping to access metadata by modem */ + mdata_perm = BIT(QCOM_SCM_VMID_HLOS); + ret = q6v5_xfer_mem_ownership(qproc, &mdata_perm, + true, phys, fw->size); + if (ret) { + dev_err(qproc->dev, + "assigning Q6 access to metadata failed: %d\n", ret); + ret = -EAGAIN; + goto free_dma_attrs; + } + + writel(phys, qproc->rmb_base + RMB_PMI_META_DATA_REG); + writel(RMB_CMD_META_DATA_READY, qproc->rmb_base + RMB_MBA_COMMAND_REG); + + ret = q6v5_rmb_mba_wait(qproc, RMB_MBA_META_DATA_AUTH_SUCCESS, 1000); + if (ret == -ETIMEDOUT) + dev_err(qproc->dev, "MPSS header authentication timed out\n"); + else if (ret < 0) + dev_err(qproc->dev, "MPSS header authentication failed: %d\n", ret); + + /* Metadata authentication done, remove modem access */ + xferop_ret = q6v5_xfer_mem_ownership(qproc, &mdata_perm, + false, phys, fw->size); + if (xferop_ret) + dev_warn(qproc->dev, + "mdt buffer not reclaimed system may become unstable\n"); + +free_dma_attrs: + dma_free_attrs(qproc->dev, fw->size, ptr, phys, dma_attrs); + + return ret < 0 ? ret : 0; +} + +static bool q6v5_phdr_valid(const struct elf32_phdr *phdr) +{ + if (phdr->p_type != PT_LOAD) + return false; + + if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) + return false; + + if (!phdr->p_memsz) + return false; + + return true; +} + +static int q6v5_mpss_load(struct q6v5 *qproc) +{ + const struct elf32_phdr *phdrs; + const struct elf32_phdr *phdr; + const struct firmware *seg_fw; + const struct firmware *fw; + struct elf32_hdr *ehdr; + phys_addr_t mpss_reloc; + phys_addr_t boot_addr; + phys_addr_t min_addr = PHYS_ADDR_MAX; + phys_addr_t max_addr = 0; + bool relocate = false; + char seg_name[10]; + ssize_t offset; + size_t size = 0; + void *ptr; + int ret; + int i; + + ret = request_firmware(&fw, "modem.mdt", qproc->dev); + if (ret < 0) { + dev_err(qproc->dev, "unable to load modem.mdt\n"); + return ret; + } + + /* Initialize the RMB validator */ + writel(0, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG); + + ret = q6v5_mpss_init_image(qproc, fw); + if (ret) + goto release_firmware; + + ehdr = (struct elf32_hdr *)fw->data; + phdrs = (struct elf32_phdr *)(ehdr + 1); + + for (i = 0; i < ehdr->e_phnum; i++) { + phdr = &phdrs[i]; + + if (!q6v5_phdr_valid(phdr)) + continue; + + if (phdr->p_flags & QCOM_MDT_RELOCATABLE) + relocate = true; + + if (phdr->p_paddr < min_addr) + min_addr = phdr->p_paddr; + + if (phdr->p_paddr + phdr->p_memsz > max_addr) + max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K); + } + + mpss_reloc = relocate ? min_addr : qproc->mpss_phys; + /* Load firmware segments */ + for (i = 0; i < ehdr->e_phnum; i++) { + phdr = &phdrs[i]; + + if (!q6v5_phdr_valid(phdr)) + continue; + + offset = phdr->p_paddr - mpss_reloc; + if (offset < 0 || offset + phdr->p_memsz > qproc->mpss_size) { + dev_err(qproc->dev, "segment outside memory range\n"); + ret = -EINVAL; + goto release_firmware; + } + + ptr = qproc->mpss_region + offset; + + if (phdr->p_filesz) { + snprintf(seg_name, sizeof(seg_name), "modem.b%02d", i); + ret = request_firmware(&seg_fw, seg_name, qproc->dev); + if (ret) { + dev_err(qproc->dev, "failed to load %s\n", seg_name); + goto release_firmware; + } + + memcpy(ptr, seg_fw->data, seg_fw->size); + + release_firmware(seg_fw); + } + + if (phdr->p_memsz > phdr->p_filesz) { + memset(ptr + phdr->p_filesz, 0, + phdr->p_memsz - phdr->p_filesz); + } + size += phdr->p_memsz; + } + + /* Transfer ownership of modem ddr region to q6 */ + ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, true, + qproc->mpss_phys, qproc->mpss_size); + if (ret) { + dev_err(qproc->dev, + "assigning Q6 access to mpss memory failed: %d\n", ret); + ret = -EAGAIN; + goto release_firmware; + } + + boot_addr = relocate ? qproc->mpss_phys : min_addr; + writel(boot_addr, qproc->rmb_base + RMB_PMI_CODE_START_REG); + writel(RMB_CMD_LOAD_READY, qproc->rmb_base + RMB_MBA_COMMAND_REG); + writel(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG); + + ret = q6v5_rmb_mba_wait(qproc, RMB_MBA_AUTH_COMPLETE, 10000); + if (ret == -ETIMEDOUT) + dev_err(qproc->dev, "MPSS authentication timed out\n"); + else if (ret < 0) + dev_err(qproc->dev, "MPSS authentication failed: %d\n", ret); + +release_firmware: + release_firmware(fw); + + return ret < 0 ? ret : 0; +} + +static int q6v5_start(struct rproc *rproc) +{ + struct q6v5 *qproc = (struct q6v5 *)rproc->priv; + int xfermemop_ret; + int ret; + + qcom_q6v5_prepare(&qproc->q6v5); + + ret = q6v5_regulator_enable(qproc, qproc->proxy_regs, + qproc->proxy_reg_count); + if (ret) { + dev_err(qproc->dev, "failed to enable proxy supplies\n"); + goto disable_irqs; + } + + ret = q6v5_clk_enable(qproc->dev, qproc->proxy_clks, + qproc->proxy_clk_count); + if (ret) { + dev_err(qproc->dev, "failed to enable proxy clocks\n"); + goto disable_proxy_reg; + } + + ret = q6v5_regulator_enable(qproc, qproc->active_regs, + qproc->active_reg_count); + if (ret) { + dev_err(qproc->dev, "failed to enable supplies\n"); + goto disable_proxy_clk; + } + + ret = q6v5_clk_enable(qproc->dev, qproc->reset_clks, + qproc->reset_clk_count); + if (ret) { + dev_err(qproc->dev, "failed to enable reset clocks\n"); + goto disable_vdd; + } + + ret = q6v5_reset_deassert(qproc); + if (ret) { + dev_err(qproc->dev, "failed to deassert mss restart\n"); + goto disable_reset_clks; + } + + ret = q6v5_clk_enable(qproc->dev, qproc->active_clks, + qproc->active_clk_count); + if (ret) { + dev_err(qproc->dev, "failed to enable clocks\n"); + goto assert_reset; + } + + /* Assign MBA image access in DDR to q6 */ + ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, true, + qproc->mba_phys, qproc->mba_size); + if (ret) { + dev_err(qproc->dev, + "assigning Q6 access to mba memory failed: %d\n", ret); + goto disable_active_clks; + } + + writel(qproc->mba_phys, qproc->rmb_base + RMB_MBA_IMAGE_REG); + + ret = q6v5proc_reset(qproc); + if (ret) + goto reclaim_mba; + + ret = q6v5_rmb_mba_wait(qproc, 0, 5000); + if (ret == -ETIMEDOUT) { + dev_err(qproc->dev, "MBA boot timed out\n"); + goto halt_axi_ports; + } else if (ret != RMB_MBA_XPU_UNLOCKED && + ret != RMB_MBA_XPU_UNLOCKED_SCRIBBLED) { + dev_err(qproc->dev, "MBA returned unexpected status %d\n", ret); + ret = -EINVAL; + goto halt_axi_ports; + } + + dev_info(qproc->dev, "MBA booted, loading mpss\n"); + + ret = q6v5_mpss_load(qproc); + if (ret) + goto reclaim_mpss; + + ret = qcom_q6v5_wait_for_start(&qproc->q6v5, msecs_to_jiffies(5000)); + if (ret == -ETIMEDOUT) { + dev_err(qproc->dev, "start timed out\n"); + goto reclaim_mpss; + } + + xfermemop_ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, false, + qproc->mba_phys, + qproc->mba_size); + if (xfermemop_ret) + dev_err(qproc->dev, + "Failed to reclaim mba buffer system may become unstable\n"); + qproc->running = true; + + return 0; + +reclaim_mpss: + xfermemop_ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, + false, qproc->mpss_phys, + qproc->mpss_size); + WARN_ON(xfermemop_ret); + +halt_axi_ports: + q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_q6); + q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_modem); + q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_nc); + +reclaim_mba: + xfermemop_ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, false, + qproc->mba_phys, + qproc->mba_size); + if (xfermemop_ret) { + dev_err(qproc->dev, + "Failed to reclaim mba buffer, system may become unstable\n"); + } + +disable_active_clks: + q6v5_clk_disable(qproc->dev, qproc->active_clks, + qproc->active_clk_count); + +assert_reset: + q6v5_reset_assert(qproc); +disable_reset_clks: + q6v5_clk_disable(qproc->dev, qproc->reset_clks, + qproc->reset_clk_count); +disable_vdd: + q6v5_regulator_disable(qproc, qproc->active_regs, + qproc->active_reg_count); +disable_proxy_clk: + q6v5_clk_disable(qproc->dev, qproc->proxy_clks, + qproc->proxy_clk_count); +disable_proxy_reg: + q6v5_regulator_disable(qproc, qproc->proxy_regs, + qproc->proxy_reg_count); + +disable_irqs: + qcom_q6v5_unprepare(&qproc->q6v5); + + return ret; +} + +static int q6v5_stop(struct rproc *rproc) +{ + struct q6v5 *qproc = (struct q6v5 *)rproc->priv; + int ret; + u32 val; + + qproc->running = false; + + ret = qcom_q6v5_request_stop(&qproc->q6v5); + if (ret == -ETIMEDOUT) + dev_err(qproc->dev, "timed out on wait\n"); + + q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_q6); + q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_modem); + q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_nc); + if (qproc->version == MSS_MSM8996) { + /* + * To avoid high MX current during LPASS/MSS restart. + */ + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); + val |= Q6SS_CLAMP_IO | QDSP6v56_CLAMP_WL | + QDSP6v56_CLAMP_QMC_MEM; + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); + } + + + ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, false, + qproc->mpss_phys, qproc->mpss_size); + WARN_ON(ret); + + q6v5_reset_assert(qproc); + + ret = qcom_q6v5_unprepare(&qproc->q6v5); + if (ret) { + q6v5_clk_disable(qproc->dev, qproc->proxy_clks, + qproc->proxy_clk_count); + q6v5_regulator_disable(qproc, qproc->proxy_regs, + qproc->proxy_reg_count); + } + + q6v5_clk_disable(qproc->dev, qproc->reset_clks, + qproc->reset_clk_count); + q6v5_clk_disable(qproc->dev, qproc->active_clks, + qproc->active_clk_count); + q6v5_regulator_disable(qproc, qproc->active_regs, + qproc->active_reg_count); + + return 0; +} + +static void *q6v5_da_to_va(struct rproc *rproc, u64 da, int len) +{ + struct q6v5 *qproc = rproc->priv; + int offset; + + offset = da - qproc->mpss_reloc; + if (offset < 0 || offset + len > qproc->mpss_size) + return NULL; + + return qproc->mpss_region + offset; +} + +static const struct rproc_ops q6v5_ops = { + .start = q6v5_start, + .stop = q6v5_stop, + .da_to_va = q6v5_da_to_va, + .load = q6v5_load, +}; + +static void qcom_msa_handover(struct qcom_q6v5 *q6v5) +{ + struct q6v5 *qproc = container_of(q6v5, struct q6v5, q6v5); + + q6v5_clk_disable(qproc->dev, qproc->proxy_clks, + qproc->proxy_clk_count); + q6v5_regulator_disable(qproc, qproc->proxy_regs, + qproc->proxy_reg_count); +} + +static int q6v5_init_mem(struct q6v5 *qproc, struct platform_device *pdev) +{ + struct of_phandle_args args; + struct resource *res; + int ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qdsp6"); + qproc->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(qproc->reg_base)) + return PTR_ERR(qproc->reg_base); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rmb"); + qproc->rmb_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(qproc->rmb_base)) + return PTR_ERR(qproc->rmb_base); + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "qcom,halt-regs", 3, 0, &args); + if (ret < 0) { + dev_err(&pdev->dev, "failed to parse qcom,halt-regs\n"); + return -EINVAL; + } + + qproc->halt_map = syscon_node_to_regmap(args.np); + of_node_put(args.np); + if (IS_ERR(qproc->halt_map)) + return PTR_ERR(qproc->halt_map); + + qproc->halt_q6 = args.args[0]; + qproc->halt_modem = args.args[1]; + qproc->halt_nc = args.args[2]; + + return 0; +} + +static int q6v5_init_clocks(struct device *dev, struct clk **clks, + char **clk_names) +{ + int i; + + if (!clk_names) + return 0; + + for (i = 0; clk_names[i]; i++) { + clks[i] = devm_clk_get(dev, clk_names[i]); + if (IS_ERR(clks[i])) { + int rc = PTR_ERR(clks[i]); + + if (rc != -EPROBE_DEFER) + dev_err(dev, "Failed to get %s clock\n", + clk_names[i]); + return rc; + } + } + + return i; +} + +static int q6v5_init_reset(struct q6v5 *qproc) +{ + qproc->mss_restart = devm_reset_control_get_exclusive(qproc->dev, + NULL); + if (IS_ERR(qproc->mss_restart)) { + dev_err(qproc->dev, "failed to acquire mss restart\n"); + return PTR_ERR(qproc->mss_restart); + } + + return 0; +} + +static int q6v5_alloc_memory_region(struct q6v5 *qproc) +{ + struct device_node *child; + struct device_node *node; + struct resource r; + int ret; + + child = of_get_child_by_name(qproc->dev->of_node, "mba"); + node = of_parse_phandle(child, "memory-region", 0); + ret = of_address_to_resource(node, 0, &r); + if (ret) { + dev_err(qproc->dev, "unable to resolve mba region\n"); + return ret; + } + of_node_put(node); + + qproc->mba_phys = r.start; + qproc->mba_size = resource_size(&r); + qproc->mba_region = devm_ioremap_wc(qproc->dev, qproc->mba_phys, qproc->mba_size); + if (!qproc->mba_region) { + dev_err(qproc->dev, "unable to map memory region: %pa+%zx\n", + &r.start, qproc->mba_size); + return -EBUSY; + } + + child = of_get_child_by_name(qproc->dev->of_node, "mpss"); + node = of_parse_phandle(child, "memory-region", 0); + ret = of_address_to_resource(node, 0, &r); + if (ret) { + dev_err(qproc->dev, "unable to resolve mpss region\n"); + return ret; + } + of_node_put(node); + + qproc->mpss_phys = qproc->mpss_reloc = r.start; + qproc->mpss_size = resource_size(&r); + qproc->mpss_region = devm_ioremap_wc(qproc->dev, qproc->mpss_phys, qproc->mpss_size); + if (!qproc->mpss_region) { + dev_err(qproc->dev, "unable to map memory region: %pa+%zx\n", + &r.start, qproc->mpss_size); + return -EBUSY; + } + + return 0; +} + +static int q6v5_probe(struct platform_device *pdev) +{ + const struct rproc_hexagon_res *desc; + struct q6v5 *qproc; + struct rproc *rproc; + int ret; + + desc = of_device_get_match_data(&pdev->dev); + if (!desc) + return -EINVAL; + + rproc = rproc_alloc(&pdev->dev, pdev->name, &q6v5_ops, + desc->hexagon_mba_image, sizeof(*qproc)); + if (!rproc) { + dev_err(&pdev->dev, "failed to allocate rproc\n"); + return -ENOMEM; + } + + qproc = (struct q6v5 *)rproc->priv; + qproc->dev = &pdev->dev; + qproc->rproc = rproc; + platform_set_drvdata(pdev, qproc); + + ret = q6v5_init_mem(qproc, pdev); + if (ret) + goto free_rproc; + + ret = q6v5_alloc_memory_region(qproc); + if (ret) + goto free_rproc; + + ret = q6v5_init_clocks(&pdev->dev, qproc->proxy_clks, + desc->proxy_clk_names); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get proxy clocks.\n"); + goto free_rproc; + } + qproc->proxy_clk_count = ret; + + ret = q6v5_init_clocks(&pdev->dev, qproc->reset_clks, + desc->reset_clk_names); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get reset clocks.\n"); + goto free_rproc; + } + qproc->reset_clk_count = ret; + + ret = q6v5_init_clocks(&pdev->dev, qproc->active_clks, + desc->active_clk_names); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get active clocks.\n"); + goto free_rproc; + } + qproc->active_clk_count = ret; + + ret = q6v5_regulator_init(&pdev->dev, qproc->proxy_regs, + desc->proxy_supply); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get proxy regulators.\n"); + goto free_rproc; + } + qproc->proxy_reg_count = ret; + + ret = q6v5_regulator_init(&pdev->dev, qproc->active_regs, + desc->active_supply); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get active regulators.\n"); + goto free_rproc; + } + qproc->active_reg_count = ret; + + ret = q6v5_init_reset(qproc); + if (ret) + goto free_rproc; + + qproc->version = desc->version; + qproc->has_alt_reset = desc->has_alt_reset; + qproc->need_mem_protection = desc->need_mem_protection; + + ret = qcom_q6v5_init(&qproc->q6v5, pdev, rproc, MPSS_CRASH_REASON_SMEM, + qcom_msa_handover); + if (ret) + goto free_rproc; + + qproc->mpss_perm = BIT(QCOM_SCM_VMID_HLOS); + qproc->mba_perm = BIT(QCOM_SCM_VMID_HLOS); + qcom_add_glink_subdev(rproc, &qproc->glink_subdev); + qcom_add_smd_subdev(rproc, &qproc->smd_subdev); + qcom_add_ssr_subdev(rproc, &qproc->ssr_subdev, "mpss"); + qproc->sysmon = qcom_add_sysmon_subdev(rproc, "modem", 0x12); + + ret = rproc_add(rproc); + if (ret) + goto free_rproc; + + return 0; + +free_rproc: + rproc_free(rproc); + + return ret; +} + +static int q6v5_remove(struct platform_device *pdev) +{ + struct q6v5 *qproc = platform_get_drvdata(pdev); + + rproc_del(qproc->rproc); + + qcom_remove_sysmon_subdev(qproc->sysmon); + qcom_remove_glink_subdev(qproc->rproc, &qproc->glink_subdev); + qcom_remove_smd_subdev(qproc->rproc, &qproc->smd_subdev); + qcom_remove_ssr_subdev(qproc->rproc, &qproc->ssr_subdev); + rproc_free(qproc->rproc); + + return 0; +} + +static const struct rproc_hexagon_res sdm845_mss = { + .hexagon_mba_image = "mba.mbn", + .proxy_clk_names = (char*[]){ + "xo", + "prng", + NULL + }, + .reset_clk_names = (char*[]){ + "iface", + "snoc_axi", + NULL + }, + .active_clk_names = (char*[]){ + "bus", + "mem", + "gpll0_mss", + "mnoc_axi", + NULL + }, + .need_mem_protection = true, + .has_alt_reset = true, + .version = MSS_SDM845, +}; + +static const struct rproc_hexagon_res msm8996_mss = { + .hexagon_mba_image = "mba.mbn", + .proxy_clk_names = (char*[]){ + "xo", + "pnoc", + NULL + }, + .active_clk_names = (char*[]){ + "iface", + "bus", + "mem", + "gpll0_mss_clk", + NULL + }, + .need_mem_protection = true, + .has_alt_reset = false, + .version = MSS_MSM8996, +}; + +static const struct rproc_hexagon_res msm8916_mss = { + .hexagon_mba_image = "mba.mbn", + .proxy_supply = (struct qcom_mss_reg_res[]) { + { + .supply = "mx", + .uV = 1050000, + }, + { + .supply = "cx", + .uA = 100000, + }, + { + .supply = "pll", + .uA = 100000, + }, + {} + }, + .proxy_clk_names = (char*[]){ + "xo", + NULL + }, + .active_clk_names = (char*[]){ + "iface", + "bus", + "mem", + NULL + }, + .need_mem_protection = false, + .has_alt_reset = false, + .version = MSS_MSM8916, +}; + +static const struct rproc_hexagon_res msm8974_mss = { + .hexagon_mba_image = "mba.b00", + .proxy_supply = (struct qcom_mss_reg_res[]) { + { + .supply = "mx", + .uV = 1050000, + }, + { + .supply = "cx", + .uA = 100000, + }, + { + .supply = "pll", + .uA = 100000, + }, + {} + }, + .active_supply = (struct qcom_mss_reg_res[]) { + { + .supply = "mss", + .uV = 1050000, + .uA = 100000, + }, + {} + }, + .proxy_clk_names = (char*[]){ + "xo", + NULL + }, + .active_clk_names = (char*[]){ + "iface", + "bus", + "mem", + NULL + }, + .need_mem_protection = false, + .has_alt_reset = false, + .version = MSS_MSM8974, +}; + +static const struct of_device_id q6v5_of_match[] = { + { .compatible = "qcom,q6v5-pil", .data = &msm8916_mss}, + { .compatible = "qcom,msm8916-mss-pil", .data = &msm8916_mss}, + { .compatible = "qcom,msm8974-mss-pil", .data = &msm8974_mss}, + { .compatible = "qcom,msm8996-mss-pil", .data = &msm8996_mss}, + { .compatible = "qcom,sdm845-mss-pil", .data = &sdm845_mss}, + { }, +}; +MODULE_DEVICE_TABLE(of, q6v5_of_match); + +static struct platform_driver q6v5_driver = { + .probe = q6v5_probe, + .remove = q6v5_remove, + .driver = { + .name = "qcom-q6v5-mss", + .of_match_table = q6v5_of_match, + }, +}; +module_platform_driver(q6v5_driver); + +MODULE_DESCRIPTION("Qualcomm Self-authenticating modem remoteproc driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/remoteproc/qcom_q6v5_pil.c b/drivers/remoteproc/qcom_q6v5_pil.c deleted file mode 100644 index d7a4b9eca5d2..000000000000 --- a/drivers/remoteproc/qcom_q6v5_pil.c +++ /dev/null @@ -1,1378 +0,0 @@ -/* - * Qualcomm Peripheral Image Loader - * - * Copyright (C) 2016 Linaro Ltd. - * Copyright (C) 2014 Sony Mobile Communications AB - * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "remoteproc_internal.h" -#include "qcom_common.h" -#include "qcom_q6v5.h" - -#include - -#define MPSS_CRASH_REASON_SMEM 421 - -/* RMB Status Register Values */ -#define RMB_PBL_SUCCESS 0x1 - -#define RMB_MBA_XPU_UNLOCKED 0x1 -#define RMB_MBA_XPU_UNLOCKED_SCRIBBLED 0x2 -#define RMB_MBA_META_DATA_AUTH_SUCCESS 0x3 -#define RMB_MBA_AUTH_COMPLETE 0x4 - -/* PBL/MBA interface registers */ -#define RMB_MBA_IMAGE_REG 0x00 -#define RMB_PBL_STATUS_REG 0x04 -#define RMB_MBA_COMMAND_REG 0x08 -#define RMB_MBA_STATUS_REG 0x0C -#define RMB_PMI_META_DATA_REG 0x10 -#define RMB_PMI_CODE_START_REG 0x14 -#define RMB_PMI_CODE_LENGTH_REG 0x18 -#define RMB_MBA_MSS_STATUS 0x40 -#define RMB_MBA_ALT_RESET 0x44 - -#define RMB_CMD_META_DATA_READY 0x1 -#define RMB_CMD_LOAD_READY 0x2 - -/* QDSP6SS Register Offsets */ -#define QDSP6SS_RESET_REG 0x014 -#define QDSP6SS_GFMUX_CTL_REG 0x020 -#define QDSP6SS_PWR_CTL_REG 0x030 -#define QDSP6SS_MEM_PWR_CTL 0x0B0 -#define QDSP6SS_STRAP_ACC 0x110 - -/* AXI Halt Register Offsets */ -#define AXI_HALTREQ_REG 0x0 -#define AXI_HALTACK_REG 0x4 -#define AXI_IDLE_REG 0x8 - -#define HALT_ACK_TIMEOUT_MS 100 - -/* QDSP6SS_RESET */ -#define Q6SS_STOP_CORE BIT(0) -#define Q6SS_CORE_ARES BIT(1) -#define Q6SS_BUS_ARES_ENABLE BIT(2) - -/* QDSP6SS_GFMUX_CTL */ -#define Q6SS_CLK_ENABLE BIT(1) - -/* 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) - -/* QDSP6v56 parameters */ -#define QDSP6v56_LDO_BYP BIT(25) -#define QDSP6v56_BHS_ON BIT(24) -#define QDSP6v56_CLAMP_WL BIT(21) -#define QDSP6v56_CLAMP_QMC_MEM BIT(22) -#define HALT_CHECK_MAX_LOOPS 200 -#define QDSP6SS_XO_CBCR 0x0038 -#define QDSP6SS_ACC_OVERRIDE_VAL 0x20 - -/* QDSP6v65 parameters */ -#define QDSP6SS_SLEEP 0x3C -#define QDSP6SS_BOOT_CORE_START 0x400 -#define QDSP6SS_BOOT_CMD 0x404 -#define SLEEP_CHECK_MAX_LOOPS 200 -#define BOOT_FSM_TIMEOUT 10000 - -struct reg_info { - struct regulator *reg; - int uV; - int uA; -}; - -struct qcom_mss_reg_res { - const char *supply; - int uV; - int uA; -}; - -struct rproc_hexagon_res { - const char *hexagon_mba_image; - struct qcom_mss_reg_res *proxy_supply; - struct qcom_mss_reg_res *active_supply; - char **proxy_clk_names; - char **reset_clk_names; - char **active_clk_names; - int version; - bool need_mem_protection; - bool has_alt_reset; -}; - -struct q6v5 { - struct device *dev; - struct rproc *rproc; - - void __iomem *reg_base; - void __iomem *rmb_base; - - struct regmap *halt_map; - u32 halt_q6; - u32 halt_modem; - u32 halt_nc; - - struct reset_control *mss_restart; - - struct qcom_q6v5 q6v5; - - struct clk *active_clks[8]; - struct clk *reset_clks[4]; - struct clk *proxy_clks[4]; - int active_clk_count; - int reset_clk_count; - int proxy_clk_count; - - struct reg_info active_regs[1]; - struct reg_info proxy_regs[3]; - int active_reg_count; - int proxy_reg_count; - - bool running; - - phys_addr_t mba_phys; - void *mba_region; - size_t mba_size; - - phys_addr_t mpss_phys; - phys_addr_t mpss_reloc; - void *mpss_region; - size_t mpss_size; - - struct qcom_rproc_glink glink_subdev; - struct qcom_rproc_subdev smd_subdev; - struct qcom_rproc_ssr ssr_subdev; - struct qcom_sysmon *sysmon; - bool need_mem_protection; - bool has_alt_reset; - int mpss_perm; - int mba_perm; - int version; -}; - -enum { - MSS_MSM8916, - MSS_MSM8974, - MSS_MSM8996, - MSS_SDM845, -}; - -static int q6v5_regulator_init(struct device *dev, struct reg_info *regs, - const struct qcom_mss_reg_res *reg_res) -{ - int rc; - int i; - - if (!reg_res) - return 0; - - for (i = 0; reg_res[i].supply; i++) { - regs[i].reg = devm_regulator_get(dev, reg_res[i].supply); - if (IS_ERR(regs[i].reg)) { - rc = PTR_ERR(regs[i].reg); - if (rc != -EPROBE_DEFER) - dev_err(dev, "Failed to get %s\n regulator", - reg_res[i].supply); - return rc; - } - - regs[i].uV = reg_res[i].uV; - regs[i].uA = reg_res[i].uA; - } - - return i; -} - -static int q6v5_regulator_enable(struct q6v5 *qproc, - struct reg_info *regs, int count) -{ - int ret; - int i; - - for (i = 0; i < count; i++) { - if (regs[i].uV > 0) { - ret = regulator_set_voltage(regs[i].reg, - regs[i].uV, INT_MAX); - if (ret) { - dev_err(qproc->dev, - "Failed to request voltage for %d.\n", - i); - goto err; - } - } - - if (regs[i].uA > 0) { - ret = regulator_set_load(regs[i].reg, - regs[i].uA); - if (ret < 0) { - dev_err(qproc->dev, - "Failed to set regulator mode\n"); - goto err; - } - } - - ret = regulator_enable(regs[i].reg); - if (ret) { - dev_err(qproc->dev, "Regulator enable failed\n"); - goto err; - } - } - - return 0; -err: - for (; i >= 0; i--) { - if (regs[i].uV > 0) - regulator_set_voltage(regs[i].reg, 0, INT_MAX); - - if (regs[i].uA > 0) - regulator_set_load(regs[i].reg, 0); - - regulator_disable(regs[i].reg); - } - - return ret; -} - -static void q6v5_regulator_disable(struct q6v5 *qproc, - struct reg_info *regs, int count) -{ - int i; - - for (i = 0; i < count; i++) { - if (regs[i].uV > 0) - regulator_set_voltage(regs[i].reg, 0, INT_MAX); - - if (regs[i].uA > 0) - regulator_set_load(regs[i].reg, 0); - - regulator_disable(regs[i].reg); - } -} - -static int q6v5_clk_enable(struct device *dev, - struct clk **clks, int count) -{ - int rc; - int i; - - for (i = 0; i < count; i++) { - rc = clk_prepare_enable(clks[i]); - if (rc) { - dev_err(dev, "Clock enable failed\n"); - goto err; - } - } - - return 0; -err: - for (i--; i >= 0; i--) - clk_disable_unprepare(clks[i]); - - return rc; -} - -static void q6v5_clk_disable(struct device *dev, - struct clk **clks, int count) -{ - int i; - - for (i = 0; i < count; i++) - clk_disable_unprepare(clks[i]); -} - -static int q6v5_xfer_mem_ownership(struct q6v5 *qproc, int *current_perm, - bool remote_owner, phys_addr_t addr, - size_t size) -{ - struct qcom_scm_vmperm next; - - if (!qproc->need_mem_protection) - return 0; - if (remote_owner && *current_perm == BIT(QCOM_SCM_VMID_MSS_MSA)) - return 0; - if (!remote_owner && *current_perm == BIT(QCOM_SCM_VMID_HLOS)) - return 0; - - next.vmid = remote_owner ? QCOM_SCM_VMID_MSS_MSA : QCOM_SCM_VMID_HLOS; - next.perm = remote_owner ? QCOM_SCM_PERM_RW : QCOM_SCM_PERM_RWX; - - return qcom_scm_assign_mem(addr, ALIGN(size, SZ_4K), - current_perm, &next, 1); -} - -static int q6v5_load(struct rproc *rproc, const struct firmware *fw) -{ - struct q6v5 *qproc = rproc->priv; - - memcpy(qproc->mba_region, fw->data, fw->size); - - return 0; -} - -static int q6v5_reset_assert(struct q6v5 *qproc) -{ - if (qproc->has_alt_reset) - return reset_control_reset(qproc->mss_restart); - else - return reset_control_assert(qproc->mss_restart); -} - -static int q6v5_reset_deassert(struct q6v5 *qproc) -{ - int ret; - - if (qproc->has_alt_reset) { - writel(1, qproc->rmb_base + RMB_MBA_ALT_RESET); - ret = reset_control_reset(qproc->mss_restart); - writel(0, qproc->rmb_base + RMB_MBA_ALT_RESET); - } else { - ret = reset_control_deassert(qproc->mss_restart); - } - - return ret; -} - -static int q6v5_rmb_pbl_wait(struct q6v5 *qproc, int ms) -{ - unsigned long timeout; - s32 val; - - timeout = jiffies + msecs_to_jiffies(ms); - for (;;) { - val = readl(qproc->rmb_base + RMB_PBL_STATUS_REG); - if (val) - break; - - if (time_after(jiffies, timeout)) - return -ETIMEDOUT; - - msleep(1); - } - - return val; -} - -static int q6v5_rmb_mba_wait(struct q6v5 *qproc, u32 status, int ms) -{ - - unsigned long timeout; - s32 val; - - timeout = jiffies + msecs_to_jiffies(ms); - for (;;) { - val = readl(qproc->rmb_base + RMB_MBA_STATUS_REG); - if (val < 0) - break; - - if (!status && val) - break; - else if (status && val == status) - break; - - if (time_after(jiffies, timeout)) - return -ETIMEDOUT; - - msleep(1); - } - - return val; -} - -static int q6v5proc_reset(struct q6v5 *qproc) -{ - u32 val; - int ret; - int i; - - if (qproc->version == MSS_SDM845) { - val = readl(qproc->reg_base + QDSP6SS_SLEEP); - val |= 0x1; - writel(val, qproc->reg_base + QDSP6SS_SLEEP); - - ret = readl_poll_timeout(qproc->reg_base + QDSP6SS_SLEEP, - val, !(val & BIT(31)), 1, - SLEEP_CHECK_MAX_LOOPS); - if (ret) { - dev_err(qproc->dev, "QDSP6SS Sleep clock timed out\n"); - return -ETIMEDOUT; - } - - /* De-assert QDSP6 stop core */ - writel(1, qproc->reg_base + QDSP6SS_BOOT_CORE_START); - /* Trigger boot FSM */ - writel(1, qproc->reg_base + QDSP6SS_BOOT_CMD); - - ret = readl_poll_timeout(qproc->rmb_base + RMB_MBA_MSS_STATUS, - val, (val & BIT(0)) != 0, 10, BOOT_FSM_TIMEOUT); - if (ret) { - dev_err(qproc->dev, "Boot FSM failed to complete.\n"); - /* Reset the modem so that boot FSM is in reset state */ - q6v5_reset_deassert(qproc); - return ret; - } - - goto pbl_wait; - } else if (qproc->version == MSS_MSM8996) { - /* Override the ACC value if required */ - writel(QDSP6SS_ACC_OVERRIDE_VAL, - qproc->reg_base + QDSP6SS_STRAP_ACC); - - /* Assert resets, stop core */ - val = readl(qproc->reg_base + QDSP6SS_RESET_REG); - val |= Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENABLE | Q6SS_STOP_CORE; - writel(val, qproc->reg_base + QDSP6SS_RESET_REG); - - /* BHS require xo cbcr to be enabled */ - val = readl(qproc->reg_base + QDSP6SS_XO_CBCR); - val |= 0x1; - writel(val, qproc->reg_base + QDSP6SS_XO_CBCR); - - /* Read CLKOFF bit to go low indicating CLK is enabled */ - ret = readl_poll_timeout(qproc->reg_base + QDSP6SS_XO_CBCR, - val, !(val & BIT(31)), 1, - HALT_CHECK_MAX_LOOPS); - if (ret) { - dev_err(qproc->dev, - "xo cbcr enabling timed out (rc:%d)\n", ret); - return ret; - } - /* Enable power block headswitch and wait for it to stabilize */ - val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); - val |= QDSP6v56_BHS_ON; - writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); - val |= readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); - udelay(1); - - /* Put LDO in bypass mode */ - val |= QDSP6v56_LDO_BYP; - writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); - - /* Deassert QDSP6 compiler memory clamp */ - val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); - val &= ~QDSP6v56_CLAMP_QMC_MEM; - writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); - - /* Deassert memory peripheral sleep and L2 memory standby */ - val |= Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N; - writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); - - /* Turn on L1, L2, ETB and JU memories 1 at a time */ - val = readl(qproc->reg_base + QDSP6SS_MEM_PWR_CTL); - for (i = 19; i >= 0; i--) { - val |= BIT(i); - writel(val, qproc->reg_base + - QDSP6SS_MEM_PWR_CTL); - /* - * Read back value to ensure the write is done then - * wait for 1us for both memory peripheral and data - * array to turn on. - */ - val |= readl(qproc->reg_base + QDSP6SS_MEM_PWR_CTL); - udelay(1); - } - /* Remove word line clamp */ - val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); - val &= ~QDSP6v56_CLAMP_WL; - writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); - } else { - /* Assert resets, stop core */ - val = readl(qproc->reg_base + QDSP6SS_RESET_REG); - val |= Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENABLE | Q6SS_STOP_CORE; - writel(val, qproc->reg_base + QDSP6SS_RESET_REG); - - /* Enable power block headswitch and wait for it to stabilize */ - val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); - val |= QDSS_BHS_ON | QDSS_LDO_BYP; - writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); - val |= readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); - udelay(1); - /* - * Turn on memories. L2 banks should be done individually - * to minimize inrush current. - */ - val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); - val |= Q6SS_SLP_RET_N | Q6SS_L2TAG_SLP_NRET_N | - Q6SS_ETB_SLP_NRET_N | Q6SS_L2DATA_STBY_N; - writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); - val |= Q6SS_L2DATA_SLP_NRET_N_2; - writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); - val |= Q6SS_L2DATA_SLP_NRET_N_1; - writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); - val |= Q6SS_L2DATA_SLP_NRET_N_0; - writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); - } - /* Remove IO clamp */ - val &= ~Q6SS_CLAMP_IO; - writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); - - /* Bring core out of reset */ - val = readl(qproc->reg_base + QDSP6SS_RESET_REG); - val &= ~Q6SS_CORE_ARES; - writel(val, qproc->reg_base + QDSP6SS_RESET_REG); - - /* Turn on core clock */ - val = readl(qproc->reg_base + QDSP6SS_GFMUX_CTL_REG); - val |= Q6SS_CLK_ENABLE; - writel(val, qproc->reg_base + QDSP6SS_GFMUX_CTL_REG); - - /* Start core execution */ - val = readl(qproc->reg_base + QDSP6SS_RESET_REG); - val &= ~Q6SS_STOP_CORE; - writel(val, qproc->reg_base + QDSP6SS_RESET_REG); - -pbl_wait: - /* Wait for PBL status */ - ret = q6v5_rmb_pbl_wait(qproc, 1000); - if (ret == -ETIMEDOUT) { - dev_err(qproc->dev, "PBL boot timed out\n"); - } else if (ret != RMB_PBL_SUCCESS) { - dev_err(qproc->dev, "PBL returned unexpected status %d\n", ret); - ret = -EINVAL; - } else { - ret = 0; - } - - return ret; -} - -static void q6v5proc_halt_axi_port(struct q6v5 *qproc, - struct regmap *halt_map, - u32 offset) -{ - unsigned long timeout; - unsigned int val; - int ret; - - /* Check if we're already idle */ - ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); - if (!ret && val) - return; - - /* Assert halt request */ - regmap_write(halt_map, offset + AXI_HALTREQ_REG, 1); - - /* Wait for halt */ - timeout = jiffies + msecs_to_jiffies(HALT_ACK_TIMEOUT_MS); - for (;;) { - ret = regmap_read(halt_map, offset + AXI_HALTACK_REG, &val); - if (ret || val || time_after(jiffies, timeout)) - break; - - msleep(1); - } - - ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); - if (ret || !val) - dev_err(qproc->dev, "port failed halt\n"); - - /* Clear halt request (port will remain halted until reset) */ - regmap_write(halt_map, offset + AXI_HALTREQ_REG, 0); -} - -static int q6v5_mpss_init_image(struct q6v5 *qproc, const struct firmware *fw) -{ - unsigned long dma_attrs = DMA_ATTR_FORCE_CONTIGUOUS; - dma_addr_t phys; - int mdata_perm; - int xferop_ret; - void *ptr; - int ret; - - ptr = dma_alloc_attrs(qproc->dev, fw->size, &phys, GFP_KERNEL, dma_attrs); - if (!ptr) { - dev_err(qproc->dev, "failed to allocate mdt buffer\n"); - return -ENOMEM; - } - - memcpy(ptr, fw->data, fw->size); - - /* Hypervisor mapping to access metadata by modem */ - mdata_perm = BIT(QCOM_SCM_VMID_HLOS); - ret = q6v5_xfer_mem_ownership(qproc, &mdata_perm, - true, phys, fw->size); - if (ret) { - dev_err(qproc->dev, - "assigning Q6 access to metadata failed: %d\n", ret); - ret = -EAGAIN; - goto free_dma_attrs; - } - - writel(phys, qproc->rmb_base + RMB_PMI_META_DATA_REG); - writel(RMB_CMD_META_DATA_READY, qproc->rmb_base + RMB_MBA_COMMAND_REG); - - ret = q6v5_rmb_mba_wait(qproc, RMB_MBA_META_DATA_AUTH_SUCCESS, 1000); - if (ret == -ETIMEDOUT) - dev_err(qproc->dev, "MPSS header authentication timed out\n"); - else if (ret < 0) - dev_err(qproc->dev, "MPSS header authentication failed: %d\n", ret); - - /* Metadata authentication done, remove modem access */ - xferop_ret = q6v5_xfer_mem_ownership(qproc, &mdata_perm, - false, phys, fw->size); - if (xferop_ret) - dev_warn(qproc->dev, - "mdt buffer not reclaimed system may become unstable\n"); - -free_dma_attrs: - dma_free_attrs(qproc->dev, fw->size, ptr, phys, dma_attrs); - - return ret < 0 ? ret : 0; -} - -static bool q6v5_phdr_valid(const struct elf32_phdr *phdr) -{ - if (phdr->p_type != PT_LOAD) - return false; - - if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) - return false; - - if (!phdr->p_memsz) - return false; - - return true; -} - -static int q6v5_mpss_load(struct q6v5 *qproc) -{ - const struct elf32_phdr *phdrs; - const struct elf32_phdr *phdr; - const struct firmware *seg_fw; - const struct firmware *fw; - struct elf32_hdr *ehdr; - phys_addr_t mpss_reloc; - phys_addr_t boot_addr; - phys_addr_t min_addr = PHYS_ADDR_MAX; - phys_addr_t max_addr = 0; - bool relocate = false; - char seg_name[10]; - ssize_t offset; - size_t size = 0; - void *ptr; - int ret; - int i; - - ret = request_firmware(&fw, "modem.mdt", qproc->dev); - if (ret < 0) { - dev_err(qproc->dev, "unable to load modem.mdt\n"); - return ret; - } - - /* Initialize the RMB validator */ - writel(0, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG); - - ret = q6v5_mpss_init_image(qproc, fw); - if (ret) - goto release_firmware; - - ehdr = (struct elf32_hdr *)fw->data; - phdrs = (struct elf32_phdr *)(ehdr + 1); - - for (i = 0; i < ehdr->e_phnum; i++) { - phdr = &phdrs[i]; - - if (!q6v5_phdr_valid(phdr)) - continue; - - if (phdr->p_flags & QCOM_MDT_RELOCATABLE) - relocate = true; - - if (phdr->p_paddr < min_addr) - min_addr = phdr->p_paddr; - - if (phdr->p_paddr + phdr->p_memsz > max_addr) - max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K); - } - - mpss_reloc = relocate ? min_addr : qproc->mpss_phys; - /* Load firmware segments */ - for (i = 0; i < ehdr->e_phnum; i++) { - phdr = &phdrs[i]; - - if (!q6v5_phdr_valid(phdr)) - continue; - - offset = phdr->p_paddr - mpss_reloc; - if (offset < 0 || offset + phdr->p_memsz > qproc->mpss_size) { - dev_err(qproc->dev, "segment outside memory range\n"); - ret = -EINVAL; - goto release_firmware; - } - - ptr = qproc->mpss_region + offset; - - if (phdr->p_filesz) { - snprintf(seg_name, sizeof(seg_name), "modem.b%02d", i); - ret = request_firmware(&seg_fw, seg_name, qproc->dev); - if (ret) { - dev_err(qproc->dev, "failed to load %s\n", seg_name); - goto release_firmware; - } - - memcpy(ptr, seg_fw->data, seg_fw->size); - - release_firmware(seg_fw); - } - - if (phdr->p_memsz > phdr->p_filesz) { - memset(ptr + phdr->p_filesz, 0, - phdr->p_memsz - phdr->p_filesz); - } - size += phdr->p_memsz; - } - - /* Transfer ownership of modem ddr region to q6 */ - ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, true, - qproc->mpss_phys, qproc->mpss_size); - if (ret) { - dev_err(qproc->dev, - "assigning Q6 access to mpss memory failed: %d\n", ret); - ret = -EAGAIN; - goto release_firmware; - } - - boot_addr = relocate ? qproc->mpss_phys : min_addr; - writel(boot_addr, qproc->rmb_base + RMB_PMI_CODE_START_REG); - writel(RMB_CMD_LOAD_READY, qproc->rmb_base + RMB_MBA_COMMAND_REG); - writel(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG); - - ret = q6v5_rmb_mba_wait(qproc, RMB_MBA_AUTH_COMPLETE, 10000); - if (ret == -ETIMEDOUT) - dev_err(qproc->dev, "MPSS authentication timed out\n"); - else if (ret < 0) - dev_err(qproc->dev, "MPSS authentication failed: %d\n", ret); - -release_firmware: - release_firmware(fw); - - return ret < 0 ? ret : 0; -} - -static int q6v5_start(struct rproc *rproc) -{ - struct q6v5 *qproc = (struct q6v5 *)rproc->priv; - int xfermemop_ret; - int ret; - - qcom_q6v5_prepare(&qproc->q6v5); - - ret = q6v5_regulator_enable(qproc, qproc->proxy_regs, - qproc->proxy_reg_count); - if (ret) { - dev_err(qproc->dev, "failed to enable proxy supplies\n"); - goto disable_irqs; - } - - ret = q6v5_clk_enable(qproc->dev, qproc->proxy_clks, - qproc->proxy_clk_count); - if (ret) { - dev_err(qproc->dev, "failed to enable proxy clocks\n"); - goto disable_proxy_reg; - } - - ret = q6v5_regulator_enable(qproc, qproc->active_regs, - qproc->active_reg_count); - if (ret) { - dev_err(qproc->dev, "failed to enable supplies\n"); - goto disable_proxy_clk; - } - - ret = q6v5_clk_enable(qproc->dev, qproc->reset_clks, - qproc->reset_clk_count); - if (ret) { - dev_err(qproc->dev, "failed to enable reset clocks\n"); - goto disable_vdd; - } - - ret = q6v5_reset_deassert(qproc); - if (ret) { - dev_err(qproc->dev, "failed to deassert mss restart\n"); - goto disable_reset_clks; - } - - ret = q6v5_clk_enable(qproc->dev, qproc->active_clks, - qproc->active_clk_count); - if (ret) { - dev_err(qproc->dev, "failed to enable clocks\n"); - goto assert_reset; - } - - /* Assign MBA image access in DDR to q6 */ - ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, true, - qproc->mba_phys, qproc->mba_size); - if (ret) { - dev_err(qproc->dev, - "assigning Q6 access to mba memory failed: %d\n", ret); - goto disable_active_clks; - } - - writel(qproc->mba_phys, qproc->rmb_base + RMB_MBA_IMAGE_REG); - - ret = q6v5proc_reset(qproc); - if (ret) - goto reclaim_mba; - - ret = q6v5_rmb_mba_wait(qproc, 0, 5000); - if (ret == -ETIMEDOUT) { - dev_err(qproc->dev, "MBA boot timed out\n"); - goto halt_axi_ports; - } else if (ret != RMB_MBA_XPU_UNLOCKED && - ret != RMB_MBA_XPU_UNLOCKED_SCRIBBLED) { - dev_err(qproc->dev, "MBA returned unexpected status %d\n", ret); - ret = -EINVAL; - goto halt_axi_ports; - } - - dev_info(qproc->dev, "MBA booted, loading mpss\n"); - - ret = q6v5_mpss_load(qproc); - if (ret) - goto reclaim_mpss; - - ret = qcom_q6v5_wait_for_start(&qproc->q6v5, msecs_to_jiffies(5000)); - if (ret == -ETIMEDOUT) { - dev_err(qproc->dev, "start timed out\n"); - goto reclaim_mpss; - } - - xfermemop_ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, false, - qproc->mba_phys, - qproc->mba_size); - if (xfermemop_ret) - dev_err(qproc->dev, - "Failed to reclaim mba buffer system may become unstable\n"); - qproc->running = true; - - return 0; - -reclaim_mpss: - xfermemop_ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, - false, qproc->mpss_phys, - qproc->mpss_size); - WARN_ON(xfermemop_ret); - -halt_axi_ports: - q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_q6); - q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_modem); - q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_nc); - -reclaim_mba: - xfermemop_ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, false, - qproc->mba_phys, - qproc->mba_size); - if (xfermemop_ret) { - dev_err(qproc->dev, - "Failed to reclaim mba buffer, system may become unstable\n"); - } - -disable_active_clks: - q6v5_clk_disable(qproc->dev, qproc->active_clks, - qproc->active_clk_count); - -assert_reset: - q6v5_reset_assert(qproc); -disable_reset_clks: - q6v5_clk_disable(qproc->dev, qproc->reset_clks, - qproc->reset_clk_count); -disable_vdd: - q6v5_regulator_disable(qproc, qproc->active_regs, - qproc->active_reg_count); -disable_proxy_clk: - q6v5_clk_disable(qproc->dev, qproc->proxy_clks, - qproc->proxy_clk_count); -disable_proxy_reg: - q6v5_regulator_disable(qproc, qproc->proxy_regs, - qproc->proxy_reg_count); - -disable_irqs: - qcom_q6v5_unprepare(&qproc->q6v5); - - return ret; -} - -static int q6v5_stop(struct rproc *rproc) -{ - struct q6v5 *qproc = (struct q6v5 *)rproc->priv; - int ret; - u32 val; - - qproc->running = false; - - ret = qcom_q6v5_request_stop(&qproc->q6v5); - if (ret == -ETIMEDOUT) - dev_err(qproc->dev, "timed out on wait\n"); - - q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_q6); - q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_modem); - q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_nc); - if (qproc->version == MSS_MSM8996) { - /* - * To avoid high MX current during LPASS/MSS restart. - */ - val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); - val |= Q6SS_CLAMP_IO | QDSP6v56_CLAMP_WL | - QDSP6v56_CLAMP_QMC_MEM; - writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); - } - - - ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, false, - qproc->mpss_phys, qproc->mpss_size); - WARN_ON(ret); - - q6v5_reset_assert(qproc); - - ret = qcom_q6v5_unprepare(&qproc->q6v5); - if (ret) { - q6v5_clk_disable(qproc->dev, qproc->proxy_clks, - qproc->proxy_clk_count); - q6v5_regulator_disable(qproc, qproc->proxy_regs, - qproc->proxy_reg_count); - } - - q6v5_clk_disable(qproc->dev, qproc->reset_clks, - qproc->reset_clk_count); - q6v5_clk_disable(qproc->dev, qproc->active_clks, - qproc->active_clk_count); - q6v5_regulator_disable(qproc, qproc->active_regs, - qproc->active_reg_count); - - return 0; -} - -static void *q6v5_da_to_va(struct rproc *rproc, u64 da, int len) -{ - struct q6v5 *qproc = rproc->priv; - int offset; - - offset = da - qproc->mpss_reloc; - if (offset < 0 || offset + len > qproc->mpss_size) - return NULL; - - return qproc->mpss_region + offset; -} - -static const struct rproc_ops q6v5_ops = { - .start = q6v5_start, - .stop = q6v5_stop, - .da_to_va = q6v5_da_to_va, - .load = q6v5_load, -}; - -static void qcom_msa_handover(struct qcom_q6v5 *q6v5) -{ - struct q6v5 *qproc = container_of(q6v5, struct q6v5, q6v5); - - q6v5_clk_disable(qproc->dev, qproc->proxy_clks, - qproc->proxy_clk_count); - q6v5_regulator_disable(qproc, qproc->proxy_regs, - qproc->proxy_reg_count); -} - -static int q6v5_init_mem(struct q6v5 *qproc, struct platform_device *pdev) -{ - struct of_phandle_args args; - struct resource *res; - int ret; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qdsp6"); - qproc->reg_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(qproc->reg_base)) - return PTR_ERR(qproc->reg_base); - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rmb"); - qproc->rmb_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(qproc->rmb_base)) - return PTR_ERR(qproc->rmb_base); - - ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, - "qcom,halt-regs", 3, 0, &args); - if (ret < 0) { - dev_err(&pdev->dev, "failed to parse qcom,halt-regs\n"); - return -EINVAL; - } - - qproc->halt_map = syscon_node_to_regmap(args.np); - of_node_put(args.np); - if (IS_ERR(qproc->halt_map)) - return PTR_ERR(qproc->halt_map); - - qproc->halt_q6 = args.args[0]; - qproc->halt_modem = args.args[1]; - qproc->halt_nc = args.args[2]; - - return 0; -} - -static int q6v5_init_clocks(struct device *dev, struct clk **clks, - char **clk_names) -{ - int i; - - if (!clk_names) - return 0; - - for (i = 0; clk_names[i]; i++) { - clks[i] = devm_clk_get(dev, clk_names[i]); - if (IS_ERR(clks[i])) { - int rc = PTR_ERR(clks[i]); - - if (rc != -EPROBE_DEFER) - dev_err(dev, "Failed to get %s clock\n", - clk_names[i]); - return rc; - } - } - - return i; -} - -static int q6v5_init_reset(struct q6v5 *qproc) -{ - qproc->mss_restart = devm_reset_control_get_exclusive(qproc->dev, - NULL); - if (IS_ERR(qproc->mss_restart)) { - dev_err(qproc->dev, "failed to acquire mss restart\n"); - return PTR_ERR(qproc->mss_restart); - } - - return 0; -} - -static int q6v5_alloc_memory_region(struct q6v5 *qproc) -{ - struct device_node *child; - struct device_node *node; - struct resource r; - int ret; - - child = of_get_child_by_name(qproc->dev->of_node, "mba"); - node = of_parse_phandle(child, "memory-region", 0); - ret = of_address_to_resource(node, 0, &r); - if (ret) { - dev_err(qproc->dev, "unable to resolve mba region\n"); - return ret; - } - of_node_put(node); - - qproc->mba_phys = r.start; - qproc->mba_size = resource_size(&r); - qproc->mba_region = devm_ioremap_wc(qproc->dev, qproc->mba_phys, qproc->mba_size); - if (!qproc->mba_region) { - dev_err(qproc->dev, "unable to map memory region: %pa+%zx\n", - &r.start, qproc->mba_size); - return -EBUSY; - } - - child = of_get_child_by_name(qproc->dev->of_node, "mpss"); - node = of_parse_phandle(child, "memory-region", 0); - ret = of_address_to_resource(node, 0, &r); - if (ret) { - dev_err(qproc->dev, "unable to resolve mpss region\n"); - return ret; - } - of_node_put(node); - - qproc->mpss_phys = qproc->mpss_reloc = r.start; - qproc->mpss_size = resource_size(&r); - qproc->mpss_region = devm_ioremap_wc(qproc->dev, qproc->mpss_phys, qproc->mpss_size); - if (!qproc->mpss_region) { - dev_err(qproc->dev, "unable to map memory region: %pa+%zx\n", - &r.start, qproc->mpss_size); - return -EBUSY; - } - - return 0; -} - -static int q6v5_probe(struct platform_device *pdev) -{ - const struct rproc_hexagon_res *desc; - struct q6v5 *qproc; - struct rproc *rproc; - int ret; - - desc = of_device_get_match_data(&pdev->dev); - if (!desc) - return -EINVAL; - - rproc = rproc_alloc(&pdev->dev, pdev->name, &q6v5_ops, - desc->hexagon_mba_image, sizeof(*qproc)); - if (!rproc) { - dev_err(&pdev->dev, "failed to allocate rproc\n"); - return -ENOMEM; - } - - qproc = (struct q6v5 *)rproc->priv; - qproc->dev = &pdev->dev; - qproc->rproc = rproc; - platform_set_drvdata(pdev, qproc); - - ret = q6v5_init_mem(qproc, pdev); - if (ret) - goto free_rproc; - - ret = q6v5_alloc_memory_region(qproc); - if (ret) - goto free_rproc; - - ret = q6v5_init_clocks(&pdev->dev, qproc->proxy_clks, - desc->proxy_clk_names); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to get proxy clocks.\n"); - goto free_rproc; - } - qproc->proxy_clk_count = ret; - - ret = q6v5_init_clocks(&pdev->dev, qproc->reset_clks, - desc->reset_clk_names); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to get reset clocks.\n"); - goto free_rproc; - } - qproc->reset_clk_count = ret; - - ret = q6v5_init_clocks(&pdev->dev, qproc->active_clks, - desc->active_clk_names); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to get active clocks.\n"); - goto free_rproc; - } - qproc->active_clk_count = ret; - - ret = q6v5_regulator_init(&pdev->dev, qproc->proxy_regs, - desc->proxy_supply); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to get proxy regulators.\n"); - goto free_rproc; - } - qproc->proxy_reg_count = ret; - - ret = q6v5_regulator_init(&pdev->dev, qproc->active_regs, - desc->active_supply); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to get active regulators.\n"); - goto free_rproc; - } - qproc->active_reg_count = ret; - - ret = q6v5_init_reset(qproc); - if (ret) - goto free_rproc; - - qproc->version = desc->version; - qproc->has_alt_reset = desc->has_alt_reset; - qproc->need_mem_protection = desc->need_mem_protection; - - ret = qcom_q6v5_init(&qproc->q6v5, pdev, rproc, MPSS_CRASH_REASON_SMEM, - qcom_msa_handover); - if (ret) - goto free_rproc; - - qproc->mpss_perm = BIT(QCOM_SCM_VMID_HLOS); - qproc->mba_perm = BIT(QCOM_SCM_VMID_HLOS); - qcom_add_glink_subdev(rproc, &qproc->glink_subdev); - qcom_add_smd_subdev(rproc, &qproc->smd_subdev); - qcom_add_ssr_subdev(rproc, &qproc->ssr_subdev, "mpss"); - qproc->sysmon = qcom_add_sysmon_subdev(rproc, "modem", 0x12); - - ret = rproc_add(rproc); - if (ret) - goto free_rproc; - - return 0; - -free_rproc: - rproc_free(rproc); - - return ret; -} - -static int q6v5_remove(struct platform_device *pdev) -{ - struct q6v5 *qproc = platform_get_drvdata(pdev); - - rproc_del(qproc->rproc); - - qcom_remove_sysmon_subdev(qproc->sysmon); - qcom_remove_glink_subdev(qproc->rproc, &qproc->glink_subdev); - qcom_remove_smd_subdev(qproc->rproc, &qproc->smd_subdev); - qcom_remove_ssr_subdev(qproc->rproc, &qproc->ssr_subdev); - rproc_free(qproc->rproc); - - return 0; -} - -static const struct rproc_hexagon_res sdm845_mss = { - .hexagon_mba_image = "mba.mbn", - .proxy_clk_names = (char*[]){ - "xo", - "prng", - NULL - }, - .reset_clk_names = (char*[]){ - "iface", - "snoc_axi", - NULL - }, - .active_clk_names = (char*[]){ - "bus", - "mem", - "gpll0_mss", - "mnoc_axi", - NULL - }, - .need_mem_protection = true, - .has_alt_reset = true, - .version = MSS_SDM845, -}; - -static const struct rproc_hexagon_res msm8996_mss = { - .hexagon_mba_image = "mba.mbn", - .proxy_clk_names = (char*[]){ - "xo", - "pnoc", - NULL - }, - .active_clk_names = (char*[]){ - "iface", - "bus", - "mem", - "gpll0_mss_clk", - NULL - }, - .need_mem_protection = true, - .has_alt_reset = false, - .version = MSS_MSM8996, -}; - -static const struct rproc_hexagon_res msm8916_mss = { - .hexagon_mba_image = "mba.mbn", - .proxy_supply = (struct qcom_mss_reg_res[]) { - { - .supply = "mx", - .uV = 1050000, - }, - { - .supply = "cx", - .uA = 100000, - }, - { - .supply = "pll", - .uA = 100000, - }, - {} - }, - .proxy_clk_names = (char*[]){ - "xo", - NULL - }, - .active_clk_names = (char*[]){ - "iface", - "bus", - "mem", - NULL - }, - .need_mem_protection = false, - .has_alt_reset = false, - .version = MSS_MSM8916, -}; - -static const struct rproc_hexagon_res msm8974_mss = { - .hexagon_mba_image = "mba.b00", - .proxy_supply = (struct qcom_mss_reg_res[]) { - { - .supply = "mx", - .uV = 1050000, - }, - { - .supply = "cx", - .uA = 100000, - }, - { - .supply = "pll", - .uA = 100000, - }, - {} - }, - .active_supply = (struct qcom_mss_reg_res[]) { - { - .supply = "mss", - .uV = 1050000, - .uA = 100000, - }, - {} - }, - .proxy_clk_names = (char*[]){ - "xo", - NULL - }, - .active_clk_names = (char*[]){ - "iface", - "bus", - "mem", - NULL - }, - .need_mem_protection = false, - .has_alt_reset = false, - .version = MSS_MSM8974, -}; - -static const struct of_device_id q6v5_of_match[] = { - { .compatible = "qcom,q6v5-pil", .data = &msm8916_mss}, - { .compatible = "qcom,msm8916-mss-pil", .data = &msm8916_mss}, - { .compatible = "qcom,msm8974-mss-pil", .data = &msm8974_mss}, - { .compatible = "qcom,msm8996-mss-pil", .data = &msm8996_mss}, - { .compatible = "qcom,sdm845-mss-pil", .data = &sdm845_mss}, - { }, -}; -MODULE_DEVICE_TABLE(of, q6v5_of_match); - -static struct platform_driver q6v5_driver = { - .probe = q6v5_probe, - .remove = q6v5_remove, - .driver = { - .name = "qcom-q6v5-pil", - .of_match_table = q6v5_of_match, - }, -}; -module_platform_driver(q6v5_driver); - -MODULE_DESCRIPTION("Peripheral Image Loader for Hexagon"); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3