/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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");