summaryrefslogtreecommitdiff
path: root/drivers/remoteproc/qcom_tz_pil.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/remoteproc/qcom_tz_pil.c')
-rw-r--r--drivers/remoteproc/qcom_tz_pil.c719
1 files changed, 719 insertions, 0 deletions
diff --git a/drivers/remoteproc/qcom_tz_pil.c b/drivers/remoteproc/qcom_tz_pil.c
new file mode 100644
index 000000000000..2f43b00fee13
--- /dev/null
+++ b/drivers/remoteproc/qcom_tz_pil.c
@@ -0,0 +1,719 @@
+/*
+ * Qualcomm Peripheral Image Loader
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/remoteproc.h>
+#include <linux/interrupt.h>
+#include <linux/memblock.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/elf.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+#include <linux/qcom_scm.h>
+#include <linux/soc/qcom/smem.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+#include <linux/soc/qcom/smd.h>
+
+#include "remoteproc_internal.h"
+
+#define PAS_INIT_IMAGE_CMD 1
+#define PAS_MEM_SETUP_CMD 2
+#define PAS_AUTH_AND_RESET_CMD 5
+#define PAS_SHUTDOWN_CMD 6
+#define PAS_IS_SUPPORTED_CMD 7
+
+struct qproc {
+ struct device *dev;
+ struct rproc *rproc;
+
+ int pas_id;
+
+ int wdog_irq;
+ int fatal_irq;
+ int ready_irq;
+ int handover_irq;
+ int stop_ack_irq;
+
+ struct gpio_desc *stop_gpio;
+
+ const char *name;
+ struct regulator *pll;
+
+ unsigned proxy_clk_count;
+ struct clk *scm_core_clk;
+ struct clk *scm_iface_clk;
+ struct clk *scm_bus_clk;
+ struct clk *scm_src_clk;
+
+ struct clk **proxy_clks;
+
+ struct completion start_done;
+ struct completion stop_done;
+
+ unsigned crash_reason;
+ struct device_node *smd_edge_node;
+
+ phys_addr_t reloc_phys;
+ size_t reloc_size;
+};
+
+static int qproc_scm_clk_enable(struct qproc *qproc)
+{
+ int ret;
+
+ ret = clk_prepare_enable(qproc->scm_core_clk);
+ if (ret)
+ goto bail;
+ ret = clk_prepare_enable(qproc->scm_iface_clk);
+ if (ret)
+ goto disable_core;
+ ret = clk_prepare_enable(qproc->scm_bus_clk);
+ if (ret)
+ goto disable_iface;
+
+ ret = clk_prepare_enable(qproc->scm_src_clk);
+ if (ret)
+ goto disable_bus;
+
+ return 0;
+
+disable_bus:
+ clk_disable_unprepare(qproc->scm_bus_clk);
+disable_iface:
+ clk_disable_unprepare(qproc->scm_iface_clk);
+disable_core:
+ clk_disable_unprepare(qproc->scm_core_clk);
+bail:
+ return ret;
+}
+
+static void qproc_scm_clk_disable(struct qproc *qproc)
+{
+ clk_disable_unprepare(qproc->scm_core_clk);
+ clk_disable_unprepare(qproc->scm_iface_clk);
+ clk_disable_unprepare(qproc->scm_bus_clk);
+ clk_disable_unprepare(qproc->scm_src_clk);
+}
+
+/**
+ * struct pil_mdt - Representation of <name>.mdt file in memory
+ * @hdr: ELF32 header
+ * @phdr: ELF32 program headers
+ */
+struct mdt_hdr {
+ struct elf32_hdr hdr;
+ struct elf32_phdr phdr[];
+};
+
+#define segment_is_hash(flag) (((flag) & (0x7 << 24)) == (0x2 << 24))
+
+static int segment_is_loadable(const struct elf32_phdr *p)
+{
+ return (p->p_type == PT_LOAD) &&
+ !segment_is_hash(p->p_flags) &&
+ p->p_memsz;
+}
+
+static bool segment_is_relocatable(const struct elf32_phdr *p)
+{
+ return !!(p->p_flags & BIT(27));
+}
+
+/**
+ * rproc_mdt_sanity_check() - sanity check mdt firmware header
+ * @rproc: the remote processor handle
+ * @fw: the mdt header firmware image
+ */
+static int qproc_sanity_check(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ struct elf32_hdr *ehdr;
+ struct mdt_hdr *mdt;
+
+ if (!fw) {
+ dev_err(&rproc->dev, "failed to load %s\n", rproc->name);
+ return -EINVAL;
+ }
+
+ if (fw->size < sizeof(struct elf32_hdr)) {
+ dev_err(&rproc->dev, "image is too small\n");
+ return -EINVAL;
+ }
+
+ mdt = (struct mdt_hdr *)fw->data;
+ ehdr = &mdt->hdr;
+
+ if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
+ dev_err(&rproc->dev, "image is corrupted (bad magic)\n");
+ return -EINVAL;
+ }
+
+ if (ehdr->e_phnum == 0) {
+ dev_err(&rproc->dev, "no loadable segments\n");
+ return -EINVAL;
+ }
+
+ if (sizeof(struct elf32_phdr) * ehdr->e_phnum +
+ sizeof(struct elf32_hdr) > fw->size) {
+ dev_err(&rproc->dev, "firmware size is too small\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct resource_table * qproc_find_rsc_table(struct rproc *rproc,
+ const struct firmware *fw,
+ int *tablesz)
+{
+ static struct resource_table table = { .ver = 1, };
+
+ *tablesz = sizeof(table);
+ return &table;
+}
+
+static int qproc_load_segment(struct rproc *rproc, const char *fw_name,
+ const struct elf32_phdr *phdr, phys_addr_t paddr)
+{
+ const struct firmware *fw;
+ void *ptr;
+ int ret = 0;
+
+ ptr = ioremap_nocache(paddr, phdr->p_memsz);
+ if (!ptr) {
+ dev_err(&rproc->dev, "failed to ioremap segment area (%pa+0x%x)\n", &paddr, phdr->p_memsz);
+ return -EBUSY;
+ }
+
+ if (phdr->p_filesz) {
+ ret = request_firmware(&fw, fw_name, &rproc->dev);
+ if (ret) {
+ dev_err(&rproc->dev, "failed to load %s\n", fw_name);
+ goto out;
+ }
+
+ memcpy_toio(ptr, fw->data, fw->size);
+
+ release_firmware(fw);
+ }
+
+ if (phdr->p_memsz > phdr->p_filesz)
+ memset_io(ptr + phdr->p_filesz, 0,
+ phdr->p_memsz - phdr->p_filesz);
+
+out:
+ iounmap(ptr);
+ return ret;
+}
+
+static int qproc_load(struct rproc *rproc, const struct firmware *fw)
+{
+ const struct elf32_phdr *phdr;
+ const struct elf32_hdr *ehdr;
+ const struct mdt_hdr *mdt;
+ phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX;
+ phys_addr_t max_addr = 0;
+ phys_addr_t diff_addr;
+ struct qproc *qproc = rproc->priv;
+ char *fw_name;
+ int ret;
+ int i;
+ size_t align = 0;
+ bool relocatable = false;
+ phys_addr_t paddr;
+
+ ret = qproc_scm_clk_enable(qproc);
+ if (ret)
+ return ret;
+
+ mdt = (struct mdt_hdr *)fw->data;
+ ehdr = &mdt->hdr;
+
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ phdr = &mdt->phdr[i];
+
+ if (!segment_is_loadable(phdr))
+ continue;
+
+ if (phdr->p_paddr < min_addr) {
+ min_addr = phdr->p_paddr;
+
+ if (segment_is_relocatable(phdr)) {
+ align = phdr->p_align;
+ relocatable = true;
+ }
+ }
+
+ if (phdr->p_paddr + phdr->p_memsz > max_addr)
+ max_addr = round_up(phdr->p_paddr + phdr->p_memsz, SZ_4K);
+ }
+
+ ret = qcom_scm_pas_init_image(qproc->dev,
+ qproc->pas_id, fw->data, fw->size);
+ if (ret) {
+ dev_err(qproc->dev, "Invalid firmware metadata\n");
+ return -EINVAL;
+ }
+
+ diff_addr = max_addr - min_addr;
+ dev_dbg(qproc->dev, "pas_mem_setup %pa, %pa\n", &min_addr, &diff_addr);
+
+ ret = qcom_scm_pas_mem_setup(qproc->pas_id,
+ relocatable ? qproc->reloc_phys : min_addr, max_addr - min_addr);
+ if (ret) {
+ dev_err(qproc->dev, "unable to setup memory for image\n");
+ return -EINVAL;
+ }
+
+ fw_name = kzalloc(strlen(qproc->name) + 5, GFP_KERNEL);
+ if (!fw_name)
+ return -ENOMEM;
+
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ phdr = &mdt->phdr[i];
+
+ if (!segment_is_loadable(phdr))
+ continue;
+
+ paddr = relocatable ?
+ (phdr->p_paddr - min_addr + qproc->reloc_phys) :
+ phdr->p_paddr;
+ sprintf(fw_name, "%s.b%02d", qproc->name, i);
+ ret = qproc_load_segment(rproc, fw_name, phdr, paddr);
+ if (ret)
+ break;
+ }
+
+ kfree(fw_name);
+
+ qproc_scm_clk_disable(qproc);
+
+ return 0;
+}
+
+const struct rproc_fw_ops qproc_fw_ops = {
+ .find_rsc_table = qproc_find_rsc_table,
+ .load = qproc_load,
+ .sanity_check = qproc_sanity_check,
+};
+
+static int qproc_start(struct rproc *rproc)
+{
+ struct qproc *qproc = (struct qproc *)rproc->priv;
+ int ret;
+
+ ret = regulator_enable(qproc->pll);
+ if (ret) {
+ dev_err(qproc->dev, "failed to enable pll supply\n");
+ return ret;
+ }
+
+ ret = qproc_scm_clk_enable(qproc);
+ if (ret)
+ goto disable_regulator;
+
+ ret = qcom_scm_pas_auth_and_reset(qproc->pas_id);
+ if (ret) {
+ dev_err(qproc->dev,
+ "failed to authenticate image and release reset\n");
+ goto unroll_clocks;
+ }
+
+ /* if ready irq not provided skip waiting */
+ if (qproc->ready_irq < 0)
+ goto done;
+
+ ret = wait_for_completion_timeout(&qproc->start_done, msecs_to_jiffies(10000));
+ if (ret == 0) {
+ dev_err(qproc->dev, "start timed out\n");
+
+ qcom_scm_pas_shutdown(qproc->pas_id);
+ goto unroll_clocks;
+ }
+
+done:
+ dev_info(qproc->dev, "start successful\n");
+
+ return 0;
+
+unroll_clocks:
+ qproc_scm_clk_disable(qproc);
+
+disable_regulator:
+ regulator_disable(qproc->pll);
+
+ return ret;
+}
+
+static int qproc_stop(struct rproc *rproc)
+{
+ return 0;
+}
+
+static const struct rproc_ops qproc_ops = {
+ .start = qproc_start,
+ .stop = qproc_stop,
+};
+
+static irqreturn_t qproc_wdog_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ rproc_report_crash(qproc->rproc, RPROC_WATCHDOG);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qproc_fatal_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+ size_t len;
+ char *msg;
+ int ret;
+
+ ret = qcom_smem_get(-1, qproc->crash_reason, (void**)&msg, &len);
+ if (!ret && len > 0 && msg[0])
+ dev_err(qproc->dev, "fatal error received: %s\n", msg);
+
+ rproc_report_crash(qproc->rproc, RPROC_FATAL_ERROR);
+
+ if (!ret)
+ msg[0] = '\0';
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qproc_ready_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ complete(&qproc->start_done);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qproc_handover_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ qproc_scm_clk_disable(qproc);
+ regulator_disable(qproc->pll);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qproc_stop_ack_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ complete(&qproc->stop_done);
+ return IRQ_HANDLED;
+}
+
+static ssize_t qproc_boot_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct qproc *qproc = dev_get_drvdata(dev);
+ int ret;
+
+ ret = rproc_boot(qproc->rproc);
+ return ret ? : size;
+}
+
+static ssize_t qproc_shutdown_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct qproc *qproc = dev_get_drvdata(dev);
+
+ rproc_shutdown(qproc->rproc);
+ return size;
+}
+
+static const struct device_attribute qproc_attrs[] = {
+ __ATTR(boot, S_IWUSR, 0, qproc_boot_store),
+ __ATTR(shutdown, S_IWUSR, 0, qproc_shutdown_store),
+};
+
+static int qproc_init_pas(struct qproc *qproc)
+{
+ char *key;
+ int ret;
+
+ key = "qcom,pas-id";
+ ret = of_property_read_u32(qproc->dev->of_node, key, &qproc->pas_id);
+ if (ret) {
+ dev_err(qproc->dev, "Missing or incorrect %s\n", key);
+ return -EINVAL;
+ }
+
+ if (!qcom_scm_pas_supported(qproc->pas_id)) {
+ dev_err(qproc->dev, "PAS is not available for %d\n", qproc->pas_id);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int qproc_init_clocks(struct qproc *qproc)
+{
+ long rate;
+ int ret;
+
+ qproc->scm_core_clk = devm_clk_get(qproc->dev, "scm_core_clk");
+ if (IS_ERR(qproc->scm_core_clk)) {
+ if (PTR_ERR(qproc->scm_core_clk) != -EPROBE_DEFER)
+ dev_err(qproc->dev, "failed to acquire scm_core_clk\n");
+ return PTR_ERR(qproc->scm_core_clk);
+ }
+
+ qproc->scm_iface_clk = devm_clk_get(qproc->dev, "scm_iface_clk");
+ if (IS_ERR(qproc->scm_iface_clk)) {
+ if (PTR_ERR(qproc->scm_iface_clk) != -EPROBE_DEFER)
+ dev_err(qproc->dev, "failed to acquire scm_iface_clk\n");
+ return PTR_ERR(qproc->scm_iface_clk);
+ }
+
+ qproc->scm_bus_clk = devm_clk_get(qproc->dev, "scm_bus_clk");
+ if (IS_ERR(qproc->scm_bus_clk)) {
+ if (PTR_ERR(qproc->scm_bus_clk) != -EPROBE_DEFER)
+ dev_err(qproc->dev, "failed to acquire scm_bus_clk\n");
+ return PTR_ERR(qproc->scm_bus_clk);
+ }
+
+ qproc->scm_src_clk = devm_clk_get(qproc->dev, "scm_src_clk");
+ if (IS_ERR(qproc->scm_src_clk)) {
+ if (PTR_ERR(qproc->scm_src_clk) != -EPROBE_DEFER)
+ dev_err(qproc->dev, "failed to acquire scm_src_clk\n");
+ return PTR_ERR(qproc->scm_src_clk);
+ }
+
+ ret = clk_set_rate(qproc->scm_core_clk,
+clk_round_rate(qproc->scm_core_clk, 19200000));
+ ret = clk_set_rate(qproc->scm_bus_clk,
+clk_round_rate(qproc->scm_bus_clk, 19200000));
+ ret = clk_set_rate(qproc->scm_iface_clk,
+clk_round_rate(qproc->scm_iface_clk, 19200000));
+ rate = clk_round_rate(qproc->scm_core_clk, 80000000);
+ ret = clk_set_rate(qproc->scm_src_clk, rate);
+ if (ret) {
+ dev_err(qproc->dev, "failed to set rate of scm_core_clk\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int qproc_init_regulators(struct qproc *qproc)
+{
+ int ret;
+ u32 uA;
+ u32 uV;
+
+ qproc->pll = devm_regulator_get(qproc->dev, "qcom,pll");
+ if (IS_ERR(qproc->pll)) {
+ if (PTR_ERR(qproc->pll) != -EPROBE_DEFER)
+ dev_err(qproc->dev, "failed to aquire regulator\n");
+ return PTR_ERR(qproc->pll);
+ }
+
+ ret = of_property_read_u32(qproc->dev->of_node, "qcom,pll-uV", &uV);
+ if (ret)
+ dev_warn(qproc->dev, "failed to read qcom,pll_uV, skipping\n");
+ else
+ regulator_set_voltage(qproc->pll, uV, uV);
+
+ ret = of_property_read_u32(qproc->dev->of_node, "qcom,pll-uA", &uA);
+ if (ret)
+ dev_warn(qproc->dev, "failed to read qcom,pll_uA, skipping\n");
+ else
+ regulator_set_load(qproc->pll, uA);
+
+ return 0;
+}
+
+static int qproc_request_irq(struct qproc *qproc, struct platform_device *pdev, const char *name, irq_handler_t thread_fn)
+{
+ int ret;
+
+ ret = platform_get_irq_byname(pdev, name);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "no %s IRQ defined\n", name);
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, ret,
+ NULL, thread_fn,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "qproc", qproc);
+ if (ret)
+ dev_err(&pdev->dev, "request %s IRQ failed\n", name);
+ return ret;
+}
+
+static int qproc_probe(struct platform_device *pdev)
+{
+ struct qproc *qproc;
+ struct rproc *rproc;
+ char *fw_name;
+ const char *name;
+ const char *key;
+ int ret;
+ int i;
+ struct device_node *np;
+ struct resource r;
+
+
+ key = "qcom,firmware-name";
+ ret = of_property_read_string(pdev->dev.of_node, key, &name);
+ if (ret) {
+ dev_err(&pdev->dev, "missing or incorrect %s\n", key);
+ return -EINVAL;
+ }
+
+ fw_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s.mdt", name);
+ if (!fw_name)
+ return -ENOMEM;
+
+ rproc = rproc_alloc(&pdev->dev, pdev->name, &qproc_ops,
+ fw_name, sizeof(*qproc));
+ if (!rproc) {
+ dev_err(&pdev->dev, "unable to allocate remoteproc\n");
+ return -ENOMEM;
+ }
+
+ rproc->fw_ops = &qproc_fw_ops;
+
+ qproc = (struct qproc *)rproc->priv;
+ qproc->dev = &pdev->dev;
+ qproc->rproc = rproc;
+ qproc->name = name;
+ platform_set_drvdata(pdev, qproc);
+
+ init_completion(&qproc->start_done);
+ init_completion(&qproc->stop_done);
+
+ ret = of_property_read_u32(pdev->dev.of_node, "qcom,crash-reason",
+ &qproc->crash_reason);
+ if (ret)
+ dev_info(&pdev->dev, "no crash reason id\n");
+
+ qproc->smd_edge_node = of_parse_phandle(pdev->dev.of_node,
+ "qcom,smd-edges", 0);
+
+ ret = qproc_init_pas(qproc);
+ if (ret)
+ goto free_rproc;
+
+ ret = qproc_init_clocks(qproc);
+ if (ret)
+ goto free_rproc;
+
+ ret = qproc_init_regulators(qproc);
+ if (ret)
+ goto free_rproc;
+
+ ret = qproc_request_irq(qproc, pdev, "wdog", qproc_wdog_interrupt);
+ qproc->wdog_irq = ret;
+
+ ret = qproc_request_irq(qproc, pdev, "fatal", qproc_fatal_interrupt);
+ qproc->fatal_irq = ret;
+
+ ret = qproc_request_irq(qproc, pdev, "ready", qproc_ready_interrupt);
+ qproc->ready_irq = ret;
+
+ ret = qproc_request_irq(qproc, pdev, "handover", qproc_handover_interrupt);
+ qproc->handover_irq = ret;
+
+ ret = qproc_request_irq(qproc, pdev, "stop-ack", qproc_stop_ack_interrupt);
+ qproc->stop_ack_irq = ret;
+
+ for (i = 0; i < ARRAY_SIZE(qproc_attrs); i++) {
+ ret = device_create_file(&pdev->dev, &qproc_attrs[i]);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to create sysfs file\n");
+ goto remove_device_files;
+ }
+ }
+
+ np = of_parse_phandle(pdev->dev.of_node, "memory-region", 0);
+ if (!np) {
+ dev_err(&pdev->dev, "No memory region specified\n");
+ } else {
+
+ ret = of_address_to_resource(np, 0, &r);
+ of_node_put(np);
+ if (ret)
+ return ret;
+
+ qproc->reloc_phys = r.start;
+ qproc->reloc_size = resource_size(&r);
+
+ dev_info(&pdev->dev, "Found relocation area %lu@%pad\n",
+ qproc->reloc_size, &qproc->reloc_phys);
+ }
+
+ ret = rproc_add(rproc);
+ if (ret)
+ goto remove_device_files;
+
+ return 0;
+
+remove_device_files:
+ for (i--; i >= 0; i--)
+ device_remove_file(&pdev->dev, &qproc_attrs[i]);
+
+free_rproc:
+ rproc_put(rproc);
+
+ return ret;
+}
+
+static int qproc_remove(struct platform_device *pdev)
+{
+ struct qproc *qproc = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(qproc_attrs); i++)
+ device_remove_file(&pdev->dev, &qproc_attrs[i]);
+
+ rproc_put(qproc->rproc);
+
+ return 0;
+}
+
+static const struct of_device_id qproc_of_match[] = {
+ { .compatible = "qcom,tz-pil", },
+ { },
+};
+
+static struct platform_driver qproc_driver = {
+ .probe = qproc_probe,
+ .remove = qproc_remove,
+ .driver = {
+ .name = "qcom-tz-pil",
+ .of_match_table = qproc_of_match,
+ },
+};
+
+module_platform_driver(qproc_driver);