diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-02-23 20:38:10 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-02-23 20:38:10 +0300 |
commit | a3919caaa27a5fde1cbda46e394bb17953e104a1 (patch) | |
tree | 9b4e8ec6df45722380c4255ac019a260757ae1cb /drivers | |
parent | 15192b029509316af4977d2cd389c1eb11183d13 (diff) | |
parent | 01625cc5e65f420dbea57f7de6b9552a0f807a84 (diff) | |
download | linux-a3919caaa27a5fde1cbda46e394bb17953e104a1.tar.xz |
Merge tag 'rproc-v4.11' of git://github.com/andersson/remoteproc
Pull remoteproc updates from Bjorn Andersson:
"This introduces support for booting the dedicated sensor core in the
Qualcomm MSM8996, updates the Qualcomm ADSP and Hexagon drivers to
utilize SMD subdevice helpers for properly handle shutdowns and
restarts of the remoteproc, add virtio support to the ST remoteproc
and refactor the Qualcomm Hexagon driver to handle variations between
platforms.
The support code for parsing, loading and authenticating Qualcomm
firmware files (MDT) is refactored and move to drivers/soc/qcom, to
allow for non-remoteproc drivers to utilize this.
Finally it brings some cleanups to the remoteproc core"
* tag 'rproc-v4.11' of git://github.com/andersson/remoteproc: (27 commits)
remoteproc: qcom: mdt_loader: Use signed type for offset
remoteproc: st: add virtio communication support
remoteproc: st: correct probe error management
remoteproc: Modify the function names
remoteproc: Reduce asynchronous request_firmware to auto-boot only
remoteproc: Drop qcom_scm_pas_supported() from adsp_probe()
MAINTAINERS: Add missing rpmsg include path
remoteproc: qcom: Use common SMD edge handler
remoteproc: qcom: wcnss: Make SMD handling common
remoteproc: Move qcom_mdt_loader into drivers/soc/qcom
remoteproc: qcom: mdt_loader: Refactor MDT loader
remoteproc: qcom: mdt_loader: Don't overwrite firmware object
remoteproc: qcom: Extract non-mdt related helper
remoteproc: qcom: q6v5: Decouple driver from MDT loader
remoteproc: qcom: q6v5: Remove mss supply from 8916
remoteproc: qcom: fix initializers for qcom_mss_reg_res array
remoteproc: Drop firmware_loading_complete
remoteproc: Add RPROC_DELETED state
remoteproc: Move rproc_delete_debug_dir() to rproc_del()
remoteproc: qcom: Add SLPI rproc support to load and boot slpi proc.
...
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/remoteproc/Kconfig | 18 | ||||
-rw-r--r-- | drivers/remoteproc/Makefile | 2 | ||||
-rw-r--r-- | drivers/remoteproc/da8xx_remoteproc.c | 2 | ||||
-rw-r--r-- | drivers/remoteproc/omap_remoteproc.c | 2 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_adsp_pil.c | 134 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_common.c | 96 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_common.h | 22 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_mdt_loader.c | 180 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_mdt_loader.h | 13 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_q6v5_pil.c | 531 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_wcnss.c | 60 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_core.c | 52 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_sysfs.c | 1 | ||||
-rw-r--r-- | drivers/remoteproc/st_remoteproc.c | 119 | ||||
-rw-r--r-- | drivers/remoteproc/st_slim_rproc.c | 2 | ||||
-rw-r--r-- | drivers/remoteproc/wkup_m3_rproc.c | 2 | ||||
-rw-r--r-- | drivers/soc/qcom/Kconfig | 4 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 1 | ||||
-rw-r--r-- | drivers/soc/qcom/mdt_loader.c | 204 |
19 files changed, 966 insertions, 479 deletions
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index 8f9cf0bc571c..65f86bc24c07 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -7,6 +7,9 @@ config REMOTEPROC select FW_LOADER select VIRTIO select VIRTUALIZATION + help + Support for remote processors (such as DSP coprocessors). These + are mainly used on embedded systems. if REMOTEPROC @@ -25,11 +28,11 @@ config OMAP_REMOTEPROC Currently only supported on OMAP4. - Usually you want to say y here, in order to enable multimedia + Usually you want to say Y here, in order to enable multimedia use-cases to run on your platform (multimedia codecs are offloaded to remote DSP processors using this framework). - It's safe to say n here if you're not interested in multimedia + It's safe to say N here if you're not interested in multimedia offloading or just want a bare minimum kernel. config WKUP_M3_RPROC @@ -73,14 +76,16 @@ config QCOM_ADSP_PIL depends on OF && ARCH_QCOM depends on REMOTEPROC depends on QCOM_SMEM + depends on QCOM_SMD || (COMPILE_TEST && QCOM_SMD=n) select MFD_SYSCON select QCOM_MDT_LOADER + select QCOM_RPROC_COMMON select QCOM_SCM help Say y here to support the TrustZone based Peripherial Image Loader for the Qualcomm ADSP remote processors. -config QCOM_MDT_LOADER +config QCOM_RPROC_COMMON tristate config QCOM_Q6V5_PIL @@ -88,8 +93,9 @@ config QCOM_Q6V5_PIL depends on OF && ARCH_QCOM depends on QCOM_SMEM depends on REMOTEPROC + depends on QCOM_SMD || (COMPILE_TEST && QCOM_SMD=n) select MFD_SYSCON - select QCOM_MDT_LOADER + select QCOM_RPROC_COMMON select QCOM_SCM help Say y here to support the Qualcomm Peripherial Image Loader for the @@ -102,6 +108,7 @@ config QCOM_WCNSS_PIL depends on QCOM_SMEM depends on REMOTEPROC select QCOM_MDT_LOADER + select QCOM_RPROC_COMMON select QCOM_SCM help Say y here to support the Peripheral Image Loader for the Qualcomm @@ -111,6 +118,9 @@ config ST_REMOTEPROC tristate "ST remoteproc support" depends on ARCH_STI depends on REMOTEPROC + select MAILBOX + select STI_MBOX + select RPMSG_VIRTIO help Say y here to support ST's adjunct processors via the remote processor framework. diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 0938ea3c41ba..ffc5e430df27 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -12,7 +12,7 @@ obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o obj-$(CONFIG_QCOM_ADSP_PIL) += qcom_adsp_pil.o -obj-$(CONFIG_QCOM_MDT_LOADER) += qcom_mdt_loader.o +obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss_pil.o qcom_wcnss_pil-y += qcom_wcnss.o diff --git a/drivers/remoteproc/da8xx_remoteproc.c b/drivers/remoteproc/da8xx_remoteproc.c index 1afac8f31be0..3814de28599c 100644 --- a/drivers/remoteproc/da8xx_remoteproc.c +++ b/drivers/remoteproc/da8xx_remoteproc.c @@ -151,7 +151,7 @@ static void da8xx_rproc_kick(struct rproc *rproc, int vqid) writel(SYSCFG_CHIPSIG2, drproc->chipsig); } -static struct rproc_ops da8xx_rproc_ops = { +static const struct rproc_ops da8xx_rproc_ops = { .start = da8xx_rproc_start, .stop = da8xx_rproc_stop, .kick = da8xx_rproc_kick, diff --git a/drivers/remoteproc/omap_remoteproc.c b/drivers/remoteproc/omap_remoteproc.c index fa63bf2eb885..a96ce9083f7f 100644 --- a/drivers/remoteproc/omap_remoteproc.c +++ b/drivers/remoteproc/omap_remoteproc.c @@ -177,7 +177,7 @@ static int omap_rproc_stop(struct rproc *rproc) return 0; } -static struct rproc_ops omap_rproc_ops = { +static const struct rproc_ops omap_rproc_ops = { .start = omap_rproc_start, .stop = omap_rproc_stop, .kick = omap_rproc_kick, diff --git a/drivers/remoteproc/qcom_adsp_pil.c b/drivers/remoteproc/qcom_adsp_pil.c index 43a4ed2f346c..49fe2f807e1d 100644 --- a/drivers/remoteproc/qcom_adsp_pil.c +++ b/drivers/remoteproc/qcom_adsp_pil.c @@ -1,5 +1,5 @@ /* - * Qualcomm ADSP Peripheral Image Loader for MSM8974 and MSM8996 + * Qualcomm ADSP/SLPI Peripheral Image Loader for MSM8974 and MSM8996 * * Copyright (C) 2016 Linaro Ltd * Copyright (C) 2014 Sony Mobile Communications AB @@ -26,15 +26,19 @@ #include <linux/qcom_scm.h> #include <linux/regulator/consumer.h> #include <linux/remoteproc.h> +#include <linux/soc/qcom/mdt_loader.h> #include <linux/soc/qcom/smem.h> #include <linux/soc/qcom/smem_state.h> -#include "qcom_mdt_loader.h" +#include "qcom_common.h" #include "remoteproc_internal.h" -#define ADSP_CRASH_REASON_SMEM 423 -#define ADSP_FIRMWARE_NAME "adsp.mdt" -#define ADSP_PAS_ID 1 +struct adsp_data { + int crash_reason_smem; + const char *firmware_name; + int pas_id; + bool has_aggre2_clk; +}; struct qcom_adsp { struct device *dev; @@ -50,8 +54,14 @@ struct qcom_adsp { unsigned stop_bit; struct clk *xo; + struct clk *aggre2_clk; struct regulator *cx_supply; + struct regulator *px_supply; + + int pas_id; + int crash_reason_smem; + bool has_aggre2_clk; struct completion start_done; struct completion stop_done; @@ -60,39 +70,16 @@ struct qcom_adsp { phys_addr_t mem_reloc; void *mem_region; size_t mem_size; + + struct qcom_rproc_subdev smd_subdev; }; static int adsp_load(struct rproc *rproc, const struct firmware *fw) { struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; - phys_addr_t fw_addr; - size_t fw_size; - bool relocate; - int ret; - - ret = qcom_scm_pas_init_image(ADSP_PAS_ID, fw->data, fw->size); - if (ret) { - dev_err(&rproc->dev, "invalid firmware metadata\n"); - return ret; - } - - ret = qcom_mdt_parse(fw, &fw_addr, &fw_size, &relocate); - if (ret) { - dev_err(&rproc->dev, "failed to parse mdt header\n"); - return ret; - } - if (relocate) { - adsp->mem_reloc = fw_addr; - - ret = qcom_scm_pas_mem_setup(ADSP_PAS_ID, adsp->mem_phys, fw_size); - if (ret) { - dev_err(&rproc->dev, "unable to setup memory for image\n"); - return ret; - } - } - - return qcom_mdt_load(rproc, fw, rproc->firmware); + return qcom_mdt_load(adsp->dev, fw, rproc->firmware, adsp->pas_id, + adsp->mem_region, adsp->mem_phys, adsp->mem_size); } static const struct rproc_fw_ops adsp_fw_ops = { @@ -109,31 +96,43 @@ static int adsp_start(struct rproc *rproc) if (ret) return ret; + ret = clk_prepare_enable(adsp->aggre2_clk); + if (ret) + goto disable_xo_clk; + ret = regulator_enable(adsp->cx_supply); if (ret) - goto disable_clocks; + goto disable_aggre2_clk; + + ret = regulator_enable(adsp->px_supply); + if (ret) + goto disable_cx_supply; - ret = qcom_scm_pas_auth_and_reset(ADSP_PAS_ID); + ret = qcom_scm_pas_auth_and_reset(adsp->pas_id); if (ret) { dev_err(adsp->dev, "failed to authenticate image and release reset\n"); - goto disable_regulators; + goto disable_px_supply; } ret = wait_for_completion_timeout(&adsp->start_done, msecs_to_jiffies(5000)); if (!ret) { dev_err(adsp->dev, "start timed out\n"); - qcom_scm_pas_shutdown(ADSP_PAS_ID); + qcom_scm_pas_shutdown(adsp->pas_id); ret = -ETIMEDOUT; - goto disable_regulators; + goto disable_px_supply; } ret = 0; -disable_regulators: +disable_px_supply: + regulator_disable(adsp->px_supply); +disable_cx_supply: regulator_disable(adsp->cx_supply); -disable_clocks: +disable_aggre2_clk: + clk_disable_unprepare(adsp->aggre2_clk); +disable_xo_clk: clk_disable_unprepare(adsp->xo); return ret; @@ -157,7 +156,7 @@ static int adsp_stop(struct rproc *rproc) BIT(adsp->stop_bit), 0); - ret = qcom_scm_pas_shutdown(ADSP_PAS_ID); + ret = qcom_scm_pas_shutdown(adsp->pas_id); if (ret) dev_err(adsp->dev, "failed to shutdown: %d\n", ret); @@ -197,7 +196,7 @@ static irqreturn_t adsp_fatal_interrupt(int irq, void *dev) size_t len; char *msg; - msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, ADSP_CRASH_REASON_SMEM, &len); + msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, adsp->crash_reason_smem, &len); if (!IS_ERR(msg) && len > 0 && msg[0]) dev_err(adsp->dev, "fatal error received: %s\n", msg); @@ -244,6 +243,17 @@ static int adsp_init_clock(struct qcom_adsp *adsp) return ret; } + if (adsp->has_aggre2_clk) { + adsp->aggre2_clk = devm_clk_get(adsp->dev, "aggre2"); + if (IS_ERR(adsp->aggre2_clk)) { + ret = PTR_ERR(adsp->aggre2_clk); + if (ret != -EPROBE_DEFER) + dev_err(adsp->dev, + "failed to get aggre2 clock"); + return ret; + } + } + return 0; } @@ -255,6 +265,10 @@ static int adsp_init_regulator(struct qcom_adsp *adsp) regulator_set_load(adsp->cx_supply, 100000); + adsp->px_supply = devm_regulator_get(adsp->dev, "px"); + if (IS_ERR(adsp->px_supply)) + return PTR_ERR(adsp->px_supply); + return 0; } @@ -311,20 +325,20 @@ static int adsp_alloc_memory_region(struct qcom_adsp *adsp) static int adsp_probe(struct platform_device *pdev) { + const struct adsp_data *desc; struct qcom_adsp *adsp; struct rproc *rproc; int ret; + desc = of_device_get_match_data(&pdev->dev); + if (!desc) + return -EINVAL; + if (!qcom_scm_is_available()) return -EPROBE_DEFER; - if (!qcom_scm_pas_supported(ADSP_PAS_ID)) { - dev_err(&pdev->dev, "PAS is not available for ADSP\n"); - return -ENXIO; - } - rproc = rproc_alloc(&pdev->dev, pdev->name, &adsp_ops, - ADSP_FIRMWARE_NAME, sizeof(*adsp)); + desc->firmware_name, sizeof(*adsp)); if (!rproc) { dev_err(&pdev->dev, "unable to allocate remoteproc\n"); return -ENOMEM; @@ -335,6 +349,9 @@ static int adsp_probe(struct platform_device *pdev) adsp = (struct qcom_adsp *)rproc->priv; adsp->dev = &pdev->dev; adsp->rproc = rproc; + adsp->pas_id = desc->pas_id; + adsp->crash_reason_smem = desc->crash_reason_smem; + adsp->has_aggre2_clk = desc->has_aggre2_clk; platform_set_drvdata(pdev, adsp); init_completion(&adsp->start_done); @@ -384,6 +401,8 @@ static int adsp_probe(struct platform_device *pdev) goto free_rproc; } + qcom_add_smd_subdev(rproc, &adsp->smd_subdev); + ret = rproc_add(rproc); if (ret) goto free_rproc; @@ -402,14 +421,31 @@ static int adsp_remove(struct platform_device *pdev) qcom_smem_state_put(adsp->state); rproc_del(adsp->rproc); + + qcom_remove_smd_subdev(adsp->rproc, &adsp->smd_subdev); rproc_free(adsp->rproc); return 0; } +static const struct adsp_data adsp_resource_init = { + .crash_reason_smem = 423, + .firmware_name = "adsp.mdt", + .pas_id = 1, + .has_aggre2_clk = false, +}; + +static const struct adsp_data slpi_resource_init = { + .crash_reason_smem = 424, + .firmware_name = "slpi.mdt", + .pas_id = 12, + .has_aggre2_clk = true, +}; + static const struct of_device_id adsp_of_match[] = { - { .compatible = "qcom,msm8974-adsp-pil" }, - { .compatible = "qcom,msm8996-adsp-pil" }, + { .compatible = "qcom,msm8974-adsp-pil", .data = &adsp_resource_init}, + { .compatible = "qcom,msm8996-adsp-pil", .data = &adsp_resource_init}, + { .compatible = "qcom,msm8996-slpi-pil", .data = &slpi_resource_init}, { }, }; MODULE_DEVICE_TABLE(of, adsp_of_match); diff --git a/drivers/remoteproc/qcom_common.c b/drivers/remoteproc/qcom_common.c new file mode 100644 index 000000000000..bb90481215c6 --- /dev/null +++ b/drivers/remoteproc/qcom_common.c @@ -0,0 +1,96 @@ +/* + * Qualcomm Peripheral Image Loader helpers + * + * Copyright (C) 2016 Linaro Ltd + * Copyright (C) 2015 Sony Mobile Communications Inc + * 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 <linux/firmware.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/remoteproc.h> +#include <linux/rpmsg/qcom_smd.h> + +#include "remoteproc_internal.h" +#include "qcom_common.h" + +#define to_smd_subdev(d) container_of(d, struct qcom_rproc_subdev, subdev) + +/** + * qcom_mdt_find_rsc_table() - provide dummy resource table for remoteproc + * @rproc: remoteproc handle + * @fw: firmware header + * @tablesz: outgoing size of the table + * + * Returns a dummy table. + */ +struct resource_table *qcom_mdt_find_rsc_table(struct rproc *rproc, + const struct firmware *fw, + int *tablesz) +{ + static struct resource_table table = { .ver = 1, }; + + *tablesz = sizeof(table); + return &table; +} +EXPORT_SYMBOL_GPL(qcom_mdt_find_rsc_table); + +static int smd_subdev_probe(struct rproc_subdev *subdev) +{ + struct qcom_rproc_subdev *smd = to_smd_subdev(subdev); + + smd->edge = qcom_smd_register_edge(smd->dev, smd->node); + + return IS_ERR(smd->edge) ? PTR_ERR(smd->edge) : 0; +} + +static void smd_subdev_remove(struct rproc_subdev *subdev) +{ + struct qcom_rproc_subdev *smd = to_smd_subdev(subdev); + + qcom_smd_unregister_edge(smd->edge); + smd->edge = NULL; +} + +/** + * qcom_add_smd_subdev() - try to add a SMD subdevice to rproc + * @rproc: rproc handle to parent the subdevice + * @smd: reference to a Qualcomm subdev context + */ +void qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd) +{ + struct device *dev = &rproc->dev; + + smd->node = of_get_child_by_name(dev->parent->of_node, "smd-edge"); + if (!smd->node) + return; + + smd->dev = dev; + rproc_add_subdev(rproc, &smd->subdev, smd_subdev_probe, smd_subdev_remove); +} +EXPORT_SYMBOL_GPL(qcom_add_smd_subdev); + +/** + * qcom_remove_smd_subdev() - remove the smd subdevice from rproc + * @rproc: rproc handle + * @smd: the SMD subdevice to remove + */ +void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd) +{ + rproc_remove_subdev(rproc, &smd->subdev); + of_node_put(smd->node); +} +EXPORT_SYMBOL_GPL(qcom_remove_smd_subdev); + +MODULE_DESCRIPTION("Qualcomm Remoteproc helper driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/remoteproc/qcom_common.h b/drivers/remoteproc/qcom_common.h new file mode 100644 index 000000000000..db5c826d5cd4 --- /dev/null +++ b/drivers/remoteproc/qcom_common.h @@ -0,0 +1,22 @@ +#ifndef __RPROC_QCOM_COMMON_H__ +#define __RPROC_QCOM_COMMON_H__ + +#include <linux/remoteproc.h> +#include "remoteproc_internal.h" + +struct qcom_rproc_subdev { + struct rproc_subdev subdev; + + struct device *dev; + struct device_node *node; + struct qcom_smd_edge *edge; +}; + +struct resource_table *qcom_mdt_find_rsc_table(struct rproc *rproc, + const struct firmware *fw, + int *tablesz); + +void qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd); +void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd); + +#endif diff --git a/drivers/remoteproc/qcom_mdt_loader.c b/drivers/remoteproc/qcom_mdt_loader.c deleted file mode 100644 index 2ff18cd6c096..000000000000 --- a/drivers/remoteproc/qcom_mdt_loader.c +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Qualcomm Peripheral Image Loader - * - * Copyright (C) 2016 Linaro Ltd - * Copyright (C) 2015 Sony Mobile Communications Inc - * 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 <linux/elf.h> -#include <linux/firmware.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/remoteproc.h> -#include <linux/sizes.h> -#include <linux/slab.h> - -#include "remoteproc_internal.h" -#include "qcom_mdt_loader.h" - -/** - * qcom_mdt_find_rsc_table() - provide dummy resource table for remoteproc - * @rproc: remoteproc handle - * @fw: firmware header - * @tablesz: outgoing size of the table - * - * Returns a dummy table. - */ -struct resource_table *qcom_mdt_find_rsc_table(struct rproc *rproc, - const struct firmware *fw, - int *tablesz) -{ - static struct resource_table table = { .ver = 1, }; - - *tablesz = sizeof(table); - return &table; -} -EXPORT_SYMBOL_GPL(qcom_mdt_find_rsc_table); - -/** - * qcom_mdt_parse() - extract useful parameters from the mdt header - * @fw: firmware handle - * @fw_addr: optional reference for base of the firmware's memory region - * @fw_size: optional reference for size of the firmware's memory region - * @fw_relocate: optional reference for flagging if the firmware is relocatable - * - * Returns 0 on success, negative errno otherwise. - */ -int qcom_mdt_parse(const struct firmware *fw, phys_addr_t *fw_addr, - size_t *fw_size, bool *fw_relocate) -{ - const struct elf32_phdr *phdrs; - const struct elf32_phdr *phdr; - const struct elf32_hdr *ehdr; - phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX; - phys_addr_t max_addr = 0; - bool relocate = false; - int i; - - ehdr = (struct elf32_hdr *)fw->data; - phdrs = (struct elf32_phdr *)(ehdr + 1); - - for (i = 0; i < ehdr->e_phnum; i++) { - phdr = &phdrs[i]; - - if (phdr->p_type != PT_LOAD) - continue; - - if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) - continue; - - if (!phdr->p_memsz) - 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); - } - - if (fw_addr) - *fw_addr = min_addr; - if (fw_size) - *fw_size = max_addr - min_addr; - if (fw_relocate) - *fw_relocate = relocate; - - return 0; -} -EXPORT_SYMBOL_GPL(qcom_mdt_parse); - -/** - * qcom_mdt_load() - load the firmware which header is defined in fw - * @rproc: rproc handle - * @fw: frimware object for the header - * @firmware: filename of the firmware, for building .bXX names - * - * Returns 0 on success, negative errno otherwise. - */ -int qcom_mdt_load(struct rproc *rproc, - const struct firmware *fw, - const char *firmware) -{ - const struct elf32_phdr *phdrs; - const struct elf32_phdr *phdr; - const struct elf32_hdr *ehdr; - size_t fw_name_len; - char *fw_name; - void *ptr; - int ret; - int i; - - ehdr = (struct elf32_hdr *)fw->data; - phdrs = (struct elf32_phdr *)(ehdr + 1); - - fw_name_len = strlen(firmware); - if (fw_name_len <= 4) - return -EINVAL; - - fw_name = kstrdup(firmware, GFP_KERNEL); - if (!fw_name) - return -ENOMEM; - - for (i = 0; i < ehdr->e_phnum; i++) { - phdr = &phdrs[i]; - - if (phdr->p_type != PT_LOAD) - continue; - - if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) - continue; - - if (!phdr->p_memsz) - continue; - - ptr = rproc_da_to_va(rproc, phdr->p_paddr, phdr->p_memsz); - if (!ptr) { - dev_err(&rproc->dev, "segment outside memory range\n"); - ret = -EINVAL; - break; - } - - if (phdr->p_filesz) { - sprintf(fw_name + fw_name_len - 3, "b%02d", i); - ret = request_firmware(&fw, fw_name, &rproc->dev); - if (ret) { - dev_err(&rproc->dev, "failed to load %s\n", - fw_name); - break; - } - - memcpy(ptr, fw->data, fw->size); - - release_firmware(fw); - } - - if (phdr->p_memsz > phdr->p_filesz) - memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); - } - - kfree(fw_name); - - return ret; -} -EXPORT_SYMBOL_GPL(qcom_mdt_load); - -MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/remoteproc/qcom_mdt_loader.h b/drivers/remoteproc/qcom_mdt_loader.h deleted file mode 100644 index c5d7122755b6..000000000000 --- a/drivers/remoteproc/qcom_mdt_loader.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef __QCOM_MDT_LOADER_H__ -#define __QCOM_MDT_LOADER_H__ - -#define QCOM_MDT_TYPE_MASK (7 << 24) -#define QCOM_MDT_TYPE_HASH (2 << 24) -#define QCOM_MDT_RELOCATABLE BIT(27) - -struct resource_table * qcom_mdt_find_rsc_table(struct rproc *rproc, const struct firmware *fw, int *tablesz); -int qcom_mdt_load(struct rproc *rproc, const struct firmware *fw, const char *fw_name); - -int qcom_mdt_parse(const struct firmware *fw, phys_addr_t *fw_addr, size_t *fw_size, bool *fw_relocate); - -#endif diff --git a/drivers/remoteproc/qcom_q6v5_pil.c b/drivers/remoteproc/qcom_q6v5_pil.c index b08989b48df7..8fd697a3cf8f 100644 --- a/drivers/remoteproc/qcom_q6v5_pil.c +++ b/drivers/remoteproc/qcom_q6v5_pil.c @@ -23,22 +23,21 @@ #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/of_address.h> +#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/remoteproc.h> #include <linux/reset.h> +#include <linux/soc/qcom/mdt_loader.h> #include <linux/soc/qcom/smem.h> #include <linux/soc/qcom/smem_state.h> #include "remoteproc_internal.h" -#include "qcom_mdt_loader.h" +#include "qcom_common.h" #include <linux/qcom_scm.h> -#define MBA_FIRMWARE_NAME "mba.b00" -#define MPSS_FIRMWARE_NAME "modem.mdt" - #define MPSS_CRASH_REASON_SMEM 421 /* RMB Status Register Values */ @@ -93,6 +92,26 @@ #define QDSS_BHS_ON BIT(21) #define QDSS_LDO_BYP BIT(22) +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 **active_clk_names; +}; + struct q6v5 { struct device *dev; struct rproc *rproc; @@ -110,11 +129,15 @@ struct q6v5 { struct qcom_smem_state *state; unsigned stop_bit; - struct regulator_bulk_data supply[4]; + struct clk *active_clks[8]; + struct clk *proxy_clks[4]; + int active_clk_count; + int proxy_clk_count; - struct clk *ahb_clk; - struct clk *axi_clk; - struct clk *rom_clk; + struct reg_info active_regs[1]; + struct reg_info proxy_regs[3]; + int active_reg_count; + int proxy_reg_count; struct completion start_done; struct completion stop_done; @@ -128,65 +151,141 @@ struct q6v5 { phys_addr_t mpss_reloc; void *mpss_region; size_t mpss_size; -}; -enum { - Q6V5_SUPPLY_CX, - Q6V5_SUPPLY_MX, - Q6V5_SUPPLY_MSS, - Q6V5_SUPPLY_PLL, + struct qcom_rproc_subdev smd_subdev; }; -static int q6v5_regulator_init(struct q6v5 *qproc) +static int q6v5_regulator_init(struct device *dev, struct reg_info *regs, + const struct qcom_mss_reg_res *reg_res) { - int ret; + int rc; + int i; - qproc->supply[Q6V5_SUPPLY_CX].supply = "cx"; - qproc->supply[Q6V5_SUPPLY_MX].supply = "mx"; - qproc->supply[Q6V5_SUPPLY_MSS].supply = "mss"; - qproc->supply[Q6V5_SUPPLY_PLL].supply = "pll"; + 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; + } - ret = devm_regulator_bulk_get(qproc->dev, - ARRAY_SIZE(qproc->supply), qproc->supply); - if (ret < 0) { - dev_err(qproc->dev, "failed to get supplies\n"); - return ret; + regs[i].uV = reg_res[i].uV; + regs[i].uA = reg_res[i].uA; } - regulator_set_load(qproc->supply[Q6V5_SUPPLY_CX].consumer, 100000); - regulator_set_load(qproc->supply[Q6V5_SUPPLY_MSS].consumer, 100000); - regulator_set_load(qproc->supply[Q6V5_SUPPLY_PLL].consumer, 10000); + 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 int q6v5_regulator_enable(struct q6v5 *qproc) +static void q6v5_regulator_disable(struct q6v5 *qproc, + struct reg_info *regs, int count) { - struct regulator *mss = qproc->supply[Q6V5_SUPPLY_MSS].consumer; - struct regulator *mx = qproc->supply[Q6V5_SUPPLY_MX].consumer; - int ret; + int i; - /* TODO: Q6V5_SUPPLY_CX is supposed to be set to super-turbo here */ + for (i = 0; i < count; i++) { + if (regs[i].uV > 0) + regulator_set_voltage(regs[i].reg, 0, INT_MAX); - ret = regulator_set_voltage(mx, 1050000, INT_MAX); - if (ret) - return ret; + if (regs[i].uA > 0) + regulator_set_load(regs[i].reg, 0); + + regulator_disable(regs[i].reg); + } +} - regulator_set_voltage(mss, 1000000, 1150000); +static int q6v5_clk_enable(struct device *dev, + struct clk **clks, int count) +{ + int rc; + int i; - return regulator_bulk_enable(ARRAY_SIZE(qproc->supply), qproc->supply); + 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_regulator_disable(struct q6v5 *qproc) +static void q6v5_clk_disable(struct device *dev, + struct clk **clks, int count) { - struct regulator *mss = qproc->supply[Q6V5_SUPPLY_MSS].consumer; - struct regulator *mx = qproc->supply[Q6V5_SUPPLY_MX].consumer; + int i; + + for (i = 0; i < count; i++) + clk_disable_unprepare(clks[i]); +} - /* TODO: Q6V5_SUPPLY_CX corner votes should be released */ +static struct resource_table *q6v5_find_rsc_table(struct rproc *rproc, + const struct firmware *fw, + int *tablesz) +{ + static struct resource_table table = { .ver = 1, }; - regulator_bulk_disable(ARRAY_SIZE(qproc->supply), qproc->supply); - regulator_set_voltage(mx, 0, INT_MAX); - regulator_set_voltage(mss, 0, 1150000); + *tablesz = sizeof(table); + return &table; } static int q6v5_load(struct rproc *rproc, const struct firmware *fw) @@ -199,7 +298,7 @@ static int q6v5_load(struct rproc *rproc, const struct firmware *fw) } static const struct rproc_fw_ops q6v5_fw_ops = { - .find_rsc_table = qcom_mdt_find_rsc_table, + .find_rsc_table = q6v5_find_rsc_table, .load = q6v5_load, }; @@ -376,45 +475,109 @@ static int q6v5_mpss_init_image(struct q6v5 *qproc, const struct firmware *fw) return ret < 0 ? ret : 0; } -static int q6v5_mpss_validate(struct q6v5 *qproc, const struct firmware *fw) +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 fw_addr; - bool relocate; + phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX; + phys_addr_t max_addr = 0; + bool relocate = false; + char seg_name[10]; + ssize_t offset; size_t size; + void *ptr; int ret; int i; - ret = qcom_mdt_parse(fw, &fw_addr, NULL, &relocate); - if (ret) { - dev_err(qproc->dev, "failed to parse mdt header\n"); + ret = request_firmware(&fw, "modem.mdt", qproc->dev); + if (ret < 0) { + dev_err(qproc->dev, "unable to load modem.mdt\n"); return ret; } - if (relocate) - boot_addr = qproc->mpss_phys; - else - boot_addr = fw_addr; + /* 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++) { + + for (i = 0; i < ehdr->e_phnum; i++) { phdr = &phdrs[i]; - if (phdr->p_type != PT_LOAD) + if (!q6v5_phdr_valid(phdr)) continue; - if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) - 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; - if (!phdr->p_memsz) + 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 = readl(qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG); if (!size) { + 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); } @@ -429,44 +592,6 @@ static int q6v5_mpss_validate(struct q6v5 *qproc, const struct firmware *fw) else if (ret < 0) dev_err(qproc->dev, "MPSS authentication failed: %d\n", ret); - return ret < 0 ? ret : 0; -} - -static int q6v5_mpss_load(struct q6v5 *qproc) -{ - const struct firmware *fw; - phys_addr_t fw_addr; - bool relocate; - int ret; - - ret = request_firmware(&fw, MPSS_FIRMWARE_NAME, qproc->dev); - if (ret < 0) { - dev_err(qproc->dev, "unable to load " MPSS_FIRMWARE_NAME "\n"); - return ret; - } - - ret = qcom_mdt_parse(fw, &fw_addr, NULL, &relocate); - if (ret) { - dev_err(qproc->dev, "failed to parse mdt header\n"); - goto release_firmware; - } - - if (relocate) - qproc->mpss_reloc = fw_addr; - - /* 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; - - ret = qcom_mdt_load(qproc->rproc, fw, MPSS_FIRMWARE_NAME); - if (ret) - goto release_firmware; - - ret = q6v5_mpss_validate(qproc, fw); - release_firmware: release_firmware(fw); @@ -478,29 +603,38 @@ static int q6v5_start(struct rproc *rproc) struct q6v5 *qproc = (struct q6v5 *)rproc->priv; int ret; - ret = q6v5_regulator_enable(qproc); + ret = q6v5_regulator_enable(qproc, qproc->proxy_regs, + qproc->proxy_reg_count); if (ret) { - dev_err(qproc->dev, "failed to enable supplies\n"); + dev_err(qproc->dev, "failed to enable proxy supplies\n"); return ret; } + 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 = reset_control_deassert(qproc->mss_restart); if (ret) { dev_err(qproc->dev, "failed to deassert mss restart\n"); goto disable_vdd; } - ret = clk_prepare_enable(qproc->ahb_clk); - if (ret) + 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; - - ret = clk_prepare_enable(qproc->axi_clk); - if (ret) - goto disable_ahb_clk; - - ret = clk_prepare_enable(qproc->rom_clk); - if (ret) - goto disable_axi_clk; + } writel(qproc->mba_phys, qproc->rmb_base + RMB_MBA_IMAGE_REG); @@ -535,7 +669,10 @@ static int q6v5_start(struct rproc *rproc) qproc->running = true; - /* TODO: All done, release the handover resources */ + q6v5_clk_disable(qproc->dev, qproc->proxy_clks, + qproc->proxy_clk_count); + q6v5_regulator_disable(qproc, qproc->proxy_regs, + qproc->proxy_reg_count); return 0; @@ -543,16 +680,19 @@ 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); - - clk_disable_unprepare(qproc->rom_clk); -disable_axi_clk: - clk_disable_unprepare(qproc->axi_clk); -disable_ahb_clk: - clk_disable_unprepare(qproc->ahb_clk); + q6v5_clk_disable(qproc->dev, qproc->active_clks, + qproc->active_clk_count); assert_reset: reset_control_assert(qproc->mss_restart); disable_vdd: - q6v5_regulator_disable(qproc); + 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); return ret; } @@ -579,10 +719,10 @@ static int q6v5_stop(struct rproc *rproc) q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_nc); reset_control_assert(qproc->mss_restart); - clk_disable_unprepare(qproc->rom_clk); - clk_disable_unprepare(qproc->axi_clk); - clk_disable_unprepare(qproc->ahb_clk); - q6v5_regulator_disable(qproc); + 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; } @@ -702,27 +842,27 @@ static int q6v5_init_mem(struct q6v5 *qproc, struct platform_device *pdev) return 0; } -static int q6v5_init_clocks(struct q6v5 *qproc) +static int q6v5_init_clocks(struct device *dev, struct clk **clks, + char **clk_names) { - qproc->ahb_clk = devm_clk_get(qproc->dev, "iface"); - if (IS_ERR(qproc->ahb_clk)) { - dev_err(qproc->dev, "failed to get iface clock\n"); - return PTR_ERR(qproc->ahb_clk); - } + int i; - qproc->axi_clk = devm_clk_get(qproc->dev, "bus"); - if (IS_ERR(qproc->axi_clk)) { - dev_err(qproc->dev, "failed to get bus clock\n"); - return PTR_ERR(qproc->axi_clk); - } + if (!clk_names) + return 0; - qproc->rom_clk = devm_clk_get(qproc->dev, "mem"); - if (IS_ERR(qproc->rom_clk)) { - dev_err(qproc->dev, "failed to get mem clock\n"); - return PTR_ERR(qproc->rom_clk); + 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 0; + return i; } static int q6v5_init_reset(struct q6v5 *qproc) @@ -805,12 +945,17 @@ static int q6v5_alloc_memory_region(struct q6v5 *qproc) 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, - MBA_FIRMWARE_NAME, sizeof(*qproc)); + desc->hexagon_mba_image, sizeof(*qproc)); if (!rproc) { dev_err(&pdev->dev, "failed to allocate rproc\n"); return -ENOMEM; @@ -834,13 +979,37 @@ static int q6v5_probe(struct platform_device *pdev) if (ret) goto free_rproc; - ret = q6v5_init_clocks(qproc); - if (ret) + 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_regulator_init(qproc); - if (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) @@ -868,6 +1037,8 @@ static int q6v5_probe(struct platform_device *pdev) goto free_rproc; } + qcom_add_smd_subdev(rproc, &qproc->smd_subdev); + ret = rproc_add(rproc); if (ret) goto free_rproc; @@ -885,13 +1056,83 @@ static int q6v5_remove(struct platform_device *pdev) struct q6v5 *qproc = platform_get_drvdata(pdev); rproc_del(qproc->rproc); + + qcom_remove_smd_subdev(qproc->rproc, &qproc->smd_subdev); rproc_free(qproc->rproc); return 0; } +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 + }, +}; + +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 + }, +}; + static const struct of_device_id q6v5_of_match[] = { - { .compatible = "qcom,q6v5-pil", }, + { .compatible = "qcom,q6v5-pil", .data = &msm8916_mss}, + { .compatible = "qcom,msm8916-mss-pil", .data = &msm8916_mss}, + { .compatible = "qcom,msm8974-mss-pil", .data = &msm8974_mss}, { }, }; MODULE_DEVICE_TABLE(of, q6v5_of_match); diff --git a/drivers/remoteproc/qcom_wcnss.c b/drivers/remoteproc/qcom_wcnss.c index ebd61f5d18bb..c7686393d505 100644 --- a/drivers/remoteproc/qcom_wcnss.c +++ b/drivers/remoteproc/qcom_wcnss.c @@ -28,11 +28,12 @@ #include <linux/qcom_scm.h> #include <linux/regulator/consumer.h> #include <linux/remoteproc.h> +#include <linux/soc/qcom/mdt_loader.h> #include <linux/soc/qcom/smem.h> #include <linux/soc/qcom/smem_state.h> #include <linux/rpmsg/qcom_smd.h> -#include "qcom_mdt_loader.h" +#include "qcom_common.h" #include "remoteproc_internal.h" #include "qcom_wcnss.h" @@ -96,9 +97,7 @@ struct qcom_wcnss { void *mem_region; size_t mem_size; - struct device_node *smd_node; - struct qcom_smd_edge *smd_edge; - struct rproc_subdev smd_subdev; + struct qcom_rproc_subdev smd_subdev; }; static const struct wcnss_data riva_data = { @@ -152,34 +151,9 @@ void qcom_wcnss_assign_iris(struct qcom_wcnss *wcnss, static int wcnss_load(struct rproc *rproc, const struct firmware *fw) { struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv; - phys_addr_t fw_addr; - size_t fw_size; - bool relocate; - int ret; - - ret = qcom_scm_pas_init_image(WCNSS_PAS_ID, fw->data, fw->size); - if (ret) { - dev_err(&rproc->dev, "invalid firmware metadata\n"); - return ret; - } - - ret = qcom_mdt_parse(fw, &fw_addr, &fw_size, &relocate); - if (ret) { - dev_err(&rproc->dev, "failed to parse mdt header\n"); - return ret; - } - if (relocate) { - wcnss->mem_reloc = fw_addr; - - ret = qcom_scm_pas_mem_setup(WCNSS_PAS_ID, wcnss->mem_phys, fw_size); - if (ret) { - dev_err(&rproc->dev, "unable to setup memory for image\n"); - return ret; - } - } - - return qcom_mdt_load(rproc, fw, rproc->firmware); + return qcom_mdt_load(wcnss->dev, fw, rproc->firmware, WCNSS_PAS_ID, + wcnss->mem_region, wcnss->mem_phys, wcnss->mem_size); } static const struct rproc_fw_ops wcnss_fw_ops = { @@ -400,23 +374,6 @@ static irqreturn_t wcnss_stop_ack_interrupt(int irq, void *dev) return IRQ_HANDLED; } -static int wcnss_smd_probe(struct rproc_subdev *subdev) -{ - struct qcom_wcnss *wcnss = container_of(subdev, struct qcom_wcnss, smd_subdev); - - wcnss->smd_edge = qcom_smd_register_edge(wcnss->dev, wcnss->smd_node); - - return IS_ERR(wcnss->smd_edge) ? PTR_ERR(wcnss->smd_edge) : 0; -} - -static void wcnss_smd_remove(struct rproc_subdev *subdev) -{ - struct qcom_wcnss *wcnss = container_of(subdev, struct qcom_wcnss, smd_subdev); - - qcom_smd_unregister_edge(wcnss->smd_edge); - wcnss->smd_edge = NULL; -} - static int wcnss_init_regulators(struct qcom_wcnss *wcnss, const struct wcnss_vreg_info *info, int num_vregs) @@ -599,9 +556,7 @@ static int wcnss_probe(struct platform_device *pdev) } } - wcnss->smd_node = of_get_child_by_name(pdev->dev.of_node, "smd-edge"); - if (wcnss->smd_node) - rproc_add_subdev(rproc, &wcnss->smd_subdev, wcnss_smd_probe, wcnss_smd_remove); + qcom_add_smd_subdev(rproc, &wcnss->smd_subdev); ret = rproc_add(rproc); if (ret) @@ -621,9 +576,10 @@ static int wcnss_remove(struct platform_device *pdev) of_platform_depopulate(&pdev->dev); - of_node_put(wcnss->smd_node); qcom_smem_state_put(wcnss->state); rproc_del(wcnss->rproc); + + qcom_remove_smd_subdev(wcnss->rproc, &wcnss->smd_subdev); rproc_free(wcnss->rproc); return 0; diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index 90b05c72186c..3dabb20b8d5d 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -961,48 +961,35 @@ clean_up: } /* - * take a firmware and look for virtio devices to register. + * take a firmware and boot it up. * * Note: this function is called asynchronously upon registration of the * remote processor (so we must wait until it completes before we try * to unregister the device. one other option is just to use kref here, * that might be cleaner). */ -static void rproc_fw_config_virtio(const struct firmware *fw, void *context) +static void rproc_auto_boot_callback(const struct firmware *fw, void *context) { struct rproc *rproc = context; - /* if rproc is marked always-on, request it to boot */ - if (rproc->auto_boot) - rproc_boot(rproc); + rproc_boot(rproc); release_firmware(fw); - /* allow rproc_del() contexts, if any, to proceed */ - complete_all(&rproc->firmware_loading_complete); } -static int rproc_add_virtio_devices(struct rproc *rproc) +static int rproc_trigger_auto_boot(struct rproc *rproc) { int ret; - /* rproc_del() calls must wait until async loader completes */ - init_completion(&rproc->firmware_loading_complete); - /* - * We must retrieve early virtio configuration info from - * the firmware (e.g. whether to register a virtio device, - * what virtio features does it support, ...). - * * We're initiating an asynchronous firmware loading, so we can * be built-in kernel code, without hanging the boot process. */ ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, rproc->firmware, &rproc->dev, GFP_KERNEL, - rproc, rproc_fw_config_virtio); - if (ret < 0) { + rproc, rproc_auto_boot_callback); + if (ret < 0) dev_err(&rproc->dev, "request_firmware_nowait err: %d\n", ret); - complete_all(&rproc->firmware_loading_complete); - } return ret; } @@ -1099,6 +1086,12 @@ static int __rproc_boot(struct rproc *rproc) return ret; } + if (rproc->state == RPROC_DELETED) { + ret = -ENODEV; + dev_err(dev, "can't boot deleted rproc %s\n", rproc->name); + goto unlock_mutex; + } + /* skip the boot process if rproc is already powered up */ if (atomic_inc_return(&rproc->power) > 1) { ret = 0; @@ -1287,9 +1280,13 @@ int rproc_add(struct rproc *rproc) /* create debugfs entries */ rproc_create_debug_dir(rproc); - ret = rproc_add_virtio_devices(rproc); - if (ret < 0) - return ret; + + /* if rproc is marked always-on, request it to boot */ + if (rproc->auto_boot) { + ret = rproc_trigger_auto_boot(rproc); + if (ret < 0) + return ret; + } /* expose to rproc_get_by_phandle users */ mutex_lock(&rproc_list_mutex); @@ -1315,8 +1312,6 @@ static void rproc_type_release(struct device *dev) dev_info(&rproc->dev, "releasing %s\n", rproc->name); - rproc_delete_debug_dir(rproc); - idr_destroy(&rproc->notifyids); if (rproc->index >= 0) @@ -1483,14 +1478,17 @@ int rproc_del(struct rproc *rproc) if (!rproc) return -EINVAL; - /* if rproc is just being registered, wait */ - wait_for_completion(&rproc->firmware_loading_complete); - /* if rproc is marked always-on, rproc_add() booted it */ /* TODO: make sure this works with rproc->power > 1 */ if (rproc->auto_boot) rproc_shutdown(rproc); + mutex_lock(&rproc->lock); + rproc->state = RPROC_DELETED; + mutex_unlock(&rproc->lock); + + rproc_delete_debug_dir(rproc); + /* the rproc is downref'ed as soon as it's removed from the klist */ mutex_lock(&rproc_list_mutex); list_del(&rproc->node); diff --git a/drivers/remoteproc/remoteproc_sysfs.c b/drivers/remoteproc/remoteproc_sysfs.c index bc5b0e00efb1..47be411400e5 100644 --- a/drivers/remoteproc/remoteproc_sysfs.c +++ b/drivers/remoteproc/remoteproc_sysfs.c @@ -73,6 +73,7 @@ static const char * const rproc_state_string[] = { [RPROC_SUSPENDED] = "suspended", [RPROC_RUNNING] = "running", [RPROC_CRASHED] = "crashed", + [RPROC_DELETED] = "deleted", [RPROC_LAST] = "invalid", }; diff --git a/drivers/remoteproc/st_remoteproc.c b/drivers/remoteproc/st_remoteproc.c index da4e152e9733..d534bf23dc56 100644 --- a/drivers/remoteproc/st_remoteproc.c +++ b/drivers/remoteproc/st_remoteproc.c @@ -15,6 +15,7 @@ #include <linux/err.h> #include <linux/interrupt.h> #include <linux/kernel.h> +#include <linux/mailbox_client.h> #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/of.h> @@ -25,6 +26,16 @@ #include <linux/remoteproc.h> #include <linux/reset.h> +#include "remoteproc_internal.h" + +#define ST_RPROC_VQ0 0 +#define ST_RPROC_VQ1 1 +#define ST_RPROC_MAX_VRING 2 + +#define MBOX_RX 0 +#define MBOX_TX 1 +#define MBOX_MAX 2 + struct st_rproc_config { bool sw_reset; bool pwr_reset; @@ -39,8 +50,47 @@ struct st_rproc { u32 clk_rate; struct regmap *boot_base; u32 boot_offset; + struct mbox_chan *mbox_chan[ST_RPROC_MAX_VRING * MBOX_MAX]; + struct mbox_client mbox_client_vq0; + struct mbox_client mbox_client_vq1; }; +static void st_rproc_mbox_callback(struct device *dev, u32 msg) +{ + struct rproc *rproc = dev_get_drvdata(dev); + + if (rproc_vq_interrupt(rproc, msg) == IRQ_NONE) + dev_dbg(dev, "no message was found in vqid %d\n", msg); +} + +static +void st_rproc_mbox_callback_vq0(struct mbox_client *mbox_client, void *data) +{ + st_rproc_mbox_callback(mbox_client->dev, 0); +} + +static +void st_rproc_mbox_callback_vq1(struct mbox_client *mbox_client, void *data) +{ + st_rproc_mbox_callback(mbox_client->dev, 1); +} + +static void st_rproc_kick(struct rproc *rproc, int vqid) +{ + struct st_rproc *ddata = rproc->priv; + struct device *dev = rproc->dev.parent; + int ret; + + /* send the index of the triggered virtqueue in the mailbox payload */ + if (WARN_ON(vqid >= ST_RPROC_MAX_VRING)) + return; + + ret = mbox_send_message(ddata->mbox_chan[vqid * MBOX_MAX + MBOX_TX], + (void *)&vqid); + if (ret < 0) + dev_err(dev, "failed to send message via mbox: %d\n", ret); +} + static int st_rproc_start(struct rproc *rproc) { struct st_rproc *ddata = rproc->priv; @@ -107,7 +157,8 @@ static int st_rproc_stop(struct rproc *rproc) return sw_err ?: pwr_err; } -static struct rproc_ops st_rproc_ops = { +static const struct rproc_ops st_rproc_ops = { + .kick = st_rproc_kick, .start = st_rproc_start, .stop = st_rproc_stop, }; @@ -221,8 +272,9 @@ static int st_rproc_probe(struct platform_device *pdev) struct st_rproc *ddata; struct device_node *np = dev->of_node; struct rproc *rproc; + struct mbox_chan *chan; int enabled; - int ret; + int ret, i; match = of_match_device(st_rproc_match, dev); if (!match || !match->data) { @@ -247,7 +299,7 @@ static int st_rproc_probe(struct platform_device *pdev) enabled = st_rproc_state(pdev); if (enabled < 0) { ret = enabled; - goto free_rproc; + goto free_clk; } if (enabled) { @@ -257,12 +309,67 @@ static int st_rproc_probe(struct platform_device *pdev) clk_set_rate(ddata->clk, ddata->clk_rate); } + if (of_get_property(np, "mbox-names", NULL)) { + ddata->mbox_client_vq0.dev = dev; + ddata->mbox_client_vq0.tx_done = NULL; + ddata->mbox_client_vq0.tx_block = false; + ddata->mbox_client_vq0.knows_txdone = false; + ddata->mbox_client_vq0.rx_callback = st_rproc_mbox_callback_vq0; + + ddata->mbox_client_vq1.dev = dev; + ddata->mbox_client_vq1.tx_done = NULL; + ddata->mbox_client_vq1.tx_block = false; + ddata->mbox_client_vq1.knows_txdone = false; + ddata->mbox_client_vq1.rx_callback = st_rproc_mbox_callback_vq1; + + /* + * To control a co-processor without IPC mechanism. + * This driver can be used without mbox and rpmsg. + */ + chan = mbox_request_channel_byname(&ddata->mbox_client_vq0, "vq0_rx"); + if (IS_ERR(chan)) { + dev_err(&rproc->dev, "failed to request mbox chan 0\n"); + ret = PTR_ERR(chan); + goto free_clk; + } + ddata->mbox_chan[ST_RPROC_VQ0 * MBOX_MAX + MBOX_RX] = chan; + + chan = mbox_request_channel_byname(&ddata->mbox_client_vq0, "vq0_tx"); + if (IS_ERR(chan)) { + dev_err(&rproc->dev, "failed to request mbox chan 0\n"); + ret = PTR_ERR(chan); + goto free_mbox; + } + ddata->mbox_chan[ST_RPROC_VQ0 * MBOX_MAX + MBOX_TX] = chan; + + chan = mbox_request_channel_byname(&ddata->mbox_client_vq1, "vq1_rx"); + if (IS_ERR(chan)) { + dev_err(&rproc->dev, "failed to request mbox chan 1\n"); + ret = PTR_ERR(chan); + goto free_mbox; + } + ddata->mbox_chan[ST_RPROC_VQ1 * MBOX_MAX + MBOX_RX] = chan; + + chan = mbox_request_channel_byname(&ddata->mbox_client_vq1, "vq1_tx"); + if (IS_ERR(chan)) { + dev_err(&rproc->dev, "failed to request mbox chan 1\n"); + ret = PTR_ERR(chan); + goto free_mbox; + } + ddata->mbox_chan[ST_RPROC_VQ1 * MBOX_MAX + MBOX_TX] = chan; + } + ret = rproc_add(rproc); if (ret) - goto free_rproc; + goto free_mbox; return 0; +free_mbox: + for (i = 0; i < ST_RPROC_MAX_VRING * MBOX_MAX; i++) + mbox_free_channel(ddata->mbox_chan[i]); +free_clk: + clk_unprepare(ddata->clk); free_rproc: rproc_free(rproc); return ret; @@ -272,6 +379,7 @@ static int st_rproc_remove(struct platform_device *pdev) { struct rproc *rproc = platform_get_drvdata(pdev); struct st_rproc *ddata = rproc->priv; + int i; rproc_del(rproc); @@ -279,6 +387,9 @@ static int st_rproc_remove(struct platform_device *pdev) of_reserved_mem_device_release(&pdev->dev); + for (i = 0; i < ST_RPROC_MAX_VRING * MBOX_MAX; i++) + mbox_free_channel(ddata->mbox_chan[i]); + rproc_free(rproc); return 0; diff --git a/drivers/remoteproc/st_slim_rproc.c b/drivers/remoteproc/st_slim_rproc.c index 507716c8721f..6cfd862f945b 100644 --- a/drivers/remoteproc/st_slim_rproc.c +++ b/drivers/remoteproc/st_slim_rproc.c @@ -200,7 +200,7 @@ static void *slim_rproc_da_to_va(struct rproc *rproc, u64 da, int len) return va; } -static struct rproc_ops slim_rproc_ops = { +static const struct rproc_ops slim_rproc_ops = { .start = slim_rproc_start, .stop = slim_rproc_stop, .da_to_va = slim_rproc_da_to_va, diff --git a/drivers/remoteproc/wkup_m3_rproc.c b/drivers/remoteproc/wkup_m3_rproc.c index 18175d0331fd..1ada0e51fef6 100644 --- a/drivers/remoteproc/wkup_m3_rproc.c +++ b/drivers/remoteproc/wkup_m3_rproc.c @@ -111,7 +111,7 @@ static void *wkup_m3_rproc_da_to_va(struct rproc *rproc, u64 da, int len) return va; } -static struct rproc_ops wkup_m3_rproc_ops = { +static const struct rproc_ops wkup_m3_rproc_ops = { .start = wkup_m3_rproc_start, .stop = wkup_m3_rproc_stop, .da_to_va = wkup_m3_rproc_da_to_va, diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 461b387d03cc..78b1bb7bcf20 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -10,6 +10,10 @@ config QCOM_GSBI functions for connecting the underlying serial UART, SPI, and I2C devices to the output pins. +config QCOM_MDT_LOADER + tristate + select QCOM_SCM + config QCOM_PM bool "Qualcomm Power Management" depends on ARCH_QCOM && !ARM64 diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index fdd664edf0bd..1f30260b06b8 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o +obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o obj-$(CONFIG_QCOM_PM) += spm.o obj-$(CONFIG_QCOM_SMD) += smd.o obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o diff --git a/drivers/soc/qcom/mdt_loader.c b/drivers/soc/qcom/mdt_loader.c new file mode 100644 index 000000000000..bd63df0d14e0 --- /dev/null +++ b/drivers/soc/qcom/mdt_loader.c @@ -0,0 +1,204 @@ +/* + * Qualcomm Peripheral Image Loader + * + * Copyright (C) 2016 Linaro Ltd + * Copyright (C) 2015 Sony Mobile Communications Inc + * 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 <linux/device.h> +#include <linux/elf.h> +#include <linux/firmware.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/qcom_scm.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/soc/qcom/mdt_loader.h> + +static bool mdt_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; +} + +/** + * qcom_mdt_get_size() - acquire size of the memory region needed to load mdt + * @fw: firmware object for the mdt file + * + * Returns size of the loaded firmware blob, or -EINVAL on failure. + */ +ssize_t qcom_mdt_get_size(const struct firmware *fw) +{ + const struct elf32_phdr *phdrs; + const struct elf32_phdr *phdr; + const struct elf32_hdr *ehdr; + phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX; + phys_addr_t max_addr = 0; + int i; + + ehdr = (struct elf32_hdr *)fw->data; + phdrs = (struct elf32_phdr *)(ehdr + 1); + + for (i = 0; i < ehdr->e_phnum; i++) { + phdr = &phdrs[i]; + + if (!mdt_phdr_valid(phdr)) + continue; + + 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); + } + + return min_addr < max_addr ? max_addr - min_addr : -EINVAL; +} +EXPORT_SYMBOL_GPL(qcom_mdt_get_size); + +/** + * qcom_mdt_load() - load the firmware which header is loaded as fw + * @dev: device handle to associate resources with + * @fw: firmware object for the mdt file + * @firmware: name of the firmware, for construction of segment file names + * @pas_id: PAS identifier + * @mem_region: allocated memory region to load firmware into + * @mem_phys: physical address of allocated memory region + * @mem_size: size of the allocated memory region + * + * Returns 0 on success, negative errno otherwise. + */ +int qcom_mdt_load(struct device *dev, const struct firmware *fw, + const char *firmware, int pas_id, void *mem_region, + phys_addr_t mem_phys, size_t mem_size) +{ + const struct elf32_phdr *phdrs; + const struct elf32_phdr *phdr; + const struct elf32_hdr *ehdr; + const struct firmware *seg_fw; + phys_addr_t mem_reloc; + phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX; + phys_addr_t max_addr = 0; + size_t fw_name_len; + ssize_t offset; + char *fw_name; + bool relocate = false; + void *ptr; + int ret; + int i; + + if (!fw || !mem_region || !mem_phys || !mem_size) + return -EINVAL; + + ehdr = (struct elf32_hdr *)fw->data; + phdrs = (struct elf32_phdr *)(ehdr + 1); + + fw_name_len = strlen(firmware); + if (fw_name_len <= 4) + return -EINVAL; + + fw_name = kstrdup(firmware, GFP_KERNEL); + if (!fw_name) + return -ENOMEM; + + ret = qcom_scm_pas_init_image(pas_id, fw->data, fw->size); + if (ret) { + dev_err(dev, "invalid firmware metadata\n"); + goto out; + } + + for (i = 0; i < ehdr->e_phnum; i++) { + phdr = &phdrs[i]; + + if (!mdt_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); + } + + if (relocate) { + ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, max_addr - min_addr); + if (ret) { + dev_err(dev, "unable to setup relocation\n"); + goto out; + } + + /* + * The image is relocatable, so offset each segment based on + * the lowest segment address. + */ + mem_reloc = min_addr; + } else { + /* + * Image is not relocatable, so offset each segment based on + * the allocated physical chunk of memory. + */ + mem_reloc = mem_phys; + } + + for (i = 0; i < ehdr->e_phnum; i++) { + phdr = &phdrs[i]; + + if (!mdt_phdr_valid(phdr)) + continue; + + offset = phdr->p_paddr - mem_reloc; + if (offset < 0 || offset + phdr->p_memsz > mem_size) { + dev_err(dev, "segment outside memory range\n"); + ret = -EINVAL; + break; + } + + ptr = mem_region + offset; + + if (phdr->p_filesz) { + sprintf(fw_name + fw_name_len - 3, "b%02d", i); + ret = request_firmware(&seg_fw, fw_name, dev); + if (ret) { + dev_err(dev, "failed to load %s\n", fw_name); + break; + } + + 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); + } + +out: + kfree(fw_name); + + return ret; +} +EXPORT_SYMBOL_GPL(qcom_mdt_load); + +MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format"); +MODULE_LICENSE("GPL v2"); |