From 20f7e0a78ecb00d3ed12b61b338f05fb9da9c29b Mon Sep 17 00:00:00 2001 From: John Cox Date: Mon, 19 Oct 2026 18:18:29 +0100 Subject: [PATCH 1/5] media/hevc_d: Add enum_framesizes Chrome tries to read the framesizes. It is handy info to have so supply it. Code taken from Hantro. Signed-off-by: John Cox --- .../raspberrypi/hevc_dec/hevc_d_video.c | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c index 27e92ccad24dfa..536fa6decf2645 100644 --- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c +++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c @@ -194,6 +194,26 @@ static int hevc_d_querycap(struct file *file, void *priv, return 0; } +static int hevc_d_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index != 0) + return -EINVAL; + + if (fsize->pixel_format != V4L2_PIX_FMT_HEVC_SLICE) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = HEVC_D_MIN_WIDTH; + fsize->stepwise.max_width = HEVC_D_MAX_WIDTH; + fsize->stepwise.step_width = 4; + fsize->stepwise.min_height = HEVC_D_MIN_HEIGHT; + fsize->stepwise.max_height = HEVC_D_MAX_HEIGHT; + fsize->stepwise.step_height = 4; + + return 0; +} + static int hevc_d_enum_fmt_vid_out(struct file *file, void *priv, struct v4l2_fmtdesc *f) { @@ -402,6 +422,7 @@ static int hevc_d_s_fmt_vid_out(struct file *file, void *priv, const struct v4l2_ioctl_ops hevc_d_ioctl_ops = { .vidioc_querycap = hevc_d_querycap, + .vidioc_enum_framesizes = hevc_d_enum_framesizes, .vidioc_enum_fmt_vid_cap = hevc_d_enum_fmt_vid_cap, .vidioc_g_fmt_vid_cap_mplane = hevc_d_g_fmt_vid_cap, From 0ab0c51c8b49f2b198267c8cad681f58c81308b7 Mon Sep 17 00:00:00 2001 From: John Cox Date: Mon, 19 Oct 2026 18:18:52 +0100 Subject: [PATCH 2/5] media/hevc_d: Relax restriction on OUTPUT S_FMT if buffers alloced There is no requirement to reject S_FMT if buffers are allocated and Chrome changes the format having alloced OUTPUT buffers. Relax so we only reject if streaming. Signed-off-by: John Cox --- drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c index 536fa6decf2645..3fc59c171e134a 100644 --- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c +++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c @@ -405,7 +405,7 @@ static int hevc_d_s_fmt_vid_out(struct file *file, void *priv, int ret; vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); - if (vb2_is_busy(vq)) + if (vb2_is_streaming(vq)) return -EBUSY; ret = hevc_d_try_fmt_vid_out(file, priv, f); From 59b02bd029136b7ef73b6703b8b8aaae3ef07556 Mon Sep 17 00:00:00 2001 From: John Cox Date: Tue, 20 Oct 2026 13:26:07 +0100 Subject: [PATCH 3/5] media/hevc_d: Add HEVC profile and level controls Add controls to indicate supported profiles and levels, copied from Hantro. Also whitespace fix to the ctrl table Signed-off-by: John Cox --- .../media/platform/raspberrypi/hevc_dec/hevc_d.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c index 392fd40119fcfb..4e6cffc6d74cc6 100644 --- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c +++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c @@ -46,7 +46,7 @@ static const struct v4l2_ctrl_config hevc_d_ctrls[] = { .id = V4L2_CID_STATELESS_HEVC_PPS, .ops = &hevc_d_hevc_pps_ctrl_ops, }, { - .id = V4L2_CID_STATELESS_HEVC_SCALING_MATRIX, + .id = V4L2_CID_STATELESS_HEVC_SCALING_MATRIX, }, { .id = V4L2_CID_STATELESS_HEVC_DECODE_PARAMS, }, { @@ -66,7 +66,17 @@ static const struct v4l2_ctrl_config hevc_d_ctrls[] = { .min = V4L2_STATELESS_HEVC_START_CODE_NONE, .max = V4L2_STATELESS_HEVC_START_CODE_ANNEX_B, .def = V4L2_STATELESS_HEVC_START_CODE_NONE, - }, + }, { + .id = V4L2_CID_MPEG_VIDEO_HEVC_PROFILE, + .min = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, + .max = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10, + .def = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, + }, { + .id = V4L2_CID_MPEG_VIDEO_HEVC_LEVEL, + .min = V4L2_MPEG_VIDEO_HEVC_LEVEL_1, + .max = V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1, + .def = V4L2_MPEG_VIDEO_HEVC_LEVEL_4_1, + } }; void *hevc_d_find_control_data(struct hevc_d_ctx *ctx, u32 id) From 4d7a55875f11a41883f64d6d517191e8c1363356 Mon Sep 17 00:00:00 2001 From: John Cox Date: Tue, 20 Oct 2026 13:33:32 +0100 Subject: [PATCH 4/5] media/hevc_d: Add vidioc_remove_bufs handling vidioc_remove_bufs was invented after this code was taken from Cedrus. Add the missing ioctl as there is no reason not to. Signed-off-by: John Cox --- drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c index 3fc59c171e134a..c96c734f4806d3 100644 --- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c +++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c @@ -440,6 +440,7 @@ const struct v4l2_ioctl_ops hevc_d_ioctl_ops = { .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_remove_bufs = v4l2_m2m_ioctl_remove_bufs, .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, .vidioc_streamon = v4l2_m2m_ioctl_streamon, From 976a286cbc18c4c49126e43786f291ed464e904b Mon Sep 17 00:00:00 2001 From: John Cox Date: Fri, 23 Oct 2026 14:22:09 +0100 Subject: [PATCH 5/5] media/hevc_d: Add runtime and system suspend/resume PM support Replace the manual clock enable/disable on stream start/stop with runtime PM, and add system sleep callbacks. The IRQ enables in the interrupt controller were only set at probe. Move them into the power on path so they are restored on every resume. On system suspend, stop new m2m jobs being run and then sync with the IRQ machinery. v4l2_m2m_suspend() only waits for the current job to finish, but jobs are finished before the h/w has completed phase 1 and phase 2, so that alone does not leave the h/w idle. Signed-off-by: John Cox --- .../platform/raspberrypi/hevc_dec/hevc_d.c | 58 ++++++++- .../raspberrypi/hevc_dec/hevc_d_h265.c | 4 +- .../raspberrypi/hevc_dec/hevc_d_h265.h | 1 + .../platform/raspberrypi/hevc_dec/hevc_d_hw.c | 120 +++++++++++++----- .../platform/raspberrypi/hevc_dec/hevc_d_hw.h | 6 +- .../raspberrypi/hevc_dec/hevc_d_video.c | 10 +- 6 files changed, 156 insertions(+), 43 deletions(-) diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c index 4e6cffc6d74cc6..3a4c192357f6e0 100644 --- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c +++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -221,6 +222,7 @@ static int hevc_d_probe(struct platform_device *pdev) dev->vfd = hevc_d_video_device; dev->dev = &pdev->dev; dev->pdev = pdev; + platform_set_drvdata(pdev, dev); ret = hevc_d_hw_probe(dev); if (ret) { @@ -291,8 +293,6 @@ static int hevc_d_probe(struct platform_device *pdev) goto err_m2m_mc; } - platform_set_drvdata(pdev, dev); - return 0; err_m2m_mc: @@ -327,6 +327,59 @@ static void hevc_d_remove(struct platform_device *pdev) hevc_d_hw_remove(dev); } +static int hevc_d_runtime_suspend(struct device *d) +{ + struct hevc_d_dev *dev = dev_get_drvdata(d); + + hevc_d_hw_power_off(dev); + return 0; +} + +static int hevc_d_runtime_resume(struct device *d) +{ + struct hevc_d_dev *dev = dev_get_drvdata(d); + + return hevc_d_hw_power_on(dev); +} + +static int hevc_d_suspend(struct device *d) +{ + struct hevc_d_dev *dev = dev_get_drvdata(d); + int ret; + + /* Stop new jobs being started */ + v4l2_m2m_suspend(dev->m2m_dev); + /* + * v4l2_m2m_suspend only waits for the current job to finish but we + * finish jobs before the h/w is done with them so also wait for any + * phase 1 / phase 2 work still in flight + */ + hevc_d_h265_sync(dev); + + ret = pm_runtime_force_suspend(d); + if (ret) + v4l2_m2m_resume(dev->m2m_dev); + return ret; +} + +static int hevc_d_resume(struct device *d) +{ + struct hevc_d_dev *dev = dev_get_drvdata(d); + int ret; + + ret = pm_runtime_force_resume(d); + if (ret) + return ret; + + v4l2_m2m_resume(dev->m2m_dev); + return 0; +} + +static const struct dev_pm_ops hevc_d_pm_ops = { + SYSTEM_SLEEP_PM_OPS(hevc_d_suspend, hevc_d_resume) + RUNTIME_PM_OPS(hevc_d_runtime_suspend, hevc_d_runtime_resume, NULL) +}; + static const struct of_device_id hevc_d_dt_match[] = { { .compatible = "brcm,bcm2711-hevc-dec", }, { .compatible = "brcm,bcm2712-hevc-dec", }, @@ -340,6 +393,7 @@ static struct platform_driver hevc_d_driver = { .driver = { .name = HEVC_D_NAME, .of_match_table = hevc_d_dt_match, + .pm = pm_ptr(&hevc_d_pm_ops), }, }; module_platform_driver(hevc_d_driver); diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c index 8be35a9d8dfd10..07d171feddfb29 100644 --- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c +++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c @@ -2286,7 +2286,7 @@ static void phase1_sync_claimed(struct hevc_d_dev *const dev, void *v) * phase1 has counted enables so must reenable once claimed * phase2 has unlimited enables */ -static void irq_sync(struct hevc_d_dev *const dev) +void hevc_d_h265_sync(struct hevc_d_dev *dev) { struct irq_sync sync; @@ -2322,7 +2322,7 @@ void hevc_d_h265_stop(struct hevc_d_ctx *ctx) { struct hevc_d_dev *const dev = ctx->dev; - irq_sync(dev); + hevc_d_h265_sync(dev); h265_ctx_uninit(dev, ctx); } diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.h b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.h index 9961917bf20401..75c248c76da668 100644 --- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.h +++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.h @@ -17,6 +17,7 @@ extern const struct v4l2_ctrl_ops hevc_d_hevc_slice_params_ctrl_ops; int hevc_d_h265_start(struct hevc_d_ctx *ctx); void hevc_d_h265_stop(struct hevc_d_ctx *ctx); void hevc_d_h265_trigger(struct hevc_d_ctx *ctx); +void hevc_d_h265_sync(struct hevc_d_dev *dev); void hevc_d_device_run(void *priv); diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.c index 8564b332f8d2af..14098fe0b57027 100644 --- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.c +++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -300,18 +301,22 @@ void hevc_d_hw_irq_active2_irq(struct hevc_d_dev *dev, } /* - * Stop the clock for this context - * clk_disable_unprepare does ref counting so this will not actually - * disable the clock if there are other running contexts + * Power down the h/w + * Called from runtime suspend (or directly if runtime PM is unavailable) */ -void hevc_d_hw_stop_clock(struct hevc_d_dev *dev) +void hevc_d_hw_power_off(struct hevc_d_dev *dev) { + clk_set_min_rate(dev->clock, 0); clk_disable_unprepare(dev->clock); } -/* Always starts the clock if it isn't already on this ctx */ -int hevc_d_hw_start_clock(struct hevc_d_dev *dev) +/* + * Power up the h/w and (re)initialise any state that is not set per-job + * Called from runtime resume (or directly if runtime PM is unavailable) + */ +int hevc_d_hw_power_on(struct hevc_d_dev *dev) { + u32 irq_stat; int rv; rv = clk_set_min_rate(dev->clock, dev->max_clock_rate); @@ -325,22 +330,47 @@ int hevc_d_hw_start_clock(struct hevc_d_dev *dev) dev_err(dev->dev, "Failed to enable clock\n"); return rv; } + + /* + * Enable IRQs & reset anything pending + * Whilst this seems the wrong way round the h/w doesn't actually + * set the IRQ status bits till the IRQs are enabled. On probe we + * haven't got the IRQ yet and on resume nothing is in flight so + * this should still be safe. + */ + irq_write(dev, ARG_IC_ICTRL, + ARG_IC_ICTRL_ACTIVE1_EN_SET | ARG_IC_ICTRL_ACTIVE2_EN_SET); + irq_stat = irq_read(dev, ARG_IC_ICTRL); + irq_write(dev, ARG_IC_ICTRL, irq_stat); + return 0; } -static int hw_setup(struct hevc_d_dev *dev) +/* + * Take a power reference for this context + * Runtime PM does ref counting so the h/w stays powered whilst any + * context holds a reference + */ +int hevc_d_hw_pm_get(struct hevc_d_dev *dev) +{ + int rv; + + rv = pm_runtime_resume_and_get(dev->dev); + if (rv) + dev_err(dev->dev, "Failed to power up: %d\n", rv); + return rv; +} + +void hevc_d_hw_pm_put(struct hevc_d_dev *dev) +{ + pm_runtime_put(dev->dev); +} + +static int get_max_clock_rate(struct hevc_d_dev *dev) { struct device_node *node; - u32 ver; - u32 irq_stat; struct rpi_firmware *firmware; - ver = apb_read(dev, RPI_VERSION); - if (ver != 0x202) { - dev_err(dev->dev, "Unexpected version %#x only 0x202 supported\n", ver); - return -ENODEV; - } - node = rpi_firmware_find_node(); if (!node) return -EINVAL; @@ -354,17 +384,19 @@ static int hw_setup(struct hevc_d_dev *dev) RPI_FIRMWARE_HEVC_CLK_ID); rpi_firmware_put(firmware); - /* - * Enable IRQs & reset anything pending - * Whilst this seems the wrong way round the h/w doesn't actually - * set the IRQ status bits till the IRQs are enabled. As we haven't - * got the IRQ yet this should still be safe. - */ - irq_write(dev, ARG_IC_ICTRL, - ARG_IC_ICTRL_ACTIVE1_EN_SET | ARG_IC_ICTRL_ACTIVE2_EN_SET); - irq_stat = irq_read(dev, ARG_IC_ICTRL); - irq_write(dev, ARG_IC_ICTRL, irq_stat); + return 0; +} + +/* H/w must be powered */ +static int check_version(struct hevc_d_dev *dev) +{ + u32 ver; + ver = apb_read(dev, RPI_VERSION); + if (ver != 0x202) { + dev_err(dev->dev, "Unexpected version %#x only 0x202 supported\n", ver); + return -ENODEV; + } return 0; } @@ -388,29 +420,54 @@ int hevc_d_hw_probe(struct hevc_d_dev *dev) if (IS_ERR(dev->clock)) return PTR_ERR(dev->clock); - ret = clk_prepare_enable(dev->clock); + ret = get_max_clock_rate(dev); if (ret) return ret; - ret = hw_setup(dev); - clk_disable_unprepare(dev->clock); + + /* + * Power up manually so this works without runtime PM. Runtime PM is + * told we are active once enabled and the driver core idles us after + * probe completes. + */ + ret = hevc_d_hw_power_on(dev); if (ret) return ret; + ret = check_version(dev); + if (ret) + goto fail_power_off; + irq_dec = platform_get_irq(dev->pdev, 0); - if (irq_dec < 0) - return irq_dec; + if (irq_dec < 0) { + ret = irq_dec; + goto fail_power_off; + } ret = devm_request_threaded_irq(dev->dev, irq_dec, hevc_d_irq_irq, hevc_d_irq_thread, 0, dev_name(dev->dev), dev); - if (ret) + if (ret) { dev_err(dev->dev, "Failed to request IRQ - %d\n", ret); + goto fail_power_off; + } + pm_runtime_set_active(dev->dev); + pm_runtime_enable(dev->dev); + + return 0; + +fail_power_off: + hevc_d_hw_power_off(dev); return ret; } void hevc_d_hw_remove(struct hevc_d_dev *dev) { + pm_runtime_disable(dev->dev); + if (!pm_runtime_status_suspended(dev->dev)) + hevc_d_hw_power_off(dev); + pm_runtime_set_suspended(dev->dev); + /* * IRQ auto freed on unload so no need to do it here * ioremap auto freed on unload @@ -418,4 +475,3 @@ void hevc_d_hw_remove(struct hevc_d_dev *dev) ictl_uninit(&dev->ic_active1); ictl_uninit(&dev->ic_active2); } - diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.h b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.h index 85fe96fc13d4c5..fae4474d00a3b1 100644 --- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.h +++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.h @@ -312,8 +312,10 @@ void hevc_d_hw_irq_active2_irq(struct hevc_d_dev *dev, struct hevc_d_hw_irq_ent *ient, hevc_d_irq_callback irq_cb, void *ctx); -int hevc_d_hw_start_clock(struct hevc_d_dev *dev); -void hevc_d_hw_stop_clock(struct hevc_d_dev *dev); +int hevc_d_hw_power_on(struct hevc_d_dev *dev); +void hevc_d_hw_power_off(struct hevc_d_dev *dev); +int hevc_d_hw_pm_get(struct hevc_d_dev *dev); +void hevc_d_hw_pm_put(struct hevc_d_dev *dev); int hevc_d_hw_probe(struct hevc_d_dev *dev); void hevc_d_hw_remove(struct hevc_d_dev *dev); diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c index c96c734f4806d3..43609a66da6c9f 100644 --- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c +++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c @@ -535,19 +535,19 @@ static int hevc_d_start_streaming(struct vb2_queue *vq, unsigned int count) v4l2_m2m_update_start_streaming_state(ctx->fh.m2m_ctx, vq); if (V4L2_TYPE_IS_OUTPUT(vq->type)) { - ret = hevc_d_hw_start_clock(dev); + ret = hevc_d_hw_pm_get(dev); if (ret) goto fail_cleanup; ret = hevc_d_h265_start(ctx); if (ret) - goto fail_stop_clock; + goto fail_pm_put; } return 0; -fail_stop_clock: - hevc_d_hw_stop_clock(dev); +fail_pm_put: + hevc_d_hw_pm_put(dev); fail_cleanup: v4l2_err(&dev->v4l2_dev, "%s: qtype=%d: FAIL\n", __func__, vq->type); hevc_d_queue_cleanup(vq, VB2_BUF_STATE_QUEUED); @@ -561,7 +561,7 @@ static void hevc_d_stop_streaming(struct vb2_queue *vq) if (V4L2_TYPE_IS_OUTPUT(vq->type)) { hevc_d_h265_stop(ctx); - hevc_d_hw_stop_clock(dev); + hevc_d_hw_pm_put(dev); } hevc_d_queue_cleanup(vq, VB2_BUF_STATE_ERROR);