diff options
-rw-r--r-- | drivers/remoteproc/Kconfig | 8 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc.c | 250 | ||||
-rw-r--r-- | include/linux/remoteproc.h | 35 |
3 files changed, 289 insertions, 4 deletions
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index bad48eb..069c8fd 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -6,6 +6,14 @@ config REMOTE_PROC tristate +config REMOTE_PROC_AUTOSUSPEND + bool "Autosuspend support for remoteproc" + depends on REMOTE_PROC + default y + help + Say Y here if you want remote processor to suspend + after some time of inactivity. + # can't be tristate, due to omap_device_* and omap_hwmod_* dependency config OMAP_REMOTE_PROC bool "OMAP remoteproc support" diff --git a/drivers/remoteproc/remoteproc.c b/drivers/remoteproc/remoteproc.c index 92c270b..7753c92 100644 --- a/drivers/remoteproc/remoteproc.c +++ b/drivers/remoteproc/remoteproc.c @@ -35,6 +35,7 @@ #include <linux/list.h> #include <linux/debugfs.h> #include <linux/remoteproc.h> +#include <linux/pm_runtime.h> /* list of available remote processors on this board */ static LIST_HEAD(rprocs); @@ -177,7 +178,21 @@ static int _event_notify(struct rproc *rproc, int type, void *data) case RPROC_ERROR: nh = &rproc->nb_error; rproc->state = RPROC_CRASHED; +#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND + pm_runtime_dont_use_autosuspend(rproc->dev); +#endif break; +#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND + case RPROC_PRE_SUSPEND: + nh = &rproc->nb_presus; + break; + case RPROC_POS_SUSPEND: + nh = &rproc->nb_possus; + break; + case RPROC_RESUME: + nh = &rproc->nb_resume; + break; +#endif default: return -EINVAL; } @@ -217,6 +232,16 @@ static void rproc_start(struct rproc *rproc, u64 bootaddr) goto unlock_mutext; } +#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, rproc->sus_timeout); + pm_runtime_get_noresume(rproc->dev); + pm_runtime_set_active(rproc->dev); + pm_runtime_enable(rproc->dev); + pm_runtime_mark_last_busy(rproc->dev); + pm_runtime_put_autosuspend(rproc->dev); +#endif + rproc->state = RPROC_RUNNING; dev_info(dev, "remote processor %s is now up\n", rproc->name); @@ -531,6 +556,17 @@ void rproc_put(struct rproc *rproc) * this is important, because the fw loading might have failed. */ if (rproc->state == RPROC_RUNNING || rproc->state == RPROC_CRASHED) { +#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND + /* + * Call resume, it will cancel any pending autosuspend, + * so that no callback is executed after the device is stopped. + * Device stop function takes care of shutting down the device. + */ + pm_runtime_get_sync(rproc->dev); + pm_runtime_put_noidle(rproc->dev); + pm_runtime_disable(rproc->dev); + pm_runtime_set_suspended(rproc->dev); +#endif ret = rproc->ops->stop(rproc); if (ret) { dev_err(dev, "can't stop rproc %s: %d\n", rproc->name, @@ -545,7 +581,6 @@ void rproc_put(struct rproc *rproc) goto out; } } - } rproc->state = RPROC_OFFLINE; @@ -576,6 +611,17 @@ static int _register(struct rproc *rproc, case RPROC_ERROR: nh = &rproc->nb_error; break; +#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND + case RPROC_PRE_SUSPEND: + nh = &rproc->nb_presus; + break; + case RPROC_POS_SUSPEND: + nh = &rproc->nb_possus; + break; + case RPROC_RESUME: + nh = &rproc->nb_resume; + break; +#endif default: return -EINVAL; } @@ -598,12 +644,199 @@ int rproc_event_unregister(struct rproc *rproc, } EXPORT_SYMBOL_GPL(rproc_event_unregister); +void rproc_last_busy(struct rproc *rproc) +{ +#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND + struct device *dev = rproc->dev; + unsigned long tj = jiffies + msecs_to_jiffies(10); + unsigned long exp; + + /* + * if expiration timeout is < 10msecs, cancel suspend at that + * moment to avoid any race condition. + */ + mutex_lock(&rproc->pm_lock); + exp = pm_runtime_autosuspend_expiration(dev); + if (pm_runtime_suspended(dev) || !exp || time_after(tj, exp)) { + /* + * if the remote processor is suspended, we can not wake it + * up (that would abort system suspend), instead state that + * the remote processor needs to be waken up on system resume. + */ + if (rproc->state == RPROC_SUSPENDED) { + rproc->need_resume = true; + goto unlock; + } + pm_runtime_get_sync(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + } + pm_runtime_mark_last_busy(dev); +unlock: + mutex_unlock(&rproc->pm_lock); +#endif +} +EXPORT_SYMBOL(rproc_last_busy); + +#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND +static int rproc_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rproc *rproc = platform_get_drvdata(pdev); + int ret = 0; + + dev_dbg(dev, "Enter %s\n", __func__); + + mutex_lock(&rproc->pm_lock); + if (rproc->state != RPROC_SUSPENDED) { + mutex_unlock(&rproc->pm_lock); + return 0; + } + + if (!rproc->need_resume) + goto unlock; + + rproc->need_resume = false; + pm_runtime_get_sync(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +unlock: + rproc->state = (ret) ? RPROC_CRASHED : RPROC_RUNNING; + mutex_unlock(&rproc->pm_lock); + if (ret) { + _event_notify(rproc, RPROC_ERROR, NULL); + dev_err(dev, "Error resuming %d\n", ret); + } + return ret; +} + +static int rproc_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rproc *rproc = platform_get_drvdata(pdev); + int ret = 0; + + dev_dbg(dev, "Enter %s\n", __func__); + + mutex_lock(&rproc->pm_lock); + if (rproc->state != RPROC_RUNNING) { + mutex_unlock(&rproc->pm_lock); + return 0; + } + + if (pm_runtime_suspended(dev)) + goto out; + /* + * If it is not runtime suspended, it means remote processor is still + * doing something. However we need to stop it. + */ + + dev_dbg(dev, "%s: will be forced to suspend\n", rproc->name); + + rproc->force_suspend = true; + ret = pm_runtime_suspend(dev); + rproc->force_suspend = false; + if (ret) + goto out; + /* + * As the remote processor had to be forced to suspend, it was + * executing some task, so it needs to be waken up on system resume + */ + rproc->need_resume = true; +out: + if (!ret) + rproc->state = RPROC_SUSPENDED; + mutex_unlock(&rproc->pm_lock); + + return ret; +} + +static int rproc_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rproc *rproc = platform_get_drvdata(pdev); + int ret = 0; + + dev_dbg(dev, "Enter %s\n", __func__); + + if (rproc->ops->resume) + ret = rproc->ops->resume(rproc); + + if (!ret) + _event_notify(rproc, RPROC_RESUME, NULL); + + return 0; +} + +static int rproc_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rproc *rproc = platform_get_drvdata(pdev); + int ret = 0; + unsigned to; + + dev_dbg(dev, "Enter %s\n", __func__); + + if (rproc->state == RPROC_SUSPENDED) + return 0; + /* + * Notify PROC_PRE_SUSPEND only when the suspend is not forced. + * Users can use pre suspend call back to cancel autosuspend, but + * when the suspend is forced, there is no need to notify them + */ + if (!rproc->force_suspend) + ret = _event_notify(rproc, RPROC_PRE_SUSPEND, NULL); + /* + * If rproc user avoids suspend, that means it is still using rproc. + * Lets go to abort suspend. + */ + if (ret) { + dev_dbg(dev, "suspend aborted by user %d\n", ret); + ret = -EBUSY; + goto abort; + } + /* Now call machine-specific suspend function (if exist) */ + if (rproc->ops->suspend) + ret = rproc->ops->suspend(rproc, rproc->force_suspend); + /* + * If it fails with -EBUSY/EAGAIN, remote processor is still running, + * but rproc users were not aware of that, so lets abort suspend. + * If it is a different error, there is something wrong with the + * remote processor. Return that error to pm runtime framework, + * which will disable autosuspend. + */ + if (ret) { + dev_dbg(dev, "suspend aborted by remote processor %d\n", ret); + if (ret != -EBUSY && ret != -EAGAIN) + dev_err(dev, "suspend error %d", ret); + goto abort; + } + /* we are not interested in the returned value */ + _event_notify(rproc, RPROC_POS_SUSPEND, NULL); + + return 0; +abort: + pm_runtime_mark_last_busy(dev); + to = jiffies_to_msecs(pm_runtime_autosuspend_expiration(dev) - jiffies); + pm_schedule_suspend(dev, to); + dev->power.timer_autosuspends = 1; + return ret; +} + +const struct dev_pm_ops rproc_gen_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(rproc_suspend, rproc_resume) + SET_RUNTIME_PM_OPS(rproc_runtime_suspend, rproc_runtime_resume, NULL) +}; +#endif + int rproc_register(struct device *dev, const char *name, const struct rproc_ops *ops, const char *firmware, const struct rproc_mem_entry *memory_maps, - struct module *owner) + struct module *owner, + unsigned sus_timeout) { + struct platform_device *pdev = to_platform_device(dev); struct rproc *rproc; if (!dev || !name || !ops) @@ -621,7 +854,10 @@ int rproc_register(struct device *dev, const char *name, rproc->firmware = firmware; rproc->owner = owner; rproc->memory_maps = memory_maps; - +#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND + rproc->sus_timeout = sus_timeout; + mutex_init(&rproc->pm_lock); +#endif mutex_init(&rproc->lock); INIT_WORK(&rproc->mmufault_work, rproc_mmufault_work); BLOCKING_INIT_NOTIFIER_HEAD(&rproc->nb_error); @@ -632,6 +868,8 @@ int rproc_register(struct device *dev, const char *name, list_add_tail(&rproc->next, &rprocs); spin_unlock(&rprocs_lock); + platform_set_drvdata(pdev, rproc); + dev_info(dev, "%s is available\n", name); if (!rproc_dbg) @@ -646,6 +884,12 @@ int rproc_register(struct device *dev, const char *name, debugfs_create_file("name", 0400, rproc->dbg_dir, rproc, &rproc_name_ops); +#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND + BLOCKING_INIT_NOTIFIER_HEAD(&rproc->nb_presus); + BLOCKING_INIT_NOTIFIER_HEAD(&rproc->nb_possus); + BLOCKING_INIT_NOTIFIER_HEAD(&rproc->nb_resume); +#endif + out: return 0; } diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index d3c741b..2dc9ec1 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -113,6 +113,8 @@ struct rproc; struct rproc_ops { int (*start)(struct rproc *rproc, u64 bootaddr); int (*stop)(struct rproc *rproc); + int (*suspend)(struct rproc *rproc, bool force); + int (*resume)(struct rproc *rproc); int (*iommu_init)(struct rproc *, int (*)(struct rproc *, u64, u32)); int (*iommu_exit)(struct rproc *); }; @@ -143,9 +145,23 @@ enum rproc_state { * enum rproc_event - remote processor events * * @RPROC_ERROR: Fatal error has happened on the remote processor. + * + * @RPROC_PRE_SUSPEND: users can register for that event in order to cancel + * autosuspend, they just need to return an error in the + * callback function. + * + * @RPROC_POS_SUSPEND: users can register for that event in order to release + * resources not needed when the remote processor is + * sleeping or if they need to save some context. + * + * @RPROC_RESUME: users should use this event to revert what was done in the + * POS_SUSPEND event. */ enum rproc_event { RPROC_ERROR, + RPROC_PRE_SUSPEND, + RPROC_POS_SUSPEND, + RPROC_RESUME, }; #define RPROC_MAX_NAME 100 @@ -192,6 +208,15 @@ struct rproc { struct completion firmware_loading_complete; struct work_struct mmufault_work; struct blocking_notifier_head nb_error; +#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND + unsigned sus_timeout; + bool force_suspend; + bool need_resume; + struct blocking_notifier_head nb_presus; + struct blocking_notifier_head nb_possus; + struct blocking_notifier_head nb_resume; + struct mutex pm_lock; +#endif }; struct rproc *rproc_get(const char *); @@ -199,7 +224,15 @@ void rproc_put(struct rproc *); int rproc_event_register(struct rproc *, struct notifier_block *, int); int rproc_event_unregister(struct rproc *, struct notifier_block *, int); int rproc_register(struct device *, const char *, const struct rproc_ops *, - const char *, const struct rproc_mem_entry *, struct module *); + const char *, const struct rproc_mem_entry *, struct module *, + unsigned int timeout); int rproc_unregister(const char *); +void rproc_last_busy(struct rproc *); +#ifdef CONFIG_REMOTE_PROC_AUTOSUSPEND +extern const struct dev_pm_ops rproc_gen_pm_ops; +#define GENERIC_RPROC_PM_OPS (&rproc_gen_pm_ops) +#else +#define GENERIC_RPROC_PM_OPS NULL +#endif #endif /* REMOTEPROC_H */ |