media: cedrus: Implement runtime PM

This allows the VE clocks and PLL_VE to be disabled most of the time.
A runtime PM reference is held while streaming.

Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
Signed-off-by: Samuel Holland <samuel@sholland.org>
Reviewed-by: Ezequiel Garcia <ezequiel@collabora.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
This commit is contained in:
Jernej Skrabec 2020-05-09 22:06:43 +02:00 committed by Mauro Carvalho Chehab
parent a8876c22ea
commit d5aecd289b
4 changed files with 104 additions and 45 deletions

View file

@ -16,6 +16,7 @@
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/pm.h>
#include <media/v4l2-device.h> #include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h> #include <media/v4l2-ioctl.h>
@ -551,12 +552,18 @@ static const struct of_device_id cedrus_dt_match[] = {
}; };
MODULE_DEVICE_TABLE(of, cedrus_dt_match); MODULE_DEVICE_TABLE(of, cedrus_dt_match);
static const struct dev_pm_ops cedrus_dev_pm_ops = {
SET_RUNTIME_PM_OPS(cedrus_hw_suspend,
cedrus_hw_resume, NULL)
};
static struct platform_driver cedrus_driver = { static struct platform_driver cedrus_driver = {
.probe = cedrus_probe, .probe = cedrus_probe,
.remove = cedrus_remove, .remove = cedrus_remove,
.driver = { .driver = {
.name = CEDRUS_NAME, .name = CEDRUS_NAME,
.of_match_table = of_match_ptr(cedrus_dt_match), .of_match_table = of_match_ptr(cedrus_dt_match),
.pm = &cedrus_dev_pm_ops,
}, },
}; };
module_platform_driver(cedrus_driver); module_platform_driver(cedrus_driver);

View file

@ -19,6 +19,7 @@
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/reset.h> #include <linux/reset.h>
#include <linux/soc/sunxi/sunxi_sram.h> #include <linux/soc/sunxi/sunxi_sram.h>
@ -140,6 +141,64 @@ static irqreturn_t cedrus_irq(int irq, void *data)
return IRQ_HANDLED; return IRQ_HANDLED;
} }
int cedrus_hw_suspend(struct device *device)
{
struct cedrus_dev *dev = dev_get_drvdata(device);
reset_control_assert(dev->rstc);
clk_disable_unprepare(dev->ram_clk);
clk_disable_unprepare(dev->mod_clk);
clk_disable_unprepare(dev->ahb_clk);
return 0;
}
int cedrus_hw_resume(struct device *device)
{
struct cedrus_dev *dev = dev_get_drvdata(device);
int ret;
ret = clk_prepare_enable(dev->ahb_clk);
if (ret) {
dev_err(dev->dev, "Failed to enable AHB clock\n");
return ret;
}
ret = clk_prepare_enable(dev->mod_clk);
if (ret) {
dev_err(dev->dev, "Failed to enable MOD clock\n");
goto err_ahb_clk;
}
ret = clk_prepare_enable(dev->ram_clk);
if (ret) {
dev_err(dev->dev, "Failed to enable RAM clock\n");
goto err_mod_clk;
}
ret = reset_control_reset(dev->rstc);
if (ret) {
dev_err(dev->dev, "Failed to apply reset\n");
goto err_ram_clk;
}
return 0;
err_ram_clk:
clk_disable_unprepare(dev->ram_clk);
err_mod_clk:
clk_disable_unprepare(dev->mod_clk);
err_ahb_clk:
clk_disable_unprepare(dev->ahb_clk);
return ret;
}
int cedrus_hw_probe(struct cedrus_dev *dev) int cedrus_hw_probe(struct cedrus_dev *dev)
{ {
const struct cedrus_variant *variant; const struct cedrus_variant *variant;
@ -236,42 +295,17 @@ int cedrus_hw_probe(struct cedrus_dev *dev)
goto err_sram; goto err_sram;
} }
ret = clk_prepare_enable(dev->ahb_clk); pm_runtime_enable(dev->dev);
if (ret) { if (!pm_runtime_enabled(dev->dev)) {
dev_err(dev->dev, "Failed to enable AHB clock\n"); ret = cedrus_hw_resume(dev->dev);
if (ret)
goto err_sram; goto err_pm;
}
ret = clk_prepare_enable(dev->mod_clk);
if (ret) {
dev_err(dev->dev, "Failed to enable MOD clock\n");
goto err_ahb_clk;
}
ret = clk_prepare_enable(dev->ram_clk);
if (ret) {
dev_err(dev->dev, "Failed to enable RAM clock\n");
goto err_mod_clk;
}
ret = reset_control_reset(dev->rstc);
if (ret) {
dev_err(dev->dev, "Failed to apply reset\n");
goto err_ram_clk;
} }
return 0; return 0;
err_ram_clk: err_pm:
clk_disable_unprepare(dev->ram_clk); pm_runtime_disable(dev->dev);
err_mod_clk:
clk_disable_unprepare(dev->mod_clk);
err_ahb_clk:
clk_disable_unprepare(dev->ahb_clk);
err_sram: err_sram:
sunxi_sram_release(dev->dev); sunxi_sram_release(dev->dev);
err_mem: err_mem:
@ -282,11 +316,9 @@ int cedrus_hw_probe(struct cedrus_dev *dev)
void cedrus_hw_remove(struct cedrus_dev *dev) void cedrus_hw_remove(struct cedrus_dev *dev)
{ {
reset_control_assert(dev->rstc); pm_runtime_disable(dev->dev);
if (!pm_runtime_status_suspended(dev->dev))
clk_disable_unprepare(dev->ram_clk); cedrus_hw_suspend(dev->dev);
clk_disable_unprepare(dev->mod_clk);
clk_disable_unprepare(dev->ahb_clk);
sunxi_sram_release(dev->dev); sunxi_sram_release(dev->dev);

View file

@ -22,6 +22,9 @@ void cedrus_engine_disable(struct cedrus_dev *dev);
void cedrus_dst_format_set(struct cedrus_dev *dev, void cedrus_dst_format_set(struct cedrus_dev *dev,
struct v4l2_pix_format *fmt); struct v4l2_pix_format *fmt);
int cedrus_hw_suspend(struct device *device);
int cedrus_hw_resume(struct device *device);
int cedrus_hw_probe(struct cedrus_dev *dev); int cedrus_hw_probe(struct cedrus_dev *dev);
void cedrus_hw_remove(struct cedrus_dev *dev); void cedrus_hw_remove(struct cedrus_dev *dev);

View file

@ -13,6 +13,8 @@
* Marek Szyprowski, <m.szyprowski@samsung.com> * Marek Szyprowski, <m.szyprowski@samsung.com>
*/ */
#include <linux/pm_runtime.h>
#include <media/videobuf2-dma-contig.h> #include <media/videobuf2-dma-contig.h>
#include <media/v4l2-device.h> #include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h> #include <media/v4l2-ioctl.h>
@ -450,12 +452,24 @@ static int cedrus_start_streaming(struct vb2_queue *vq, unsigned int count)
return -EINVAL; return -EINVAL;
} }
if (V4L2_TYPE_IS_OUTPUT(vq->type) && if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
dev->dec_ops[ctx->current_codec]->start) ret = pm_runtime_get_sync(dev->dev);
ret = dev->dec_ops[ctx->current_codec]->start(ctx); if (ret < 0)
goto err_cleanup;
if (ret) if (dev->dec_ops[ctx->current_codec]->start) {
cedrus_queue_cleanup(vq, VB2_BUF_STATE_QUEUED); ret = dev->dec_ops[ctx->current_codec]->start(ctx);
if (ret)
goto err_pm;
}
}
return 0;
err_pm:
pm_runtime_put(dev->dev);
err_cleanup:
cedrus_queue_cleanup(vq, VB2_BUF_STATE_QUEUED);
return ret; return ret;
} }
@ -465,9 +479,12 @@ static void cedrus_stop_streaming(struct vb2_queue *vq)
struct cedrus_ctx *ctx = vb2_get_drv_priv(vq); struct cedrus_ctx *ctx = vb2_get_drv_priv(vq);
struct cedrus_dev *dev = ctx->dev; struct cedrus_dev *dev = ctx->dev;
if (V4L2_TYPE_IS_OUTPUT(vq->type) && if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
dev->dec_ops[ctx->current_codec]->stop) if (dev->dec_ops[ctx->current_codec]->stop)
dev->dec_ops[ctx->current_codec]->stop(ctx); dev->dec_ops[ctx->current_codec]->stop(ctx);
pm_runtime_put(dev->dev);
}
cedrus_queue_cleanup(vq, VB2_BUF_STATE_ERROR); cedrus_queue_cleanup(vq, VB2_BUF_STATE_ERROR);
} }