diff --git a/drivers/video/tegra/host/pva/nvpva_queue.c b/drivers/video/tegra/host/pva/nvpva_queue.c index e8bfa55e..94d67357 100644 --- a/drivers/video/tegra/host/pva/nvpva_queue.c +++ b/drivers/video/tegra/host/pva/nvpva_queue.c @@ -297,6 +297,42 @@ fail_alloc_pool: return ERR_PTR(err); } +#ifdef CONFIG_PM +int nvpva_queue_pool_prepare_suspend(struct nvpva_queue_pool *pool) +{ + struct nvhost_device_data *pdata = platform_get_drvdata(pool->pdev); + struct pva *pva = pdata->private_data; + int err = 0; + unsigned int queue_id; + unsigned int i; + + nvpva_dbg_fn(pva, ""); + + mutex_lock(&pool->queue_lock); + + /* For each active queues, ensure there are no outstanding tasks. */ + for (i = 0; i < NUM_POOL_ALLOC_SUB_TABLES; i++) + for_each_set_bit(queue_id, &pool->alloc_table[i], pool->max_queue_cnt) { + struct nvpva_queue *queue = &pool->queues[queue_id]; + struct nvpva_queue_task_pool *task_pool = queue->task_pool; + bool nvpva_queue_is_idle; + + mutex_lock(&task_pool->lock); + nvpva_queue_is_idle = (task_pool->alloc_table[i] == 0ULL); + mutex_unlock(&task_pool->lock); + + if (!nvpva_queue_is_idle) { + err = -EBUSY; + goto not_idle; + } + } + +not_idle: + mutex_unlock(&pool->queue_lock); + return err; +} +#endif /* CONFIG_PM */ + void nvpva_queue_deinit(struct nvpva_queue_pool *pool) { if (!pool) diff --git a/drivers/video/tegra/host/pva/nvpva_queue.h b/drivers/video/tegra/host/pva/nvpva_queue.h index 426acac6..e956a583 100644 --- a/drivers/video/tegra/host/pva/nvpva_queue.h +++ b/drivers/video/tegra/host/pva/nvpva_queue.h @@ -290,4 +290,20 @@ void nvpva_queue_free_task_memory(struct nvpva_queue *queue, int index); */ int nvpva_queue_set_attr(struct nvpva_queue *queue, void *arg); +#ifdef CONFIG_PM +/** + * @brief Prepare suspension of all queues. + * + * This function is responsible for the following, + * - Checks if there are no outstanding tasks. + * - Does not attempt to clear or flush the outstanding tasks. + * + * @param pool Pointer to a queue pool table. + * + * @return 0, on successul completion. + * -EBUSY, if there are any outstanding tasks in queue. + **/ +int nvpva_queue_pool_prepare_suspend(struct nvpva_queue_pool *pool); +#endif /* CONFIG_PM */ + #endif diff --git a/drivers/video/tegra/host/pva/pva.c b/drivers/video/tegra/host/pva/pva.c index 9972b76b..bb9743e1 100644 --- a/drivers/video/tegra/host/pva/pva.c +++ b/drivers/video/tegra/host/pva/pva.c @@ -1138,6 +1138,9 @@ static int pva_probe(struct platform_device *pdev) memset(&pva->vpu_util_info, 0, sizeof(pva->vpu_util_info)); pva->syncpts.syncpts_mapped_r = false; pva->syncpts.syncpts_mapped_rw = false; +#ifdef CONFIG_PM + pva->is_suspended = false; +#endif nvpva_dbg_fn(pva, "match. compatible = %s", match->compatible); pva->is_hv_mode = is_tegra_hypervisor_mode(); if (pva->is_hv_mode) @@ -1398,6 +1401,180 @@ static int __exit pva_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int nvpva_module_runtime_suspend(struct device *dev) +{ + struct nvhost_device_data *pdata = dev_get_drvdata(dev); + struct pva *pva = pdata->private_data; + + nvpva_dbg_fn(pva, ""); + + if (nvhost_module_pm_ops.runtime_suspend != NULL) + return nvhost_module_pm_ops.runtime_suspend(dev); + + return -EOPNOTSUPP; +} + +static int nvpva_module_runtime_resume(struct device *dev) +{ + struct nvhost_device_data *pdata = dev_get_drvdata(dev); + struct pva *pva = pdata->private_data; + + nvpva_dbg_fn(pva, ""); + + if (nvhost_module_pm_ops.runtime_resume != NULL) + return nvhost_module_pm_ops.runtime_resume(dev); + + return -EOPNOTSUPP; +} + +static int nvpva_module_suspend(struct device *dev) +{ + int err = 0; + struct nvhost_device_data *pdata = dev_get_drvdata(dev); + struct pva *pva = pdata->private_data; + + nvpva_dbg_fn(pva, ""); + + if (nvhost_module_pm_ops.suspend != NULL) { + err = nvhost_module_pm_ops.suspend(dev); + if (err != 0) { + dev_err(dev, "(FAIL) NvHost suspend\n"); + goto fail_nvhost_module_suspend; + } + } else { + err = pm_runtime_force_suspend(dev); + if (err != 0) { + dev_err(dev, "(FAIL) PM suspend\n"); + goto fail_nvhost_module_suspend; + } + } + + /* Mark module to be in suspend state. */ + pva->is_suspended = true; + +fail_nvhost_module_suspend: + return err; +} + +static int nvpva_module_resume(struct device *dev) +{ + int err = 0; + struct nvhost_device_data *pdata = dev_get_drvdata(dev); + struct pva *pva = pdata->private_data; nvpva_dbg_fn(pva, ""); + + + nvpva_dbg_fn(pva, ""); + + /* Confirm if module is in suspend state. */ + if (!pva->is_suspended) { + dev_warn(dev, "nvpva is not in suspend state.\n"); + goto fail_not_in_suspend; + } + + if (nvhost_module_pm_ops.resume != NULL) { + err = nvhost_module_pm_ops.resume(dev); + if (err != 0) { + dev_err(dev, "(FAIL) NvHost resume\n"); + goto fail_nvhost_module_resume; + } + } else { + err = pm_runtime_force_resume(dev); + if (err != 0) { + dev_err(dev, "(FAIL) PM resume\n"); + goto fail_nvhost_module_resume; + } + } + + return 0; + +fail_nvhost_module_resume: +fail_not_in_suspend: + return err; +} + +static int nvpva_module_prepare_suspend(struct device *dev) +{ + int err = 0; + struct nvhost_device_data *pdata = dev_get_drvdata(dev); + struct pva *pva = pdata->private_data; + + nvpva_dbg_fn(pva, ""); + + /* Confirm if module is not in suspend state. */ + if (pva->is_suspended) { + dev_warn(dev, "nvpva is already in suspend state.\n"); + goto fail_already_in_suspend; + } + + /* Prepare for queue pool suspension. */ + err = nvpva_queue_pool_prepare_suspend(pva->pool); + if (err != 0) { + dev_err(dev, "(FAIL) Queue suspend\n"); + goto fail_nvpva_queue_pool_prepare_suspend; + } + + /* NvHost prepare suspend - callback */ + if (nvhost_module_pm_ops.prepare != NULL) { + err = nvhost_module_pm_ops.prepare(dev); + if (err != 0) { + dev_err(dev, "(FAIL) NvHost prepare suspend\n"); + goto fail_nvhost_module_prepare_suspend; + } + } else { + /* If we took an extra reference, drop it now to prevent + * the device from automatically resuming upon system + * resume. + */ + pm_runtime_put_sync(dev); + } + + + return 0; + +fail_nvhost_module_prepare_suspend: +fail_nvpva_queue_pool_prepare_suspend: +fail_already_in_suspend: + return err; +} + +static void nvpva_module_complete_resume(struct device *dev) +{ + struct nvhost_device_data *pdata = dev_get_drvdata(dev); + struct pva *pva = pdata->private_data; + + nvpva_dbg_fn(pva, ""); + + if (nvhost_module_pm_ops.complete != NULL) { + nvhost_module_pm_ops.complete(dev); + } else { + /* Retake reference dropped above */ + pm_runtime_get_noresume(dev); + } + + /* Module is no longer in suspend and has resumed successfully */ + pva->is_suspended = false; +} + +/** + * SC7 suspend sequence + * - prepare_suspend + * - suspend + * + * SC7 resume sequence + * - resume + * - complete_resume + **/ +const struct dev_pm_ops nvpva_module_pm_ops = { + SET_RUNTIME_PM_OPS(nvpva_module_runtime_suspend, + nvpva_module_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(nvpva_module_suspend, + nvpva_module_resume) + .prepare = nvpva_module_prepare_suspend, + .complete = nvpva_module_complete_resume, +}; +#endif /* CONFIG_PM */ + static struct platform_driver pva_driver = { .probe = pva_probe, .remove = __exit_p(pva_remove), @@ -1408,7 +1585,7 @@ static struct platform_driver pva_driver = { .of_match_table = tegra_pva_of_match, #endif #ifdef CONFIG_PM - .pm = &nvhost_module_pm_ops, + .pm = &nvpva_module_pm_ops, #endif }, }; diff --git a/drivers/video/tegra/host/pva/pva.h b/drivers/video/tegra/host/pva/pva.h index 3e9e3616..6e9632d5 100644 --- a/drivers/video/tegra/host/pva/pva.h +++ b/drivers/video/tegra/host/pva/pva.h @@ -432,6 +432,9 @@ struct pva { struct work_struct pva_abort_handler_work; bool booted; +#ifdef CONFIG_PM + bool is_suspended; +#endif u32 log_level; u32 driver_log_mask; struct nvpva_client_context *clients;