Commit d5aecd28 authored by Jernej Skrabec's avatar Jernej Skrabec Committed by Mauro Carvalho Chehab
Browse files

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: default avatarJernej Skrabec <jernej.skrabec@siol.net>
Signed-off-by: default avatarSamuel Holland <samuel@sholland.org>
Reviewed-by: default avatarEzequiel Garcia <ezequiel@collabora.com>
Signed-off-by: default avatarHans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab+huawei@kernel.org>
parent a8876c22
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm.h>

#include <media/v4l2-device.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);

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 = {
	.probe		= cedrus_probe,
	.remove		= cedrus_remove,
	.driver		= {
		.name		= CEDRUS_NAME,
		.of_match_table	= of_match_ptr(cedrus_dt_match),
		.pm		= &cedrus_dev_pm_ops,
	},
};
module_platform_driver(cedrus_driver);
+69 −37
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/soc/sunxi/sunxi_sram.h>
@@ -140,6 +141,64 @@ static irqreturn_t cedrus_irq(int irq, void *data)
	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)
{
	const struct cedrus_variant *variant;
@@ -236,42 +295,17 @@ int cedrus_hw_probe(struct cedrus_dev *dev)
		goto err_sram;
	}

	ret = clk_prepare_enable(dev->ahb_clk);
	if (ret) {
		dev_err(dev->dev, "Failed to enable AHB clock\n");

		goto err_sram;
	}

	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;
	pm_runtime_enable(dev->dev);
	if (!pm_runtime_enabled(dev->dev)) {
		ret = cedrus_hw_resume(dev->dev);
		if (ret)
			goto err_pm;
	}

	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);
err_pm:
	pm_runtime_disable(dev->dev);
err_sram:
	sunxi_sram_release(dev->dev);
err_mem:
@@ -282,11 +316,9 @@ err_mem:

void cedrus_hw_remove(struct cedrus_dev *dev)
{
	reset_control_assert(dev->rstc);

	clk_disable_unprepare(dev->ram_clk);
	clk_disable_unprepare(dev->mod_clk);
	clk_disable_unprepare(dev->ahb_clk);
	pm_runtime_disable(dev->dev);
	if (!pm_runtime_status_suspended(dev->dev))
		cedrus_hw_suspend(dev->dev);

	sunxi_sram_release(dev->dev);

+3 −0
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@ void cedrus_engine_disable(struct cedrus_dev *dev);
void cedrus_dst_format_set(struct cedrus_dev *dev,
			   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);
void cedrus_hw_remove(struct cedrus_dev *dev);

+25 −8
Original line number Diff line number Diff line
@@ -13,6 +13,8 @@
 * Marek Szyprowski, <m.szyprowski@samsung.com>
 */

#include <linux/pm_runtime.h>

#include <media/videobuf2-dma-contig.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
@@ -450,11 +452,23 @@ static int cedrus_start_streaming(struct vb2_queue *vq, unsigned int count)
		return -EINVAL;
	}

	if (V4L2_TYPE_IS_OUTPUT(vq->type) &&
	    dev->dec_ops[ctx->current_codec]->start)
		ret = dev->dec_ops[ctx->current_codec]->start(ctx);
	if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
		ret = pm_runtime_get_sync(dev->dev);
		if (ret < 0)
			goto err_cleanup;

		if (dev->dec_ops[ctx->current_codec]->start) {
			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;
@@ -465,10 +479,13 @@ static void cedrus_stop_streaming(struct vb2_queue *vq)
	struct cedrus_ctx *ctx = vb2_get_drv_priv(vq);
	struct cedrus_dev *dev = ctx->dev;

	if (V4L2_TYPE_IS_OUTPUT(vq->type) &&
	    dev->dec_ops[ctx->current_codec]->stop)
	if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
		if (dev->dec_ops[ctx->current_codec]->stop)
			dev->dec_ops[ctx->current_codec]->stop(ctx);

		pm_runtime_put(dev->dev);
	}

	cedrus_queue_cleanup(vq, VB2_BUF_STATE_ERROR);
}