aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/remoteproc/Kconfig8
-rw-r--r--drivers/remoteproc/remoteproc.c250
-rw-r--r--include/linux/remoteproc.h35
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 */