diff options
Diffstat (limited to 'drivers/remoteproc/omap_remoteproc.c')
-rw-r--r-- | drivers/remoteproc/omap_remoteproc.c | 608 |
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(®s, + (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(®s)); + 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(®s) ? "n" : "ff", + fast_interrupts_enabled(®s) ? "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"); |