aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/remoteproc/omap_remoteproc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/remoteproc/omap_remoteproc.c')
-rw-r--r--drivers/remoteproc/omap_remoteproc.c608
1 files changed, 608 insertions, 0 deletions
diff --git a/drivers/remoteproc/omap_remoteproc.c b/drivers/remoteproc/omap_remoteproc.c
new file mode 100644
index 0000000..1e01a33
--- /dev/null
+++ b/drivers/remoteproc/omap_remoteproc.c
@@ -0,0 +1,608 @@
+/*
+ * Remote processor machine-specific module for OMAP4
+ *
+ * Copyright (C) 2011 Texas Instruments, Inc.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/remoteproc.h>
+#include <linux/sched.h>
+
+#include <plat/iommu.h>
+#include <plat/omap_device.h>
+#include <plat/remoteproc.h>
+#include <plat/mailbox.h>
+#include <plat/common.h>
+#include <plat/omap-pm.h>
+#include <plat/dmtimer.h>
+#include "../../arch/arm/mach-omap2/dvfs.h"
+#include "../../arch/arm/mach-omap2/clockdomain.h"
+
+#define PM_SUSPEND_MBOX 0xffffff07
+#define PM_SUSPEND_TIMEOUT 300
+
+struct omap_rproc_priv {
+ struct iommu *iommu;
+ int (*iommu_cb)(struct rproc *, u64, u32);
+ int (*wdt_cb)(struct rproc *);
+#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND
+ struct omap_mbox *mbox;
+ void __iomem *idle;
+ u32 idle_mask;
+ void __iomem *suspend;
+ u32 suspend_mask;
+#endif
+};
+
+#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND
+static bool _may_suspend(struct omap_rproc_priv *rpp)
+{
+ return readl(rpp->idle) & rpp->idle_mask;
+}
+
+static int _suspend(struct omap_rproc_priv *rpp)
+{
+ unsigned long timeout = msecs_to_jiffies(PM_SUSPEND_TIMEOUT) + jiffies;
+
+ omap_mbox_msg_send(rpp->mbox, PM_SUSPEND_MBOX);
+
+ while (time_after(timeout, jiffies)) {
+ if ((readl(rpp->suspend) & rpp->suspend_mask) &&
+ (readl(rpp->idle) & rpp->idle_mask))
+ return 0;
+ schedule();
+ }
+
+ return -EIO;
+}
+
+static int omap_suspend(struct rproc *rproc, bool force)
+{
+ struct omap_rproc_priv *rpp = rproc->priv;
+
+ if (rpp->idle && (force || _may_suspend(rpp)))
+ return _suspend(rpp);
+
+ return -EBUSY;
+}
+#endif
+
+static void omap_rproc_dump_registers(struct rproc *rproc)
+{
+ unsigned long flags;
+ char buf[64];
+ struct pt_regs regs;
+
+ if (!rproc->cdump_buf1)
+ return;
+
+ remoteproc_fill_pt_regs(&regs,
+ (struct exc_regs *)rproc->cdump_buf1);
+
+ pr_info("REGISTER DUMP FOR REMOTEPROC %s\n", rproc->name);
+ pr_info("PC is at %08lx\n", instruction_pointer(&regs));
+ pr_info("LR is at %08lx\n", regs.ARM_lr);
+ pr_info("pc : [<%08lx>] lr : [<%08lx>] psr: %08lx\n"
+ "sp : %08lx ip : %08lx fp : %08lx\n",
+ regs.ARM_pc, regs.ARM_lr, regs.ARM_cpsr,
+ regs.ARM_sp, regs.ARM_ip, regs.ARM_fp);
+ pr_info("r10: %08lx r9 : %08lx r8 : %08lx\n",
+ regs.ARM_r10, regs.ARM_r9,
+ regs.ARM_r8);
+ pr_info("r7 : %08lx r6 : %08lx r5 : %08lx r4 : %08lx\n",
+ regs.ARM_r7, regs.ARM_r6,
+ regs.ARM_r5, regs.ARM_r4);
+ pr_info("r3 : %08lx r2 : %08lx r1 : %08lx r0 : %08lx\n",
+ regs.ARM_r3, regs.ARM_r2,
+ regs.ARM_r1, regs.ARM_r0);
+
+ flags = regs.ARM_cpsr;
+ buf[0] = flags & PSR_N_BIT ? 'N' : 'n';
+ buf[1] = flags & PSR_Z_BIT ? 'Z' : 'z';
+ buf[2] = flags & PSR_C_BIT ? 'C' : 'c';
+ buf[3] = flags & PSR_V_BIT ? 'V' : 'v';
+ buf[4] = '\0';
+
+ pr_info("Flags: %s IRQs o%s FIQs o%s\n",
+ buf, interrupts_enabled(&regs) ? "n" : "ff",
+ fast_interrupts_enabled(&regs) ? "n" : "ff");
+}
+
+static int
+omap_rproc_map(struct device *dev, struct iommu *obj, u32 da, u32 pa, u32 size)
+{
+ struct iotlb_entry e;
+ u32 all_bits;
+ u32 pg_size[] = {SZ_16M, SZ_1M, SZ_64K, SZ_4K};
+ int size_flag[] = {MMU_CAM_PGSZ_16M, MMU_CAM_PGSZ_1M,
+ MMU_CAM_PGSZ_64K, MMU_CAM_PGSZ_4K};
+ int i, ret;
+
+ while (size) {
+ /*
+ * To find the max. page size with which both PA & VA are
+ * aligned
+ */
+ all_bits = pa | da;
+ for (i = 0; i < 4; i++) {
+ if ((size >= pg_size[i]) &&
+ ((all_bits & (pg_size[i] - 1)) == 0)) {
+ break;
+ }
+ }
+
+ memset(&e, 0, sizeof(e));
+
+ e.da = da;
+ e.pa = pa;
+ e.valid = 1;
+ e.pgsz = size_flag[i];
+ e.endian = MMU_RAM_ENDIAN_LITTLE;
+ e.elsz = MMU_RAM_ELSZ_32;
+
+ ret = iopgtable_store_entry(obj, &e);
+ if (ret) {
+ dev_err(dev, "iopgtable_store_entry fail: %d\n", ret);
+ return ret;
+ }
+
+ size -= pg_size[i];
+ da += pg_size[i];
+ pa += pg_size[i];
+ }
+
+ return 0;
+}
+
+
+static int omap_rproc_iommu_isr(struct iommu *iommu, u32 da, u32 errs, void *p)
+{
+ struct rproc *rproc = p;
+ struct omap_rproc_priv *rpp = rproc->priv;
+ int ret = -EIO;
+
+ if (rpp && rpp->iommu_cb)
+ ret = rpp->iommu_cb(rproc, (u64)da, errs);
+
+ return ret;
+}
+
+int omap_rproc_activate(struct omap_device *od)
+{
+ int i, ret = 0;
+ struct rproc *rproc = platform_get_drvdata(&od->pdev);
+ struct device *dev = rproc->dev;
+ struct omap_rproc_pdata *pdata = dev->platform_data;
+ struct omap_rproc_timers_info *timers = pdata->timers;
+#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND
+ struct omap_rproc_priv *rpp = rproc->priv;
+ struct iommu *iommu;
+
+ if (!rpp->iommu) {
+ iommu = iommu_get(pdata->iommu_name);
+ if (IS_ERR(iommu)) {
+ dev_err(dev, "iommu_get error: %ld\n",
+ PTR_ERR(iommu));
+ return PTR_ERR(iommu);
+ }
+ rpp->iommu = iommu;
+ }
+
+ if (!rpp->mbox)
+ rpp->mbox = omap_mbox_get(pdata->sus_mbox_name, NULL);
+#endif
+
+ /**
+ * Domain is in HW SUP thus in hw_auto but
+ * since remoteproc will be enabled clkdm
+ * needs to be in sw_sup (Do not let it idle).
+ */
+ if (pdata->clkdm)
+ clkdm_wakeup(pdata->clkdm);
+
+ for (i = 0; i < pdata->timers_cnt; i++)
+ omap_dm_timer_start(timers[i].odt);
+
+ for (i = 0; i < od->hwmods_cnt; i++) {
+ ret = omap_hwmod_enable(od->hwmods[i]);
+ if (ret) {
+ for (i = 0; i < pdata->timers_cnt; i++)
+ omap_dm_timer_stop(timers[i].odt);
+ break;
+ }
+ }
+
+ /**
+ * Domain is in force_wkup but since remoteproc
+ * was enabled it is safe now to switch clkdm
+ * to hw_auto (let it idle).
+ */
+ if (pdata->clkdm)
+ clkdm_allow_idle(pdata->clkdm);
+
+ return ret;
+}
+
+int omap_rproc_deactivate(struct omap_device *od)
+{
+ int i, ret = 0;
+ struct rproc *rproc = platform_get_drvdata(&od->pdev);
+ struct device *dev = rproc->dev;
+ struct omap_rproc_pdata *pdata = dev->platform_data;
+ struct omap_rproc_timers_info *timers = pdata->timers;
+#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND
+ struct omap_rproc_priv *rpp = rproc->priv;
+#endif
+ if (pdata->clkdm)
+ clkdm_wakeup(pdata->clkdm);
+
+ for (i = 0; i < od->hwmods_cnt; i++) {
+ ret = omap_hwmod_shutdown(od->hwmods[i]);
+ if (ret)
+ goto err;
+ }
+
+ for (i = 0; i < pdata->timers_cnt; i++)
+ omap_dm_timer_stop(timers[i].odt);
+
+#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND
+ if (rpp->iommu) {
+ iommu_put(rpp->iommu);
+ rpp->iommu = NULL;
+ }
+
+ if (rpp->mbox) {
+ omap_mbox_put(rpp->mbox, NULL);
+ rpp->mbox = NULL;
+ }
+#endif
+err:
+ if (pdata->clkdm)
+ clkdm_allow_idle(pdata->clkdm);
+
+ return ret;
+}
+
+static int omap_rproc_iommu_init(struct rproc *rproc,
+ int (*callback)(struct rproc *rproc, u64 fa, u32 flags))
+{
+ struct device *dev = rproc->dev;
+ struct omap_rproc_pdata *pdata = dev->platform_data;
+ int ret, i;
+ struct iommu *iommu;
+ struct omap_rproc_priv *rpp;
+
+ rpp = kzalloc(sizeof(*rpp), GFP_KERNEL);
+ if (!rpp)
+ return -ENOMEM;
+
+ if (pdata->clkdm)
+ clkdm_wakeup(pdata->clkdm);
+ iommu_set_isr(pdata->iommu_name, omap_rproc_iommu_isr, rproc);
+ iommu_set_secure(pdata->iommu_name, rproc->secure_mode,
+ rproc->secure_ttb);
+ iommu = iommu_get(pdata->iommu_name);
+ if (IS_ERR(iommu)) {
+ ret = PTR_ERR(iommu);
+ dev_err(dev, "iommu_get error: %d\n", ret);
+ goto err_mmu;
+ }
+
+ rpp->iommu = iommu;
+ rpp->iommu_cb = callback;
+ rproc->priv = rpp;
+
+ if (!rproc->secure_mode) {
+ for (i = 0; rproc->memory_maps[i].size; i++) {
+ const struct rproc_mem_entry *me =
+ &rproc->memory_maps[i];
+
+ ret = omap_rproc_map(dev, iommu, me->da, me->pa,
+ me->size);
+ if (ret)
+ goto err_map;
+ }
+ }
+ if (pdata->clkdm)
+ clkdm_allow_idle(pdata->clkdm);
+
+ return 0;
+
+err_map:
+ iommu_put(iommu);
+err_mmu:
+ iommu_set_secure(pdata->iommu_name, false, NULL);
+ if (pdata->clkdm)
+ clkdm_allow_idle(pdata->clkdm);
+ kfree(rpp);
+ return ret;
+}
+
+#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND
+static int _init_pm_flags(struct rproc *rproc)
+{
+ struct omap_rproc_pdata *pdata = rproc->dev->platform_data;
+ struct omap_rproc_priv *rpp = rproc->priv;
+ struct omap_mbox *mbox;
+
+ if (!rpp->mbox) {
+ mbox = omap_mbox_get(pdata->sus_mbox_name, NULL);
+ if (IS_ERR(mbox))
+ return PTR_ERR(mbox);
+ rpp->mbox = mbox;
+ }
+ if (!pdata->idle_addr)
+ goto err_idle;
+
+ rpp->idle = ioremap(pdata->idle_addr, sizeof(u32));
+ if (!rpp->idle)
+ goto err_idle;
+
+ if (!pdata->suspend_addr)
+ goto err_suspend;
+
+ rpp->suspend = ioremap(pdata->suspend_addr, sizeof(u32));
+ if (!rpp->suspend)
+ goto err_suspend;
+
+ rpp->idle_mask = pdata->idle_mask;
+ rpp->suspend_mask = pdata->suspend_mask;
+
+ return 0;
+err_suspend:
+ iounmap(rpp->idle);
+ rpp->idle = NULL;
+err_idle:
+ omap_mbox_put(rpp->mbox, NULL);
+ rpp->mbox = NULL;
+ return -EIO;
+}
+
+static void _destroy_pm_flags(struct rproc *rproc)
+{
+ struct omap_rproc_priv *rpp = rproc->priv;
+
+ if (rpp->mbox) {
+ omap_mbox_put(rpp->mbox, NULL);
+ rpp->mbox = NULL;
+ }
+ if (rpp->idle) {
+ iounmap(rpp->idle);
+ rpp->idle = NULL;
+ }
+ if (rpp->suspend) {
+ iounmap(rpp->suspend);
+ rpp->suspend = NULL;
+ }
+}
+#endif
+#ifdef CONFIG_REMOTEPROC_WATCHDOG
+static int omap_rproc_watchdog_init(struct rproc *rproc,
+ int (*callback)(struct rproc *rproc))
+{
+ struct omap_rproc_priv *rpp = rproc->priv;
+
+ rpp->wdt_cb = callback;
+ return 0;
+}
+
+static int omap_rproc_watchdog_exit(struct rproc *rproc)
+{
+ struct omap_rproc_priv *rpp = rproc->priv;
+
+ rpp->wdt_cb = NULL;
+ return 0;
+}
+
+static irqreturn_t omap_rproc_watchdog_isr(int irq, void *p)
+{
+ struct rproc *rproc = p;
+ struct omap_rproc_pdata *pdata = rproc->dev->platform_data;
+ struct omap_rproc_timers_info *timers = pdata->timers;
+ struct omap_dm_timer *timer = NULL;
+ struct omap_rproc_priv *rpp = rproc->priv;
+ int i;
+
+ for (i = 0; i < pdata->timers_cnt; i++) {
+ if (irq == omap_dm_timer_get_irq(timers[i].odt)) {
+ timer = timers[i].odt;
+ break;
+ }
+ }
+
+ if (!timer)
+ return IRQ_NONE;
+
+ omap_dm_timer_write_status(timer, OMAP_TIMER_INT_OVERFLOW);
+
+ if (rpp->wdt_cb)
+ rpp->wdt_cb(rproc);
+
+ return IRQ_HANDLED;
+}
+#endif
+static inline int omap_rproc_start(struct rproc *rproc, u64 bootaddr)
+{
+ struct device *dev = rproc->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct omap_rproc_pdata *pdata = dev->platform_data;
+ struct omap_rproc_timers_info *timers = pdata->timers;
+ int i;
+ int ret = 0;
+
+ if (rproc->secure_mode) {
+ pr_err("TODO: Call secure service to authenticate\n");
+ if (ret)
+ return -ENXIO;
+ }
+
+#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND
+ _init_pm_flags(rproc);
+#endif
+ for (i = 0; i < pdata->timers_cnt; i++) {
+ timers[i].odt = omap_dm_timer_request_specific(timers[i].id);
+ if (!timers[i].odt) {
+ ret = -EBUSY;
+ goto out;
+ }
+ omap_dm_timer_set_source(timers[i].odt, OMAP_TIMER_SRC_SYS_CLK);
+#ifdef CONFIG_REMOTEPROC_WATCHDOG
+ /* GPT 9 and 11 are using as WDT */
+ if (timers[i].id == 9 || timers[i].id == 11) {
+ ret = request_irq(omap_dm_timer_get_irq(timers[i].odt),
+ omap_rproc_watchdog_isr, IRQF_DISABLED,
+ "rproc-wdt", rproc);
+ /* Clean counter, remoteproc proc will set the value */
+ omap_dm_timer_set_load(timers[i].odt, 0, 0);
+ }
+#endif
+ }
+
+ ret = omap_device_enable(pdev);
+out:
+ if (ret) {
+ while (i--) {
+ omap_dm_timer_free(timers[i].odt);
+ timers[i].odt = NULL;
+ }
+ }
+
+ return ret;
+}
+
+static int omap_rproc_iommu_exit(struct rproc *rproc)
+{
+ struct omap_rproc_priv *rpp = rproc->priv;
+ struct omap_rproc_pdata *pdata = rproc->dev->platform_data;
+
+ if (pdata->clkdm)
+ clkdm_wakeup(pdata->clkdm);
+
+ if (rpp->iommu)
+ iommu_put(rpp->iommu);
+ kfree(rpp);
+ if (pdata->clkdm)
+ clkdm_allow_idle(pdata->clkdm);
+
+ return 0;
+}
+
+static inline int omap_rproc_stop(struct rproc *rproc)
+{
+ struct device *dev = rproc->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct omap_rproc_pdata *pdata = dev->platform_data;
+ struct omap_rproc_timers_info *timers = pdata->timers;
+ int ret, i;
+#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND
+ _destroy_pm_flags(rproc);
+#endif
+ ret = omap_device_idle(pdev);
+ if (ret)
+ goto err;
+
+ for (i = 0; i < pdata->timers_cnt; i++) {
+#ifdef CONFIG_REMOTEPROC_WATCHDOG
+ /* GPT 9 and 11 are used as WDT */
+ if (timers[i].id == 9 || timers[i].id == 11)
+ free_irq(omap_dm_timer_get_irq(timers[i].odt), rproc);
+#endif
+ omap_dm_timer_free(timers[i].odt);
+ timers[i].odt = NULL;
+ }
+err:
+ return ret;
+}
+
+static int omap_rproc_set_lat(struct rproc *rproc, long val)
+{
+ pm_qos_update_request(rproc->qos_request, val);
+ return 0;
+}
+
+static int omap_rproc_set_l3_bw(struct rproc *rproc, long val)
+{
+ return omap_pm_set_min_bus_tput(rproc->dev, OCP_INITIATOR_AGENT, val);
+}
+
+static int omap_rproc_scale(struct rproc *rproc, long val)
+{
+ return omap_device_scale(rproc->dev, rproc->dev, val);
+}
+
+static struct rproc_ops omap_rproc_ops = {
+ .start = omap_rproc_start,
+ .stop = omap_rproc_stop,
+#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND
+ .suspend = omap_suspend,
+#endif
+ .iommu_init = omap_rproc_iommu_init,
+ .iommu_exit = omap_rproc_iommu_exit,
+ .set_lat = omap_rproc_set_lat,
+ .set_bw = omap_rproc_set_l3_bw,
+ .scale = omap_rproc_scale,
+#ifdef CONFIG_REMOTEPROC_WATCHDOG
+ .watchdog_init = omap_rproc_watchdog_init,
+ .watchdog_exit = omap_rproc_watchdog_exit,
+#endif
+ .dump_registers = omap_rproc_dump_registers,
+};
+
+static int omap_rproc_probe(struct platform_device *pdev)
+{
+ struct omap_rproc_pdata *pdata = pdev->dev.platform_data;
+
+ pdata->clkdm = clkdm_lookup(pdata->clkdm_name);
+
+ return rproc_register(&pdev->dev, pdata->name, &omap_rproc_ops,
+ pdata->firmware, pdata->memory_pool,
+ THIS_MODULE, pdata->sus_timeout);
+}
+
+static int __devexit omap_rproc_remove(struct platform_device *pdev)
+{
+ struct omap_rproc_pdata *pdata = pdev->dev.platform_data;
+
+ return rproc_unregister(pdata->name);
+}
+
+static struct platform_driver omap_rproc_driver = {
+ .probe = omap_rproc_probe,
+ .remove = __devexit_p(omap_rproc_remove),
+ .driver = {
+ .name = "omap-rproc",
+ .owner = THIS_MODULE,
+ .pm = GENERIC_RPROC_PM_OPS,
+ },
+};
+
+static int __init omap_rproc_init(void)
+{
+ return platform_driver_register(&omap_rproc_driver);
+}
+/* must be ready in time for device_initcall users */
+subsys_initcall(omap_rproc_init);
+
+static void __exit omap_rproc_exit(void)
+{
+ platform_driver_unregister(&omap_rproc_driver);
+}
+module_exit(omap_rproc_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("OMAP Remote Processor control driver");