* [PATCH 0/7] media: verisilicon: Add RK3588 VPU720 JPEG decoder
@ 2026-08-19 10:37 Sascha Hauer
2026-08-19 10:37 ` [PATCH 1/7] media: verisilicon: Fix the cleanup when a codec ->run() fails Sascha Hauer
` (6 more replies)
0 siblings, 7 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-08-19 10:37 UTC (permalink / raw)
To: Nicolas Dufresne, Benjamin Gaignard, Philipp Zabel,
Mauro Carvalho Chehab, Heiko Stuebner, Ezequiel Garcia,
Andrzej Pietrasiewicz, Hans Verkuil, Chen-Yu Tsai, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lucas Sinn
Cc: Mauro Carvalho Chehab, linux-media, linux-rockchip, linux-kernel,
linux-arm-kernel, devicetree, Sascha Hauer
This series adds support for the RK3588 JPEG/MJPEG hardware decoder, the
VPU720. This series adds it to the hantro driver, along with three fixes
to the hantro core that came out of the work.
Patches 1-3 are independent of the new decoder and stand on their own. A
codec ->run() that fails currently leaves an armed watchdog behind that
later aborts an unrelated job, leaks the pm_runtime reference and the
clocks, and never completes the control handler object bound to the
media request, so the request stays queued until userspace closes it.
Patch 3 makes V4L2_EVENT_EOS subscribable; hantro queues the event in
three places but no application can ask for it.
Patches 4-7 add the decoder itself: the binding, the driver, a minimum
sizeimage for the coded queue, and the DT node.
Unlike the other hantro en/decoders the VPU720 works as a stateful
decoder - userspace hands it a whole frame and never looks inside the
bitstream. The driver parses the JPEG header on the CPU, builds the
quantisation and Huffman tables into a DMA side buffer and programs the
hardware from there.
Patch 6 enforces a minimum frame size to the coded queue for the JPEG
decoder path. This is necessary here as userspace doesn't know how large
a frame can get, but the decoder needs a full frame to work. In my
Gstreamer tests Gstreamer came up with buffers that were too small to
take a full frame at higher resolutions (> 1080p). That is expensive of
course, 16 MiB for a 4k image.
Tested on a Radxa Rock-5t board
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
Lucas Sinn (3):
media: verisilicon: Add Rockchip VPU720 JPEG decoder
media: verisilicon: Enforce a minimum sizeimage for the JPEG decoder
arm64: dts: rockchip: rk3588: Add VPU720 JPEG decoder node
Sascha Hauer (4):
media: verisilicon: Fix the cleanup when a codec ->run() fails
media: verisilicon: Complete the request on the ->run() error paths
media: verisilicon: Allow the EOS event to be subscribed
media: dt-bindings: Add Rockchip RK3588 VPU720 JPEG decoder
.../bindings/media/rockchip,rk3588-vpu720.yaml | 93 ++
MAINTAINERS | 1 +
arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 25 +
drivers/media/platform/verisilicon/Makefile | 1 +
drivers/media/platform/verisilicon/hantro.h | 17 +
drivers/media/platform/verisilicon/hantro_drv.c | 49 +-
.../platform/verisilicon/hantro_g1_h264_dec.c | 6 +-
.../platform/verisilicon/hantro_g1_mpeg2_dec.c | 2 +-
.../media/platform/verisilicon/hantro_g1_vp8_dec.c | 6 +-
.../platform/verisilicon/hantro_g2_hevc_dec.c | 11 +-
.../media/platform/verisilicon/hantro_g2_vp9_dec.c | 4 +-
.../platform/verisilicon/hantro_h1_jpeg_enc.c | 2 +-
drivers/media/platform/verisilicon/hantro_hw.h | 19 +-
drivers/media/platform/verisilicon/hantro_v4l2.c | 69 +-
.../verisilicon/rockchip_vpu2_hw_h264_dec.c | 6 +-
.../verisilicon/rockchip_vpu2_hw_jpeg_enc.c | 6 +-
.../verisilicon/rockchip_vpu2_hw_mpeg2_dec.c | 2 +-
.../verisilicon/rockchip_vpu2_hw_vp8_dec.c | 6 +-
.../verisilicon/rockchip_vpu720_hw_jpeg_dec.c | 962 +++++++++++++++++++++
.../platform/verisilicon/rockchip_vpu720_regs.h | 261 ++++++
.../verisilicon/rockchip_vpu981_hw_av1_dec.c | 5 +-
.../media/platform/verisilicon/rockchip_vpu_hw.c | 80 ++
22 files changed, 1595 insertions(+), 38 deletions(-)
---
base-commit: bd5f485f3f026225b86573e559af0b7254ef4184
change-id: 20260819-rk3588-jpegdec-89e1cf4c8898
Best regards,
--
Sascha Hauer <s.hauer@pengutronix.de>
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 1/7] media: verisilicon: Fix the cleanup when a codec ->run() fails
2026-08-19 10:37 [PATCH 0/7] media: verisilicon: Add RK3588 VPU720 JPEG decoder Sascha Hauer
@ 2026-08-19 10:37 ` Sascha Hauer
2026-08-19 11:23 ` sashiko-bot
2026-08-19 10:37 ` [PATCH 2/7] media: verisilicon: Complete the request on the ->run() error paths Sascha Hauer
` (5 subsequent siblings)
6 siblings, 1 reply; 13+ messages in thread
From: Sascha Hauer @ 2026-08-19 10:37 UTC (permalink / raw)
To: Nicolas Dufresne, Benjamin Gaignard, Philipp Zabel,
Mauro Carvalho Chehab, Heiko Stuebner, Ezequiel Garcia,
Andrzej Pietrasiewicz, Hans Verkuil, Chen-Yu Tsai, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lucas Sinn
Cc: Mauro Carvalho Chehab, linux-media, linux-rockchip, linux-kernel,
linux-arm-kernel, devicetree, Sascha Hauer
A codec ->run() operation that fails leaves three things behind, and they
cannot be untangled one at a time, so fix them together.
hantro_start_prepare_run() sets up the controls of the request attached
to the source buffer, hantro_end_prepare_run() completes them again and
arms the watchdog for the job that is about to be started. That pairing
does not survive the error paths. The two ->run() operations that do
reach hantro_end_prepare_run() arm a watchdog for a job that is never
started: device_run() finishes the job synchronously via
hantro_job_finish_no_pm() and nothing cancels the delayed work, so it
expires two seconds later and aborts whatever unrelated job happens to be
running by then.
Add an error argument to hantro_end_prepare_run() so that it can always
complete the request, but only arm the watchdog when the hardware is
really going to be started. Callers that succeed pass 0, the two existing
error paths pass their error code. Note the resulting invariant: after
hantro_end_prepare_run(ctx, 0) the ->run() operation must return 0, as
the watchdog is armed and only the interrupt handler disarms it.
rockchip_vpu981_av1_dec_run() then calls hantro_irq_done() on its error
path and returns the error code to device_run(), which finishes the job a
second time. The buffers have already been given back by then, so the
second attempt trips the WARN_ON(!src) in hantro_job_finish_no_pm() and
bails out. Without the watchdog change this at least reached the first
finish by accident, because the cancel_delayed_work() in
hantro_irq_done() returned true for the watchdog the error path had just
armed. Neither behaviour is something to rely on, so drop the call and
let device_run() clean the job up. No other codec calls hantro_irq_done()
from ->run().
That leaves device_run() itself. It takes a pm_runtime reference and
enables the clocks, then on any subsequent failure jumps to a single
err_cancel_job label that calls hantro_job_finish_no_pm() - which
releases neither. Release the acquired resources there.
This last part is what ties the three together. hantro_irq_done() ends up
in hantro_job_finish(), which already drops the pm reference and disables
the clocks, so as long as the AV1 error path still goes through it,
releasing the same resources in device_run() would trip the
WARN_ON(core->enable_count == 0)
in clk_core_disable() and underflow dev->power.usage_count. Conversely,
as soon as a failed job no longer arms the watchdog, hantro_irq_done()
stops releasing anything at all and the resources are leaked until
device_run() takes over.
The late_postproc setup is skipped on the error path as well. It is part
of preparing the run, the hardware is not started and the next job
configures it again. Only the sunxi variant sets late_postproc, and its
only decoder is VP9.
Fixes: 892bb6ecead9 ("media: hantro: do a PM resume earlier")
Fixes: e2da465455ce ("media: hantro: Support VP9 on the G2 core")
Fixes: 727a400686a2 ("media: verisilicon: Add Rockchip AV1 decoder")
Assisted-by: Claude:claude-opus-5
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/media/platform/verisilicon/hantro_drv.c | 32 +++++++++++++++++++---
.../platform/verisilicon/hantro_g1_h264_dec.c | 2 +-
.../platform/verisilicon/hantro_g1_mpeg2_dec.c | 2 +-
.../media/platform/verisilicon/hantro_g1_vp8_dec.c | 2 +-
.../platform/verisilicon/hantro_g2_hevc_dec.c | 2 +-
.../media/platform/verisilicon/hantro_g2_vp9_dec.c | 4 +--
.../platform/verisilicon/hantro_h1_jpeg_enc.c | 2 +-
drivers/media/platform/verisilicon/hantro_hw.h | 2 +-
.../verisilicon/rockchip_vpu2_hw_h264_dec.c | 2 +-
.../verisilicon/rockchip_vpu2_hw_jpeg_enc.c | 2 +-
.../verisilicon/rockchip_vpu2_hw_mpeg2_dec.c | 2 +-
.../verisilicon/rockchip_vpu2_hw_vp8_dec.c | 2 +-
.../verisilicon/rockchip_vpu981_hw_av1_dec.c | 5 ++--
13 files changed, 42 insertions(+), 19 deletions(-)
diff --git a/drivers/media/platform/verisilicon/hantro_drv.c b/drivers/media/platform/verisilicon/hantro_drv.c
index 2e81877f640fb..d9936f6979d2c 100644
--- a/drivers/media/platform/verisilicon/hantro_drv.c
+++ b/drivers/media/platform/verisilicon/hantro_drv.c
@@ -147,11 +147,28 @@ void hantro_start_prepare_run(struct hantro_ctx *ctx)
}
}
-void hantro_end_prepare_run(struct hantro_ctx *ctx)
+/**
+ * hantro_end_prepare_run() - finish the preparation of a job
+ * @ctx: context the job belongs to
+ * @error: 0 if the job is about to be started, negative errno if the
+ * codec ->run() operation failed and will return that error
+ *
+ * Every hantro_start_prepare_run() must be paired with a call to this
+ * function, including on the error paths of ->run(): the controls of the
+ * request were set up by hantro_start_prepare_run() and the request stays
+ * incomplete forever if they are not completed here.
+ *
+ * When @error is zero the caller must go on and start the hardware, as the
+ * watchdog is armed and only the interrupt handler disarms it again. On the
+ * error paths the job is finished synchronously by device_run(), so no
+ * watchdog is needed and arming it would make it expire during an unrelated
+ * job later on.
+ */
+void hantro_end_prepare_run(struct hantro_ctx *ctx, int error)
{
struct vb2_v4l2_buffer *src_buf;
- if (!ctx->is_encoder && ctx->dev->variant->late_postproc) {
+ if (!error && !ctx->is_encoder && ctx->dev->variant->late_postproc) {
if (hantro_needs_postproc(ctx, ctx->vpu_dst_fmt))
hantro_postproc_enable(ctx);
else
@@ -162,6 +179,9 @@ void hantro_end_prepare_run(struct hantro_ctx *ctx)
v4l2_ctrl_request_complete(src_buf->vb2_buf.req_obj.req,
&ctx->ctrl_handler);
+ if (error)
+ return;
+
/* Kick the watchdog. */
schedule_delayed_work(&ctx->dev->watchdog_work,
msecs_to_jiffies(2000));
@@ -182,15 +202,19 @@ static void device_run(void *priv)
ret = clk_bulk_enable(ctx->dev->variant->num_clocks, ctx->dev->clocks);
if (ret)
- goto err_cancel_job;
+ goto err_pm_put;
v4l2_m2m_buf_copy_metadata(src, dst);
if (ctx->codec_ops->run(ctx))
- goto err_cancel_job;
+ goto err_clk_disable;
return;
+err_clk_disable:
+ clk_bulk_disable(ctx->dev->variant->num_clocks, ctx->dev->clocks);
+err_pm_put:
+ pm_runtime_put_autosuspend(ctx->dev->dev);
err_cancel_job:
hantro_job_finish_no_pm(ctx->dev, ctx, VB2_BUF_STATE_ERROR);
}
diff --git a/drivers/media/platform/verisilicon/hantro_g1_h264_dec.c b/drivers/media/platform/verisilicon/hantro_g1_h264_dec.c
index ad5c1a6634f5c..30c2ac32cd0b0 100644
--- a/drivers/media/platform/verisilicon/hantro_g1_h264_dec.c
+++ b/drivers/media/platform/verisilicon/hantro_g1_h264_dec.c
@@ -264,7 +264,7 @@ int hantro_g1_h264_dec_run(struct hantro_ctx *ctx)
set_ref(ctx);
set_buffers(ctx, src_buf);
- hantro_end_prepare_run(ctx);
+ hantro_end_prepare_run(ctx, 0);
/* Start decoding! */
vdpu_write_relaxed(vpu,
diff --git a/drivers/media/platform/verisilicon/hantro_g1_mpeg2_dec.c b/drivers/media/platform/verisilicon/hantro_g1_mpeg2_dec.c
index e0d6bd0a6e44f..bfcece9f8e5dd 100644
--- a/drivers/media/platform/verisilicon/hantro_g1_mpeg2_dec.c
+++ b/drivers/media/platform/verisilicon/hantro_g1_mpeg2_dec.c
@@ -232,7 +232,7 @@ int hantro_g1_mpeg2_dec_run(struct hantro_ctx *ctx)
&dst_buf->vb2_buf,
seq, pic);
- hantro_end_prepare_run(ctx);
+ hantro_end_prepare_run(ctx, 0);
vdpu_write(vpu, G1_REG_INTERRUPT_DEC_E, G1_REG_INTERRUPT);
diff --git a/drivers/media/platform/verisilicon/hantro_g1_vp8_dec.c b/drivers/media/platform/verisilicon/hantro_g1_vp8_dec.c
index 851eb67f19f50..eb43b2fc19582 100644
--- a/drivers/media/platform/verisilicon/hantro_g1_vp8_dec.c
+++ b/drivers/media/platform/verisilicon/hantro_g1_vp8_dec.c
@@ -503,7 +503,7 @@ int hantro_g1_vp8_dec_run(struct hantro_ctx *ctx)
cfg_ref(ctx, hdr, vb2_dst);
cfg_buffers(ctx, hdr, vb2_dst);
- hantro_end_prepare_run(ctx);
+ hantro_end_prepare_run(ctx, 0);
vdpu_write(vpu, G1_REG_INTERRUPT_DEC_E, G1_REG_INTERRUPT);
diff --git a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c
index e8c2e83379def..d76d03eeac39d 100644
--- a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c
+++ b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c
@@ -611,7 +611,7 @@ int hantro_g2_hevc_dec_run(struct hantro_ctx *ctx)
prepare_scaling_list_buffer(ctx);
- hantro_end_prepare_run(ctx);
+ hantro_end_prepare_run(ctx, 0);
hantro_reg_write(vpu, &g2_mode, HEVC_DEC_MODE);
hantro_reg_write(vpu, &g2_clk_gate_e, 1);
diff --git a/drivers/media/platform/verisilicon/hantro_g2_vp9_dec.c b/drivers/media/platform/verisilicon/hantro_g2_vp9_dec.c
index 56c79e339030e..760f4acf90c34 100644
--- a/drivers/media/platform/verisilicon/hantro_g2_vp9_dec.c
+++ b/drivers/media/platform/verisilicon/hantro_g2_vp9_dec.c
@@ -895,7 +895,7 @@ int hantro_g2_vp9_dec_run(struct hantro_ctx *ctx)
ret = start_prepare_run(ctx, &decode_params);
if (ret) {
- hantro_end_prepare_run(ctx);
+ hantro_end_prepare_run(ctx, ret);
return ret;
}
@@ -904,7 +904,7 @@ int hantro_g2_vp9_dec_run(struct hantro_ctx *ctx)
config_registers(ctx, decode_params, src, dst);
- hantro_end_prepare_run(ctx);
+ hantro_end_prepare_run(ctx, 0);
vdpu_write(ctx->dev, G2_REG_INTERRUPT_DEC_E, G2_REG_INTERRUPT);
diff --git a/drivers/media/platform/verisilicon/hantro_h1_jpeg_enc.c b/drivers/media/platform/verisilicon/hantro_h1_jpeg_enc.c
index 86cc1a07026f0..78f51e92fb2e3 100644
--- a/drivers/media/platform/verisilicon/hantro_h1_jpeg_enc.c
+++ b/drivers/media/platform/verisilicon/hantro_h1_jpeg_enc.c
@@ -148,7 +148,7 @@ int hantro_h1_jpeg_enc_run(struct hantro_ctx *ctx)
| H1_REG_ENC_PIC_INTRA
| H1_REG_ENC_CTRL_EN_BIT;
- hantro_end_prepare_run(ctx);
+ hantro_end_prepare_run(ctx, 0);
vepu_write(vpu, reg, H1_REG_ENC_CTRL);
diff --git a/drivers/media/platform/verisilicon/hantro_hw.h b/drivers/media/platform/verisilicon/hantro_hw.h
index 13e573f1f19de..9754672a3306b 100644
--- a/drivers/media/platform/verisilicon/hantro_hw.h
+++ b/drivers/media/platform/verisilicon/hantro_hw.h
@@ -430,7 +430,7 @@ void hantro_watchdog(struct work_struct *work);
void hantro_irq_done(struct hantro_dev *vpu,
enum vb2_buffer_state result);
void hantro_start_prepare_run(struct hantro_ctx *ctx);
-void hantro_end_prepare_run(struct hantro_ctx *ctx);
+void hantro_end_prepare_run(struct hantro_ctx *ctx, int error);
irqreturn_t hantro_g1_irq(int irq, void *dev_id);
void hantro_g1_reset(struct hantro_ctx *ctx);
diff --git a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_h264_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_h264_dec.c
index 6da87f5184bcb..eb9067d56f35d 100644
--- a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_h264_dec.c
+++ b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_h264_dec.c
@@ -481,7 +481,7 @@ int rockchip_vpu2_h264_dec_run(struct hantro_ctx *ctx)
set_ref(ctx);
set_buffers(ctx, src_buf);
- hantro_end_prepare_run(ctx);
+ hantro_end_prepare_run(ctx, 0);
/* Start decoding! */
reg = vdpu_read(vpu, VDPU_SWREG(57)) | VDPU_REG_DEC_E(1);
diff --git a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c
index 61621b1be8a2f..aa34bd2e47ad2 100644
--- a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c
+++ b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c
@@ -180,7 +180,7 @@ int rockchip_vpu2_jpeg_enc_run(struct hantro_ctx *ctx)
| VEPU_REG_ENCODE_ENABLE;
/* Kick the watchdog and start encoding */
- hantro_end_prepare_run(ctx);
+ hantro_end_prepare_run(ctx, 0);
vepu_write(vpu, reg, VEPU_REG_ENCODE_START);
return 0;
diff --git a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_mpeg2_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_mpeg2_dec.c
index 50a3a3eeaa00d..e87604abb4626 100644
--- a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_mpeg2_dec.c
+++ b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_mpeg2_dec.c
@@ -239,7 +239,7 @@ int rockchip_vpu2_mpeg2_dec_run(struct hantro_ctx *ctx)
&dst_buf->vb2_buf, seq, pic);
/* Kick the watchdog and start decoding */
- hantro_end_prepare_run(ctx);
+ hantro_end_prepare_run(ctx, 0);
reg = vdpu_read(vpu, VDPU_SWREG(57)) | VDPU_REG_DEC_E(1);
vdpu_write(vpu, reg, VDPU_SWREG(57));
diff --git a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_vp8_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_vp8_dec.c
index d079075448c96..6568e2aee80fc 100644
--- a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_vp8_dec.c
+++ b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_vp8_dec.c
@@ -592,7 +592,7 @@ int rockchip_vpu2_vp8_dec_run(struct hantro_ctx *ctx)
cfg_ref(ctx, hdr, vb2_dst);
cfg_buffers(ctx, hdr, vb2_dst);
- hantro_end_prepare_run(ctx);
+ hantro_end_prepare_run(ctx, 0);
hantro_reg_write(vpu, &vp8_dec_start_dec, 1);
diff --git a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c
index e4e21ad373233..c6a5af979d478 100644
--- a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c
+++ b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c
@@ -2185,15 +2185,14 @@ int rockchip_vpu981_av1_dec_run(struct hantro_ctx *ctx)
rockchip_vpu981_av1_dec_set_output_buffer(ctx);
rockchip_vpu981_av1_dec_set_input_buffer(ctx, vb2_src);
- hantro_end_prepare_run(ctx);
+ hantro_end_prepare_run(ctx, 0);
hantro_reg_write(vpu, &av1_dec_e, 1);
return 0;
prepare_error:
- hantro_end_prepare_run(ctx);
- hantro_irq_done(vpu, VB2_BUF_STATE_ERROR);
+ hantro_end_prepare_run(ctx, ret);
return ret;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 2/7] media: verisilicon: Complete the request on the ->run() error paths
2026-08-19 10:37 [PATCH 0/7] media: verisilicon: Add RK3588 VPU720 JPEG decoder Sascha Hauer
2026-08-19 10:37 ` [PATCH 1/7] media: verisilicon: Fix the cleanup when a codec ->run() fails Sascha Hauer
@ 2026-08-19 10:37 ` Sascha Hauer
2026-08-19 12:19 ` sashiko-bot
2026-08-19 10:37 ` [PATCH 3/7] media: verisilicon: Allow the EOS event to be subscribed Sascha Hauer
` (4 subsequent siblings)
6 siblings, 1 reply; 13+ messages in thread
From: Sascha Hauer @ 2026-08-19 10:37 UTC (permalink / raw)
To: Nicolas Dufresne, Benjamin Gaignard, Philipp Zabel,
Mauro Carvalho Chehab, Heiko Stuebner, Ezequiel Garcia,
Andrzej Pietrasiewicz, Hans Verkuil, Chen-Yu Tsai, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lucas Sinn
Cc: Mauro Carvalho Chehab, linux-media, linux-rockchip, linux-kernel,
linux-arm-kernel, devicetree, Sascha Hauer
Several codec ->run() operations return early without calling
hantro_end_prepare_run(), even though hantro_start_prepare_run() has
already run and set up the controls of the request attached to the source
buffer.
The buffers are still returned to userspace, device_run() finishes the job
with VB2_BUF_STATE_ERROR, but nothing completes the control handler object
bound to the media request. vb2_buffer_done() only unbinds the request
object owned by videobuf2 itself, so num_incomplete_objects never drops to
zero and the request stays in MEDIA_REQUEST_STATE_QUEUED forever: poll() on
the request file descriptor never returns and MEDIA_REQUEST_IOC_REINIT
fails with -EBUSY. The request is only cleaned up when userspace closes it.
The vb2 buf_request_complete() callback does not help here, it is only
called from __vb2_queue_cancel() for buffers that were never queued to the
driver.
Call hantro_end_prepare_run() with the error code on those paths. Since
hantro_end_prepare_run() takes an error argument this completes the request
without arming the watchdog.
Fixes: 42cb2a8f27d2 ("media: hantro: change hantro_codec_ops run prototype to return errors")
Assisted-by: Claude:claude-opus-5
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/media/platform/verisilicon/hantro_g1_h264_dec.c | 4 +++-
drivers/media/platform/verisilicon/hantro_g1_vp8_dec.c | 4 +++-
drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c | 9 +++++++--
drivers/media/platform/verisilicon/rockchip_vpu2_hw_h264_dec.c | 4 +++-
drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c | 4 +++-
drivers/media/platform/verisilicon/rockchip_vpu2_hw_vp8_dec.c | 4 +++-
6 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/drivers/media/platform/verisilicon/hantro_g1_h264_dec.c b/drivers/media/platform/verisilicon/hantro_g1_h264_dec.c
index 30c2ac32cd0b0..4e1012eeeab6d 100644
--- a/drivers/media/platform/verisilicon/hantro_g1_h264_dec.c
+++ b/drivers/media/platform/verisilicon/hantro_g1_h264_dec.c
@@ -255,8 +255,10 @@ int hantro_g1_h264_dec_run(struct hantro_ctx *ctx)
/* Prepare the H264 decoder context. */
ret = hantro_h264_dec_prepare_run(ctx);
- if (ret)
+ if (ret) {
+ hantro_end_prepare_run(ctx, ret);
return ret;
+ }
/* Configure hardware registers. */
src_buf = hantro_get_src_buf(ctx);
diff --git a/drivers/media/platform/verisilicon/hantro_g1_vp8_dec.c b/drivers/media/platform/verisilicon/hantro_g1_vp8_dec.c
index eb43b2fc19582..a001b37ee1e32 100644
--- a/drivers/media/platform/verisilicon/hantro_g1_vp8_dec.c
+++ b/drivers/media/platform/verisilicon/hantro_g1_vp8_dec.c
@@ -442,8 +442,10 @@ int hantro_g1_vp8_dec_run(struct hantro_ctx *ctx)
hantro_start_prepare_run(ctx);
hdr = hantro_get_ctrl(ctx, V4L2_CID_STATELESS_VP8_FRAME);
- if (WARN_ON(!hdr))
+ if (WARN_ON(!hdr)) {
+ hantro_end_prepare_run(ctx, -EINVAL);
return -EINVAL;
+ }
/* Reset segment_map buffer in keyframe */
if (V4L2_VP8_FRAME_IS_KEY_FRAME(hdr) && ctx->vp8_dec.segment_map.cpu)
diff --git a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c
index d76d03eeac39d..2093cbe59b837 100644
--- a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c
+++ b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c
@@ -596,7 +596,7 @@ int hantro_g2_hevc_dec_run(struct hantro_ctx *ctx)
/* Prepare HEVC decoder context. */
ret = hantro_hevc_dec_prepare_run(ctx);
if (ret)
- return ret;
+ goto end_prepare_run;
/* Configure hardware registers. */
set_params(ctx);
@@ -604,7 +604,7 @@ int hantro_g2_hevc_dec_run(struct hantro_ctx *ctx)
/* set reference pictures */
ret = set_ref(ctx);
if (ret)
- return ret;
+ goto end_prepare_run;
set_buffers(ctx);
prepare_tile_info_buffer(ctx);
@@ -634,4 +634,9 @@ int hantro_g2_hevc_dec_run(struct hantro_ctx *ctx)
vdpu_write(vpu, G2_REG_INTERRUPT_DEC_E, G2_REG_INTERRUPT);
return 0;
+
+end_prepare_run:
+ hantro_end_prepare_run(ctx, ret);
+
+ return ret;
}
diff --git a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_h264_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_h264_dec.c
index eb9067d56f35d..58b39879de723 100644
--- a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_h264_dec.c
+++ b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_h264_dec.c
@@ -473,8 +473,10 @@ int rockchip_vpu2_h264_dec_run(struct hantro_ctx *ctx)
/* Prepare the H264 decoder context. */
ret = hantro_h264_dec_prepare_run(ctx);
- if (ret)
+ if (ret) {
+ hantro_end_prepare_run(ctx, ret);
return ret;
+ }
src_buf = hantro_get_src_buf(ctx);
set_params(ctx, src_buf);
diff --git a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c
index aa34bd2e47ad2..b273b0b56ced5 100644
--- a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c
+++ b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c
@@ -143,8 +143,10 @@ int rockchip_vpu2_jpeg_enc_run(struct hantro_ctx *ctx)
memset(&jpeg_ctx, 0, sizeof(jpeg_ctx));
jpeg_ctx.buffer = vb2_plane_vaddr(&dst_buf->vb2_buf, 0);
- if (!jpeg_ctx.buffer)
+ if (!jpeg_ctx.buffer) {
+ hantro_end_prepare_run(ctx, -ENOMEM);
return -ENOMEM;
+ }
jpeg_ctx.width = ctx->dst_fmt.width;
jpeg_ctx.height = ctx->dst_fmt.height;
diff --git a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_vp8_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_vp8_dec.c
index 6568e2aee80fc..1569e86c23bbf 100644
--- a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_vp8_dec.c
+++ b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_vp8_dec.c
@@ -519,8 +519,10 @@ int rockchip_vpu2_vp8_dec_run(struct hantro_ctx *ctx)
hantro_start_prepare_run(ctx);
hdr = hantro_get_ctrl(ctx, V4L2_CID_STATELESS_VP8_FRAME);
- if (WARN_ON(!hdr))
+ if (WARN_ON(!hdr)) {
+ hantro_end_prepare_run(ctx, -EINVAL);
return -EINVAL;
+ }
/* Reset segment_map buffer in keyframe */
if (V4L2_VP8_FRAME_IS_KEY_FRAME(hdr) && ctx->vp8_dec.segment_map.cpu)
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 3/7] media: verisilicon: Allow the EOS event to be subscribed
2026-08-19 10:37 [PATCH 0/7] media: verisilicon: Add RK3588 VPU720 JPEG decoder Sascha Hauer
2026-08-19 10:37 ` [PATCH 1/7] media: verisilicon: Fix the cleanup when a codec ->run() fails Sascha Hauer
2026-08-19 10:37 ` [PATCH 2/7] media: verisilicon: Complete the request on the ->run() error paths Sascha Hauer
@ 2026-08-19 10:37 ` Sascha Hauer
2026-08-19 10:37 ` [PATCH 4/7] media: dt-bindings: Add Rockchip RK3588 VPU720 JPEG decoder Sascha Hauer
` (3 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-08-19 10:37 UTC (permalink / raw)
To: Nicolas Dufresne, Benjamin Gaignard, Philipp Zabel,
Mauro Carvalho Chehab, Heiko Stuebner, Ezequiel Garcia,
Andrzej Pietrasiewicz, Hans Verkuil, Chen-Yu Tsai, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lucas Sinn
Cc: Mauro Carvalho Chehab, linux-media, linux-rockchip, linux-kernel,
linux-arm-kernel, devicetree, Sascha Hauer
hantro queues a V4L2_EVENT_EOS in three places. vidioc_encoder_cmd()
sends it when a V4L2_ENC_CMD_STOP has drained the encoder:
if (ec->cmd == V4L2_ENC_CMD_STOP &&
v4l2_m2m_has_stopped(ctx->fh.m2m_ctx))
v4l2_event_queue_fh(&ctx->fh, &hantro_eos_event);
hantro_buf_queue() sends it for a capture buffer queued after the last
one, and hantro_stop_streaming() when the output queue stops while
draining.
No application can ask for any of them. The ioctl ops offer
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
and v4l2_ctrl_subscribe_event() only knows about V4L2_EVENT_CTRL, so
VIDIOC_SUBSCRIBE_EVENT for V4L2_EVENT_EOS fails with -EINVAL and the
queued events are dropped on the floor. An application following the
drain sequence in the stateful encoder documentation has to fall back to
V4L2_BUF_FLAG_LAST, which hantro does set, so this has gone unnoticed.
Dispatch on the event type and hand V4L2_EVENT_EOS to
v4l2_event_subscribe(), the way coda-common.c does. Everything else
keeps going to the control handler.
V4L2_EVENT_SOURCE_CHANGE is deliberately not added, hantro never sends
one.
Fixes: daf3999c12dc ("media: hantro: Implement support for encoder commands")
Assisted-by: Claude:claude-opus-5
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/media/platform/verisilicon/hantro_v4l2.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/drivers/media/platform/verisilicon/hantro_v4l2.c b/drivers/media/platform/verisilicon/hantro_v4l2.c
index 83af9fa1ce949..9e19daffe0075 100644
--- a/drivers/media/platform/verisilicon/hantro_v4l2.c
+++ b/drivers/media/platform/verisilicon/hantro_v4l2.c
@@ -765,6 +765,17 @@ static int vidioc_encoder_cmd(struct file *file, void *priv,
return 0;
}
+static int hantro_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_EOS:
+ return v4l2_event_subscribe(fh, sub, 0, NULL);
+ default:
+ return v4l2_ctrl_subscribe_event(fh, sub);
+ }
+}
+
const struct v4l2_ioctl_ops hantro_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_framesizes = vidioc_enum_framesizes,
@@ -787,7 +798,7 @@ const struct v4l2_ioctl_ops hantro_ioctl_ops = {
.vidioc_remove_bufs = v4l2_m2m_ioctl_remove_bufs,
.vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
- .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_subscribe_event = hantro_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
.vidioc_streamon = v4l2_m2m_ioctl_streamon,
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 4/7] media: dt-bindings: Add Rockchip RK3588 VPU720 JPEG decoder
2026-08-19 10:37 [PATCH 0/7] media: verisilicon: Add RK3588 VPU720 JPEG decoder Sascha Hauer
` (2 preceding siblings ...)
2026-08-19 10:37 ` [PATCH 3/7] media: verisilicon: Allow the EOS event to be subscribed Sascha Hauer
@ 2026-08-19 10:37 ` Sascha Hauer
2026-08-19 10:37 ` [PATCH 5/7] media: verisilicon: Add Rockchip " Sascha Hauer
` (2 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-08-19 10:37 UTC (permalink / raw)
To: Nicolas Dufresne, Benjamin Gaignard, Philipp Zabel,
Mauro Carvalho Chehab, Heiko Stuebner, Ezequiel Garcia,
Andrzej Pietrasiewicz, Hans Verkuil, Chen-Yu Tsai, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lucas Sinn
Cc: Mauro Carvalho Chehab, linux-media, linux-rockchip, linux-kernel,
linux-arm-kernel, devicetree, Sascha Hauer
Add a devicetree binding schema for the VPU720 JPEG hardware decoder on
the RK3588, driven by the hantro V4L2 driver. Documents the single
register window (task registers plus the LLP link-table block at offset
0x300), the JPEG decode interrupt, the aclk/hclk clocks, the AXI/AHB
resets, the IOMMU and the power domain.
The resets and the power domain are required. The block sits in
RK3588_PD_VDPU and cannot be reached with the domain off, and the driver
takes the resets with reset_control_array_get_optional(), so a node
without them probes and the hard reset fallback in its error recovery
silently does nothing.
The IOMMU stays optional. The driver never refers to it, it is a platform
integration detail, and rockchip-vpu.yaml does not require it for the
other codec blocks on this SoC either.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
.../bindings/media/rockchip,rk3588-vpu720.yaml | 93 ++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 94 insertions(+)
diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3588-vpu720.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3588-vpu720.yaml
new file mode 100644
index 0000000000000..6bbddea66c9b9
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/rockchip,rk3588-vpu720.yaml
@@ -0,0 +1,93 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/rockchip,rk3588-vpu720.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Rockchip VPU720 JPEG Decoder
+
+maintainers:
+ - Lucas Sinn <lucas.sinn@wolfvision.net>
+
+description:
+ The VPU720 is Rockchip's in-house JPEG/MJPEG hardware decoder found on the
+ RK3588. It is driven by the Hantro V4L2 driver. A dedicated Link List
+ Processor (LLP) register block, located at offset 0x300 within the same
+ register window, allows the hardware to decode a chain of frames
+ autonomously ("link mode").
+
+properties:
+ compatible:
+ const: rockchip,rk3588-vpu720
+
+ reg:
+ maxItems: 1
+ description:
+ The decoder register window. It covers both the task (function)
+ registers at offset 0x000 and the LLP (link table) registers at
+ offset 0x300.
+
+ interrupts:
+ maxItems: 1
+
+ interrupt-names:
+ const: vdpu
+
+ clocks:
+ items:
+ - description: AXI clock
+ - description: AHB clock
+
+ clock-names:
+ items:
+ - const: aclk
+ - const: hclk
+
+ resets:
+ items:
+ - description: AXI reset line
+ - description: AHB reset line
+
+ reset-names:
+ items:
+ - const: axi
+ - const: ahb
+
+ power-domains:
+ maxItems: 1
+
+ iommus:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-names
+ - clocks
+ - clock-names
+ - resets
+ - reset-names
+ - power-domains
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/rockchip,rk3588-cru.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/power/rk3588-power.h>
+ #include <dt-bindings/reset/rockchip,rk3588-cru.h>
+
+ video-codec@fdb90000 {
+ compatible = "rockchip,rk3588-vpu720";
+ reg = <0xfdb90000 0x400>;
+ interrupts = <GIC_SPI 129 IRQ_TYPE_LEVEL_HIGH 0>;
+ interrupt-names = "vdpu";
+ clocks = <&cru ACLK_JPEG_DECODER>, <&cru HCLK_JPEG_DECODER>;
+ clock-names = "aclk", "hclk";
+ resets = <&cru SRST_A_JPEG_DECODER>, <&cru SRST_H_JPEG_DECODER>;
+ reset-names = "axi", "ahb";
+ iommus = <&jpegd_mmu>;
+ power-domains = <&power RK3588_PD_VDPU>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 9f8d226b305aa..7ef189e4ba237 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11375,6 +11375,7 @@ L: linux-rockchip@lists.infradead.org
S: Maintained
F: Documentation/devicetree/bindings/media/nxp,imx8mq-vpu.yaml
F: Documentation/devicetree/bindings/media/rockchip,rk3568-vepu.yaml
+F: Documentation/devicetree/bindings/media/rockchip,rk3588-vpu720.yaml
F: Documentation/devicetree/bindings/media/rockchip-vpu.yaml
F: drivers/media/platform/verisilicon/
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 5/7] media: verisilicon: Add Rockchip VPU720 JPEG decoder
2026-08-19 10:37 [PATCH 0/7] media: verisilicon: Add RK3588 VPU720 JPEG decoder Sascha Hauer
` (3 preceding siblings ...)
2026-08-19 10:37 ` [PATCH 4/7] media: dt-bindings: Add Rockchip RK3588 VPU720 JPEG decoder Sascha Hauer
@ 2026-08-19 10:37 ` Sascha Hauer
2026-08-19 11:24 ` sashiko-bot
2026-08-19 12:46 ` Heiko Stübner
2026-08-19 10:37 ` [PATCH 6/7] media: verisilicon: Enforce a minimum sizeimage for the " Sascha Hauer
2026-08-19 10:37 ` [PATCH 7/7] arm64: dts: rockchip: rk3588: Add VPU720 JPEG decoder node Sascha Hauer
6 siblings, 2 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-08-19 10:37 UTC (permalink / raw)
To: Nicolas Dufresne, Benjamin Gaignard, Philipp Zabel,
Mauro Carvalho Chehab, Heiko Stuebner, Ezequiel Garcia,
Andrzej Pietrasiewicz, Hans Verkuil, Chen-Yu Tsai, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lucas Sinn
Cc: Mauro Carvalho Chehab, linux-media, linux-rockchip, linux-kernel,
linux-arm-kernel, devicetree, Sascha Hauer
From: Lucas Sinn <lucas.sinn@wolfvision.net>
Add support for the Rockchip VPU720 JPEG hardware decoder on RK3588
to the verisilicon hantro driver.
Hardware requirements:
- CPU-side JPEG header parsing via v4l2_jpeg_parse_header()
- DMA side buffer with Q-tables (zigzag->raster), Huffman mincode
and value tables
- VPU720-specific two-phase IRQ clear sequence
- 16-byte stream alignment with start-byte offset
- MCU-aligned PIC_H (e.g. 1080p YUV420 needs 1088, not 1080)
- FILL_DOWN_E on every NV12 conversion, the output chroma is vertically
subsampled so the hardware completes the bottom of the picture
- DRI (restart interval) support when present
Implementation adds:
- HANTRO_JPEG_DECODER codec and HANTRO_MODE_JPEG_DEC mode
- struct hantro_jpeg_dec_hw_ctx holding the Q/H table side buffer,
which is rebuilt from the frame header on every run
- src_needs_kmap and dst_needs_kmap flags for vb2_plane_vaddr() without
DMA_ATTR_NO_KERNEL_MAPPING
- a neutral chroma plane written by the driver for a grayscale frame.
The output format converter has no YUV400 path, so the hardware writes
the luma plane and leaves the chroma alone, which comes out green
- rockchip_vpu720_jpeg_dec_run() for parse/fill/program/kick
- rejection of a frame that carries no EOI marker, which is what a
source buffer too small for the frame looks like
- rejection of a frame larger than the negotiated capture format,
whose dimensions would otherwise be programmed against strides
taken from that format
- IRQ handler with detailed error diagnostics (REG32/33:
MCU position, error flags) and soft-reset
- AXI perf counter setup (REG30)
Exposes one V4L2 M2M device: JPEG input -> NV12 output.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Lucas Sinn <lucas.sinn@wolfvision.net>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/media/platform/verisilicon/Makefile | 1 +
drivers/media/platform/verisilicon/hantro.h | 17 +
drivers/media/platform/verisilicon/hantro_drv.c | 17 +-
drivers/media/platform/verisilicon/hantro_hw.h | 17 +
drivers/media/platform/verisilicon/hantro_v4l2.c | 38 +-
.../verisilicon/rockchip_vpu720_hw_jpeg_dec.c | 962 +++++++++++++++++++++
.../platform/verisilicon/rockchip_vpu720_regs.h | 261 ++++++
.../media/platform/verisilicon/rockchip_vpu_hw.c | 80 ++
8 files changed, 1385 insertions(+), 8 deletions(-)
diff --git a/drivers/media/platform/verisilicon/Makefile b/drivers/media/platform/verisilicon/Makefile
index f6f019d04ff00..5442692cce44b 100644
--- a/drivers/media/platform/verisilicon/Makefile
+++ b/drivers/media/platform/verisilicon/Makefile
@@ -33,6 +33,7 @@ hantro-vpu-$(CONFIG_VIDEO_HANTRO_ROCKCHIP) += \
rockchip_vpu2_hw_mpeg2_dec.o \
rockchip_vpu2_hw_vp8_dec.o \
rockchip_vpu981_hw_av1_dec.o \
+ rockchip_vpu720_hw_jpeg_dec.o \
rockchip_av1_filmgrain.o \
rockchip_av1_entropymode.o \
rockchip_vpu_hw.o
diff --git a/drivers/media/platform/verisilicon/hantro.h b/drivers/media/platform/verisilicon/hantro.h
index 0353de154a1ec..00e0f5981cca3 100644
--- a/drivers/media/platform/verisilicon/hantro.h
+++ b/drivers/media/platform/verisilicon/hantro.h
@@ -39,6 +39,7 @@ struct hantro_postproc_ops;
#define HANTRO_HEVC_DECODER BIT(19)
#define HANTRO_VP9_DECODER BIT(20)
#define HANTRO_AV1_DECODER BIT(21)
+#define HANTRO_JPEG_DECODER BIT(22)
#define HANTRO_DECODERS 0xffff0000
/**
@@ -102,6 +103,19 @@ struct hantro_variant {
unsigned int double_buffer : 1;
unsigned int legacy_regs : 1;
unsigned int late_postproc : 1;
+ /*
+ * src_needs_kmap: when set, the source queue will be allocated with
+ * a kernel virtual address so the driver can CPU-parse the bitstream
+ * (e.g. for JPEG header parsing).
+ */
+ unsigned int src_needs_kmap : 1;
+ /*
+ * dst_needs_kmap: when set, the capture queue will be allocated with
+ * a kernel virtual address so the driver can write the parts of a
+ * frame the hardware does not produce (e.g. the chroma plane of a
+ * grayscale JPEG).
+ */
+ unsigned int dst_needs_kmap : 1;
const struct of_device_id *shared_devices;
};
@@ -115,6 +129,7 @@ struct hantro_variant {
* @HANTRO_MODE_HEVC_DEC: HEVC decoder.
* @HANTRO_MODE_VP9_DEC: VP9 decoder.
* @HANTRO_MODE_AV1_DEC: AV1 decoder
+ * @HANTRO_MODE_JPEG_DEC: VPU720 JPEG decoder
*/
enum hantro_codec_mode {
HANTRO_MODE_NONE = -1,
@@ -125,6 +140,7 @@ enum hantro_codec_mode {
HANTRO_MODE_HEVC_DEC,
HANTRO_MODE_VP9_DEC,
HANTRO_MODE_AV1_DEC,
+ HANTRO_MODE_JPEG_DEC,
};
/*
@@ -276,6 +292,7 @@ struct hantro_ctx {
struct hantro_hevc_dec_hw_ctx hevc_dec;
struct hantro_vp9_dec_hw_ctx vp9_dec;
struct hantro_av1_dec_hw_ctx av1_dec;
+ struct hantro_jpeg_dec_hw_ctx jpeg_dec;
};
};
diff --git a/drivers/media/platform/verisilicon/hantro_drv.c b/drivers/media/platform/verisilicon/hantro_drv.c
index d9936f6979d2c..e0e01c1b7563c 100644
--- a/drivers/media/platform/verisilicon/hantro_drv.c
+++ b/drivers/media/platform/verisilicon/hantro_drv.c
@@ -237,11 +237,13 @@ queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)
/*
* Driver does mostly sequential access, so sacrifice TLB efficiency
- * for faster allocation. Also, no CPU access on the source queue,
- * so no kernel mapping needed.
+ * for faster allocation. Omit DMA_ATTR_NO_KERNEL_MAPPING when the
+ * hardware variant needs to CPU-parse the source bitstream (e.g. the
+ * VPU720 JPEG decoder reads Q/H tables from the JPEG header).
*/
- src_vq->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES |
- DMA_ATTR_NO_KERNEL_MAPPING;
+ src_vq->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES;
+ if (!ctx->dev->variant->src_needs_kmap)
+ src_vq->dma_attrs |= DMA_ATTR_NO_KERNEL_MAPPING;
src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
src_vq->lock = &ctx->dev->vpu_mutex;
@@ -257,10 +259,12 @@ queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)
dst_vq->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES;
/*
* The Kernel needs access to the JPEG destination buffer for the
- * JPEG encoder to fill in the JPEG headers.
+ * JPEG encoder to fill in the JPEG headers, and for a decoder whose
+ * hardware variant leaves part of the frame to the driver.
*/
if (!ctx->is_encoder) {
- dst_vq->dma_attrs |= DMA_ATTR_NO_KERNEL_MAPPING;
+ if (!ctx->dev->variant->dst_needs_kmap)
+ dst_vq->dma_attrs |= DMA_ATTR_NO_KERNEL_MAPPING;
dst_vq->max_num_buffers = MAX_POSTPROC_BUFFERS;
}
@@ -746,6 +750,7 @@ static const struct of_device_id of_hantro_match[] = {
{ .compatible = "rockchip,rk3568-vpu", .data = &rk3568_vpu_variant, },
{ .compatible = "rockchip,rk3588-vepu121", .data = &rk3568_vepu_variant, },
{ .compatible = "rockchip,rk3588-av1-vpu", .data = &rk3588_vpu981_variant, },
+ { .compatible = "rockchip,rk3588-vpu720", .data = &rk3588_vpu720_variant, },
#endif
#ifdef CONFIG_VIDEO_HANTRO_IMX8M
{ .compatible = "nxp,imx8mm-vpu-g1", .data = &imx8mm_vpu_g1_variant, },
diff --git a/drivers/media/platform/verisilicon/hantro_hw.h b/drivers/media/platform/verisilicon/hantro_hw.h
index 9754672a3306b..51c35d4d0c5fd 100644
--- a/drivers/media/platform/verisilicon/hantro_hw.h
+++ b/drivers/media/platform/verisilicon/hantro_hw.h
@@ -341,6 +341,16 @@ struct hantro_av1_dec_hw_ctx {
struct mvcdfs cdfs_last_ndvc[NUM_REF_FRAMES];
int current_frame_index;
};
+
+/**
+ * struct hantro_jpeg_dec_hw_ctx
+ *
+ * @table_base: Q-table and Huffman table side buffer.
+ */
+struct hantro_jpeg_dec_hw_ctx {
+ struct hantro_aux_buf table_base;
+};
+
/**
* struct hantro_postproc_ctx
*
@@ -415,6 +425,7 @@ extern const struct hantro_variant rk3399_vpu_variant;
extern const struct hantro_variant rk3568_vepu_variant;
extern const struct hantro_variant rk3568_vpu_variant;
extern const struct hantro_variant rk3588_vpu981_variant;
+extern const struct hantro_variant rk3588_vpu720_variant;
extern const struct hantro_variant sama5d4_vdec_variant;
extern const struct hantro_variant sunxi_vpu_variant;
extern const struct hantro_variant stm32mp25_vdec_variant;
@@ -463,6 +474,12 @@ void rockchip_vpu981_av1_dec_exit(struct hantro_ctx *ctx);
int rockchip_vpu981_av1_dec_run(struct hantro_ctx *ctx);
void rockchip_vpu981_av1_dec_done(struct hantro_ctx *ctx);
+int rockchip_vpu720_jpeg_dec_init(struct hantro_ctx *ctx);
+void rockchip_vpu720_jpeg_dec_exit(struct hantro_ctx *ctx);
+int rockchip_vpu720_jpeg_dec_run(struct hantro_ctx *ctx);
+void rockchip_vpu720_reset(struct hantro_ctx *ctx);
+irqreturn_t rockchip_vpu720_irq(int irq, void *dev_id);
+
static inline unsigned short hantro_vp9_num_sbs(unsigned short dimension)
{
return (dimension + 63) / 64;
diff --git a/drivers/media/platform/verisilicon/hantro_v4l2.c b/drivers/media/platform/verisilicon/hantro_v4l2.c
index 9e19daffe0075..b9b1848e43b30 100644
--- a/drivers/media/platform/verisilicon/hantro_v4l2.c
+++ b/drivers/media/platform/verisilicon/hantro_v4l2.c
@@ -765,6 +765,40 @@ static int vidioc_encoder_cmd(struct file *file, void *priv,
return 0;
}
+/*
+ * The stateless codecs take a slice or frame per buffer described by the
+ * per frame controls and only accept V4L2_DEC_CMD_FLUSH. The JPEG decoder
+ * has no controls and decodes a whole frame per buffer, so applications
+ * drive it like a stateful decoder and expect V4L2_DEC_CMD_STOP to work
+ * for draining. The two sets of commands are disjoint, so dispatch to the
+ * helper matching the codec rather than picking one for everybody.
+ *
+ * The coded format of the context decides, not the set of codecs the device
+ * implements, so a variant offering both kinds answers per file handle.
+ */
+static bool hantro_is_stateful_dec(struct hantro_ctx *ctx)
+{
+ return ctx->vpu_src_fmt->codec_mode == HANTRO_MODE_JPEG_DEC;
+}
+
+static int hantro_try_decoder_cmd(struct file *file, void *priv,
+ struct v4l2_decoder_cmd *dc)
+{
+ if (hantro_is_stateful_dec(file_to_ctx(file)))
+ return v4l2_m2m_ioctl_try_decoder_cmd(file, priv, dc);
+
+ return v4l2_m2m_ioctl_stateless_try_decoder_cmd(file, priv, dc);
+}
+
+static int hantro_decoder_cmd(struct file *file, void *priv,
+ struct v4l2_decoder_cmd *dc)
+{
+ if (hantro_is_stateful_dec(file_to_ctx(file)))
+ return v4l2_m2m_ioctl_decoder_cmd(file, priv, dc);
+
+ return v4l2_m2m_ioctl_stateless_decoder_cmd(file, priv, dc);
+}
+
static int hantro_subscribe_event(struct v4l2_fh *fh,
const struct v4l2_event_subscription *sub)
{
@@ -807,8 +841,8 @@ const struct v4l2_ioctl_ops hantro_ioctl_ops = {
.vidioc_g_selection = vidioc_g_selection,
.vidioc_s_selection = vidioc_s_selection,
- .vidioc_decoder_cmd = v4l2_m2m_ioctl_stateless_decoder_cmd,
- .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_stateless_try_decoder_cmd,
+ .vidioc_decoder_cmd = hantro_decoder_cmd,
+ .vidioc_try_decoder_cmd = hantro_try_decoder_cmd,
.vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd,
.vidioc_encoder_cmd = vidioc_encoder_cmd,
diff --git a/drivers/media/platform/verisilicon/rockchip_vpu720_hw_jpeg_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu720_hw_jpeg_dec.c
new file mode 100644
index 0000000000000..81fd79911d694
--- /dev/null
+++ b/drivers/media/platform/verisilicon/rockchip_vpu720_hw_jpeg_dec.c
@@ -0,0 +1,962 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip VPU720 JPEG decoder driver
+ *
+ * Ported from the Rockchip MPP HAL (hal_jpegd_rkv.c /
+ * hal_jpegd_vpu7xx_com.c) and the downstream mpp_jpgdec.c kernel driver.
+ *
+ * Copyright (C) 2020 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2026 WolfVision GmbH
+ * Author: <lucas.sinn@wolfvision.net>
+ */
+
+#include <linux/align.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <media/v4l2-jpeg.h>
+#include <media/v4l2-mem2mem.h>
+
+#include "hantro.h"
+#include "hantro_hw.h"
+#include "rockchip_vpu720_regs.h"
+
+static inline struct hantro_jpeg_dec_hw_ctx *
+jpeg_dec_ctx(struct hantro_ctx *ctx)
+{
+ return &ctx->jpeg_dec;
+}
+
+/*
+ * vdpu720_jpeg_mode - map V4L2 JPEG sampling to hardware JPEG mode.
+ *
+ * The sampling factor tuple is determined by the luma channel's factors
+ * relative to the maximum in the frame. Standard JFIF layouts only,
+ * anything else returns -EINVAL: the mode also picks the MCU height and
+ * therefore PIC_H, so guessing one would decode into a wrong image.
+ */
+static int vdpu720_jpeg_mode(const struct v4l2_jpeg_frame_header *frame)
+{
+ u8 h0, v0;
+
+ if (frame->num_components == 1)
+ return VDPU720_JPEG_MODE_YUV400;
+
+ /* Component 0 always carries luma in JFIF */
+ h0 = frame->component[0].horizontal_sampling_factor;
+ v0 = frame->component[0].vertical_sampling_factor;
+
+ if (h0 == 1 && v0 == 1)
+ return VDPU720_JPEG_MODE_YUV444;
+ if (h0 == 2 && v0 == 1)
+ return VDPU720_JPEG_MODE_YUV422;
+ if (h0 == 2 && v0 == 2)
+ return VDPU720_JPEG_MODE_YUV420;
+ if (h0 == 4 && v0 == 1)
+ return VDPU720_JPEG_MODE_YUV411;
+ if (h0 == 1 && v0 == 2)
+ return VDPU720_JPEG_MODE_YUV440;
+
+ return -EINVAL;
+}
+
+/*
+ * vdpu720_nb_htbl_sets - number of Huffman table sets the hardware reads.
+ *
+ * One for a grayscale frame, two for a colour one. vdpu720_write_htbl()
+ * fills that many sets and vdpu720_fill_regs() sizes HTBL_SEL and the length
+ * registers from the same number, so the two cannot drift apart.
+ */
+static unsigned int vdpu720_nb_htbl_sets(unsigned int num_components)
+{
+ return num_components == 1 ? 1 : VDPU720_NB_HTBL_SETS;
+}
+
+/*
+ * vdpu720_write_qtbl - write all Q-tables into the DMA side buffer.
+ *
+ * Tables are stored sequentially, one per component in component order.
+ * Each entry is widened to u16 and reordered from JPEG zig-zag scan to
+ * natural raster-scan order, matching the hardware expectation.
+ */
+static int vdpu720_write_qtbl(struct hantro_ctx *ctx,
+ const struct v4l2_jpeg_header *hdr)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ struct hantro_jpeg_dec_hw_ctx *jpeg_ctx = jpeg_dec_ctx(ctx);
+ u16 *base = jpeg_ctx->table_base.cpu;
+ unsigned int k, i;
+
+ for (k = 0; k < hdr->frame.num_components; k++) {
+ u8 tq_id = hdr->frame.component[k].quantization_table_selector;
+ u8 qtbl[VDPU720_QTBL_ENTRIES];
+ u16 *dst;
+
+ /*
+ * v4l2_jpeg_parse_header() filled quantization_tables[] by
+ * destination selector (packed DQT segments handled); .start
+ * points at the 64 Qk values, already past the Pq|Tq byte.
+ */
+ if (tq_id > 3 || !hdr->quantization_tables[tq_id].start) {
+ dev_err(vpu->dev,
+ "Q-table %u not found for component %u\n",
+ tq_id, k);
+ return -EINVAL;
+ }
+
+ /*
+ * Bulk-copy the Q-table out of the uncached source buffer once;
+ * the per-element zigzag reads below would otherwise each be a
+ * separate uncached bus transaction.
+ */
+ memcpy(qtbl, hdr->quantization_tables[tq_id].start, sizeof(qtbl));
+ dst = base + k * VDPU720_QTBL_ENTRIES;
+
+ /*
+ * Reorder zigzag → raster scan.
+ * v4l2_jpeg_zigzag_scan_index[z] = raster position of zigzag
+ * element z. JPEG Q-tables are stored in zigzag order; the
+ * hardware expects them in natural raster (row-major) order.
+ */
+ for (i = 0; i < VDPU720_QTBL_ENTRIES; i++)
+ dst[v4l2_jpeg_zigzag_scan_index[i]] = (u16)qtbl[i];
+ }
+
+ return 0;
+}
+
+/*
+ * vdpu720_compute_mincode - compute the minimum Huffman code arrays for
+ * one Huffman table (DC or AC) from the 16-byte BITS array.
+ *
+ * @bits: BITS[16] – number of codes of each length 1..16
+ * @min_code: output: minimum code value per length (16 entries)
+ * @acc_addr: output: accumulated symbol-table address per length (16 entries)
+ *
+ * Algorithm ported verbatim from jpegd_vpu7xx_write_htbl().
+ */
+static void vdpu720_compute_mincode(const u8 *bits,
+ u16 *min_code, u16 *acc_addr)
+{
+ u16 code = 0, addr = 0;
+ unsigned int j;
+
+ for (j = 0; j < 16; j++) {
+ u16 len = bits[j];
+
+ if (len == 0 && j > 0) {
+ if (code > ((u16)(min_code[j - 1]) << 1))
+ min_code[j] = code;
+ else
+ min_code[j] = (u16)(min_code[j - 1]) << 1;
+ } else {
+ min_code[j] = code;
+ }
+
+ code += len;
+ addr += len;
+ acc_addr[j] = addr;
+ code <<= 1;
+ }
+
+ /* Sentinel: set min_code[0] to the last valid code + count */
+ if (bits[15])
+ min_code[0] = min_code[15] + bits[15] - 1;
+ else
+ min_code[0] = min_code[15];
+}
+
+/*
+ * vdpu720_write_htbl - fill the Huffman mincode and value sub-buffers.
+ *
+ * One set is written per vdpu720_nb_htbl_sets(), fed by the scan component
+ * that uses it: the first by the luma component and the second by the first
+ * chroma one. The hardware has no room for a third set and no per component
+ * selector register, so a frame whose two chroma components disagree on their
+ * tables cannot be described to it and is refused rather than decoded with
+ * the wrong table for the last component.
+ *
+ * Per-set layout in the mincode buffer:
+ * 16 x u16 DC min-codes
+ * 8 x u16 DC accumulated addresses (packed pairs)
+ * 16 x u16 AC min-codes
+ * 8 x u16 AC accumulated addresses (packed pairs)
+ *
+ * Per-set layout in the value buffer (192 bytes):
+ * 16 bytes DC code values
+ * 176 bytes AC code values
+ */
+static int vdpu720_write_htbl(struct hantro_ctx *ctx,
+ const struct v4l2_jpeg_header *hdr)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ struct hantro_jpeg_dec_hw_ctx *jpeg_ctx = jpeg_dec_ctx(ctx);
+ const struct v4l2_jpeg_scan_header *scan = hdr->scan;
+ u8 *tbl_base = jpeg_ctx->table_base.cpu;
+ u16 *p_mincode = (u16 *)(tbl_base + VDPU720_HMINCODE_OFF);
+ u8 *p_value = tbl_base + VDPU720_HVALUE_OFF;
+ unsigned int nb_sets = vdpu720_nb_htbl_sets(scan->num_components);
+ unsigned int k, i;
+
+ /* The last set is shared by every remaining component */
+ for (k = nb_sets; k < scan->num_components; k++) {
+ if (scan->component[k].dc_entropy_coding_table_selector !=
+ scan->component[nb_sets - 1].dc_entropy_coding_table_selector ||
+ scan->component[k].ac_entropy_coding_table_selector !=
+ scan->component[nb_sets - 1].ac_entropy_coding_table_selector) {
+ dev_err_ratelimited(vpu->dev,
+ "JPEG component %u uses other Huffman tables than component %u\n",
+ k, nb_sets - 1);
+ return -EINVAL;
+ }
+ }
+
+ for (k = 0; k < nb_sets; k++) {
+ u8 dc_sel = scan->component[k].dc_entropy_coding_table_selector;
+ u8 ac_sel = scan->component[k].ac_entropy_coding_table_selector;
+ u8 dc_bits[16], ac_bits[16];
+ const u8 *dc_src, *ac_src, *dc_vals, *ac_vals;
+ unsigned int dc_huffval_len, ac_huffval_len;
+ u16 min_dc[16], acc_dc[16];
+ u16 min_ac[16], acc_ac[16];
+
+ /*
+ * v4l2_jpeg_parse_header() filled huffman_tables[] indexed by
+ * (Tc << 1) | Th - Tc=0/1 (DC/AC) class, Th=0/1 (luma/chroma)
+ * id (packed DHT segments handled). .start points at BITS[16],
+ * already past the Tc|Th byte.
+ */
+ if (dc_sel > 1 || ac_sel > 1 ||
+ !hdr->huffman_tables[dc_sel].start ||
+ !hdr->huffman_tables[2 | ac_sel].start) {
+ dev_err(vpu->dev,
+ "H-table not found for component %u (dc=%u ac=%u)\n",
+ k, dc_sel, ac_sel);
+ return -EINVAL;
+ }
+
+ /*
+ * Layout at .start: [BITS 16B] [HUFFVAL sum(BITS)B]. Derive the
+ * HUFFVAL length from BITS (a packed segment's length would
+ * over-count when several tables share it).
+ */
+ dc_src = hdr->huffman_tables[dc_sel].start;
+ ac_src = hdr->huffman_tables[2 | ac_sel].start;
+
+ /*
+ * Bulk-copy the two BITS arrays out of the uncached source; they
+ * are otherwise walked byte-by-byte twice (the length sum here and
+ * again in vdpu720_compute_mincode()). The HUFFVAL blocks stay in
+ * the source and are copied out in one memcpy() further down.
+ */
+ memcpy(dc_bits, dc_src, sizeof(dc_bits));
+ memcpy(ac_bits, ac_src, sizeof(ac_bits));
+ dc_vals = dc_src + 16;
+ ac_vals = ac_src + 16;
+
+ dc_huffval_len = 0;
+ for (i = 0; i < 16; i++)
+ dc_huffval_len += dc_bits[i];
+ ac_huffval_len = 0;
+ for (i = 0; i < 16; i++)
+ ac_huffval_len += ac_bits[i];
+
+ /*
+ * The value block holds VDPU720_DC_VALUES_MAX DC and
+ * VDPU720_AC_VALUES_MAX AC symbols, and the accumulated
+ * addresses below are packed two per u16, so they have to stay
+ * within a byte as well. A table the hardware cannot hold
+ * would otherwise be truncated into it silently and decode to
+ * a wrong image. Both limits are covered by this check, the
+ * value block is the tighter of the two.
+ */
+ if (dc_huffval_len > VDPU720_DC_VALUES_MAX ||
+ ac_huffval_len > VDPU720_AC_VALUES_MAX) {
+ dev_err_ratelimited(vpu->dev,
+ "JPEG Huffman table too large for component %u (dc=%u ac=%u)\n",
+ k, dc_huffval_len, ac_huffval_len);
+ return -EINVAL;
+ }
+
+ vdpu720_compute_mincode(dc_bits, min_dc, acc_dc);
+ vdpu720_compute_mincode(ac_bits, min_ac, acc_ac);
+
+ for (i = 0; i < 16; i++)
+ *p_mincode++ = min_dc[i];
+ for (i = 0; i < 8; i++)
+ *p_mincode++ = (u16)acc_dc[2 * i] |
+ ((u16)acc_dc[2 * i + 1] << 8);
+ for (i = 0; i < 16; i++)
+ *p_mincode++ = min_ac[i];
+ for (i = 0; i < 8; i++)
+ *p_mincode++ = (u16)acc_ac[2 * i] |
+ ((u16)acc_ac[2 * i + 1] << 8);
+
+ /* Zero-pad the value block, then fill DC then AC values. */
+ memset(p_value, 0, VDPU720_HVALUE_SET_SIZE);
+ memcpy(p_value, dc_vals, dc_huffval_len);
+ memcpy(p_value + VDPU720_DC_VALUES_MAX, ac_vals, ac_huffval_len);
+ p_value += VDPU720_HVALUE_SET_SIZE;
+ }
+
+ return 0;
+}
+
+/*
+ * vdpu720_fill_regs - program the 42 VPU720 decoder registers.
+ *
+ * Called with already-parsed header and DMA addresses of the source
+ * bitstream and the destination NV12 buffer.
+ */
+static int vdpu720_fill_regs(struct hantro_ctx *ctx,
+ const struct v4l2_jpeg_header *hdr,
+ dma_addr_t tbl_dma,
+ dma_addr_t strm_dma, u32 strm_start_byte,
+ u32 strm_len_blks,
+ dma_addr_t out_dma)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ /*
+ * Use negotiated buffer dimensions for stride/vstride so the NV12 UV
+ * plane lands at the correct offset in the allocated buffer.
+ *
+ * For PIC_SIZE, use the MCU-boundary-aligned height rather than the
+ * raw JPEG header height. The VPU720 computes its vertical MCU count
+ * as floor(PIC_H / mcu_height). For YUV420/YUV440 (mcu_height=16),
+ * a 1080-pixel-high image yields floor(1080/16)=67 MCUs, but the JPEG
+ * encoder always writes ceil(1080/16)=68 complete MCUs (1088 rows of
+ * entropy data). Using PIC_H=1080 therefore causes the hardware to
+ * stop after row 1072, leaving the bottom 8 rows unwritten and
+ * potentially triggering decode errors from unread bitstream bytes.
+ *
+ * Using ALIGN(jpeg_height, mcu_height) is always safe: the encoder
+ * writes exactly ceil(height/mcu_height) MCUs, so the aligned height
+ * matches the actual entropy data in the bitstream. For modes where
+ * jpeg_height is already on an MCU boundary (all 8-pixel-MCU modes at
+ * standard resolutions), ALIGN() is a no-op.
+ */
+ u32 jpeg_width = hdr->frame.width;
+ u32 jpeg_height = hdr->frame.height;
+ u32 buf_width = ctx->dst_fmt.width;
+ u32 buf_height = ctx->dst_fmt.height;
+ u32 w_align = ALIGN(buf_width, 16);
+ u32 y_stride = w_align >> 4; /* units of 16 pixels */
+ u32 y_vstride = y_stride * buf_height; /* stride-units for Y plane, sets UV offset */
+ u32 nb_comp = hdr->frame.num_components;
+ /*
+ * qtbl_sel = number of Q-table entries written to the side buffer,
+ * one per component. Using hdr->num_dqt (segment count) is wrong
+ * when a camera packs all Q-tables into a single DQT segment
+ * (num_dqt=1) while there are 3 components - the hardware would
+ * only read 1 Q-table, leaving Cb/Cr with garbage → corrupted
+ * chroma and the "80% height cut" visual artifact.
+ */
+ u32 qtbl_sel = nb_comp;
+ /*
+ * H-table sets: one for grayscale (luma only), two for colour (luma +
+ * chroma). The three table lengths below are computed from these two
+ * counts and the side buffer layout, so what is programmed is what
+ * vdpu720_write_qtbl() and vdpu720_write_htbl() actually wrote.
+ */
+ u32 htbl_sel = vdpu720_nb_htbl_sets(nb_comp);
+ u32 mcu_width, mcu_height, jpeg_height_aligned;
+ u32 qtbl_len, hmin_len, hval_len;
+ int jpeg_mode;
+ u32 reg;
+
+ jpeg_mode = vdpu720_jpeg_mode(&hdr->frame);
+ if (jpeg_mode < 0) {
+ dev_err_ratelimited(vpu->dev,
+ "unsupported JPEG sampling factors %ux%u\n",
+ hdr->frame.component[0].horizontal_sampling_factor,
+ hdr->frame.component[0].vertical_sampling_factor);
+ return jpeg_mode;
+ }
+
+ /*
+ * MCU size follows the luma sampling factors, MCU_W = h0 * 8 and
+ * MCU_H = v0 * 8. YUV420 and YUV440 subsample the luma vertically by
+ * 2, giving MCU_H = 16; YUV420 and YUV422 subsample it horizontally
+ * by 2 and YUV411 by 4, giving MCU_W = 16 and 32. The rest is 8.
+ */
+ mcu_height = (jpeg_mode == VDPU720_JPEG_MODE_YUV420 ||
+ jpeg_mode == VDPU720_JPEG_MODE_YUV440) ? 16 : 8;
+ mcu_width = (jpeg_mode == VDPU720_JPEG_MODE_YUV411) ? 32 :
+ (jpeg_mode == VDPU720_JPEG_MODE_YUV420 ||
+ jpeg_mode == VDPU720_JPEG_MODE_YUV422) ? 16 : 8;
+ jpeg_height_aligned = ALIGN(jpeg_height, mcu_height);
+
+ /*
+ * The picture dimensions below are taken from the bitstream while the
+ * strides are taken from the negotiated capture format. A frame that
+ * is larger than what was negotiated would make the decoder write
+ * beyond the capture buffer, so refuse it rather than program the
+ * hardware with the two sets of numbers mixed.
+ */
+ if (jpeg_width > buf_width || jpeg_height_aligned > buf_height) {
+ dev_err_ratelimited(vpu->dev,
+ "JPEG %ux%u does not fit the negotiated %ux%u\n",
+ jpeg_width, jpeg_height_aligned, buf_width, buf_height);
+ return -EINVAL;
+ }
+
+ /*
+ * REG2: system config – always output NV12.
+ *
+ * FILL_DOWN_E belongs to the NV12 conversion rather than to a
+ * particular pair of heights: the output chroma is vertically
+ * subsampled, so the hardware has to complete the bottom of the
+ * picture. Two cases need it:
+ *
+ * a) jpeg_height is not on an MCU boundary (e.g. YUV420 1080p:
+ * jpeg_height=1080, jpeg_height_aligned=1088, buf_height=1088).
+ * The VPU720 needs FILL_DOWN_E to complete the last MCU row's
+ * chroma reconstruction. Without it the bottom rows are corrupt
+ * even though PIC_H is already set to the MCU-aligned height.
+ *
+ * b) MCU-aligned height < buf_height (e.g. YUV422 1080p: mcu_h=8,
+ * jpeg_height_aligned=1080, buf_height=1088). The hardware fills
+ * rows 1080..1087 by repeating the last valid row.
+ *
+ * A height that is a multiple of 16 leaves nothing to fill, but the
+ * bit is set there as well: the reference driver enables it for every
+ * NV12 conversion, whatever the picture and buffer heights are.
+ *
+ * FILL_RIGHT_E does the same for the right hand edge, but there it
+ * depends on the mode. The decoder writes whole MCUs, so the last
+ * MCU column ends at ALIGN(jpeg_width, mcu_width) while the buffer is
+ * 16 pixel aligned. Only the 8 pixel MCU widths can stop short of
+ * that and need the columns in between filled; a 16 or 32 pixel MCU
+ * already reaches at least as far. The reference driver arrives at
+ * the same set through a per mode test on (width & 0xf) <= 8.
+ */
+ reg = FIELD_PREP(VDPU720_YUV_OUT_FMT, VDPU720_YUV_OUT_FMT_NV12) |
+ VDPU720_FILL_DOWN_E;
+ if (ALIGN(jpeg_width, mcu_width) < ALIGN(jpeg_width, 16))
+ reg |= VDPU720_FILL_RIGHT_E;
+ vdpu_write_relaxed(vpu, reg, VDPU720_REG_SYS);
+
+ /*
+ * --- REG3: picture dimensions ---
+ *
+ * PIC_W stays at the raw header width while PIC_H is rounded up to
+ * the MCU boundary. Rounding the width up the same way is not safe:
+ * mcu_width reaches 32 for YUV411, so ALIGN(jpeg_width, mcu_width)
+ * can land beyond the 16 pixel aligned buffer width and point the
+ * decoder past the end of a row. FILL_RIGHT_E above covers the cases
+ * where the MCU column stops short instead. The reference driver
+ * programs the raw width here too.
+ */
+ vdpu_write_relaxed(vpu,
+ FIELD_PREP(VDPU720_PIC_W_M1, jpeg_width - 1) |
+ FIELD_PREP(VDPU720_PIC_H_M1, jpeg_height_aligned - 1),
+ VDPU720_REG_PIC_SIZE);
+
+ /* --- REG4: JPEG format, Q/H table counts, restart interval --- */
+ qtbl_len = VDPU720_TBL_LEN(qtbl_sel * VDPU720_QTBL_COMP_SIZE);
+ hmin_len = VDPU720_TBL_LEN(htbl_sel * VDPU720_HMINCODE_SET_SIZE);
+ hval_len = VDPU720_TBL_LEN(htbl_sel * VDPU720_HVALUE_SET_SIZE);
+
+ reg = FIELD_PREP(VDPU720_JPEG_MODE, jpeg_mode) |
+ FIELD_PREP(VDPU720_PIX_DEPTH, VDPU720_PIX_DEPTH_8) |
+ FIELD_PREP(VDPU720_QTBL_SEL, qtbl_sel) |
+ FIELD_PREP(VDPU720_HTBL_SEL, htbl_sel);
+ if (hdr->restart_interval) {
+ reg |= VDPU720_DRI_E;
+ reg |= FIELD_PREP(VDPU720_DRI_MCU_M1,
+ hdr->restart_interval - 1);
+ }
+ vdpu_write_relaxed(vpu, reg, VDPU720_REG_PIC_FMT);
+
+ /* --- REG5: horizontal virtual strides --- */
+ vdpu_write_relaxed(vpu,
+ FIELD_PREP(VDPU720_Y_HOR_STRIDE, y_stride) |
+ FIELD_PREP(VDPU720_UV_HOR_STRIDE, y_stride),
+ VDPU720_REG_HOR_STRIDE);
+
+ /* --- REG6: total Y-plane size (stride-units * height) --- */
+ vdpu_write_relaxed(vpu, FIELD_PREP(VDPU720_Y_VSTRIDE, y_vstride),
+ VDPU720_REG_Y_VSTRIDE);
+
+ /* --- REG7: table lengths + high stride bit --- */
+ reg = FIELD_PREP(VDPU720_QTBL_LEN, qtbl_len) |
+ FIELD_PREP(VDPU720_HTBL_MINCODE_LEN, hmin_len) |
+ FIELD_PREP(VDPU720_HTBL_VALUE_LEN, hval_len) |
+ FIELD_PREP(VDPU720_Y_HOR_STRIDE_H, y_stride >> 16);
+ vdpu_write_relaxed(vpu, reg, VDPU720_REG_TBL_LEN);
+
+ /* --- REG8: stream length and start byte --- */
+ vdpu_write_relaxed(vpu,
+ FIELD_PREP(VDPU720_STRM_START_BYTE, strm_start_byte) |
+ FIELD_PREP(VDPU720_STRM_LEN, strm_len_blks),
+ VDPU720_REG_STRM_LEN);
+
+ /* --- REG9-REG11: Q/H table DMA addresses (side buffer) --- */
+ hantro_write_addr(vpu, VDPU720_REG_QTBL_BASE, tbl_dma + 0);
+ hantro_write_addr(vpu, VDPU720_REG_HTBL_MINCODE,
+ tbl_dma + VDPU720_HMINCODE_OFF);
+ hantro_write_addr(vpu, VDPU720_REG_HTBL_VALUE,
+ tbl_dma + VDPU720_HVALUE_OFF);
+
+ /* --- REG12: stream base (16-byte aligned) --- */
+ hantro_write_addr(vpu, VDPU720_REG_STRM_BASE, strm_dma);
+
+ /* --- REG13: NV12 output buffer --- */
+ hantro_write_addr(vpu, VDPU720_REG_OUT_BASE, out_dma);
+
+ /* --- REG14: stream error handling defaults --- */
+ vdpu_write_relaxed(vpu, VDPU720_STRM_ERR_DFLT, VDPU720_REG_STRM_ERR);
+
+ /* --- REG16: enable all internal clock gates --- */
+ vdpu_write_relaxed(vpu, VDPU720_CLK_GATE_ALL, VDPU720_REG_CLK_GATE);
+
+ /* --- REG30: AXI performance counter --- */
+ vdpu_write_relaxed(vpu,
+ VDPU720_PERF_WORK_E | VDPU720_PERF_CLR_E |
+ VDPU720_PERF_CNT_TYPE |
+ FIELD_PREP(VDPU720_PERF_RD_LAT_ID, 0xa),
+ VDPU720_REG_PERF_CTRL);
+
+ return 0;
+}
+
+/*
+ * vdpu720_fill_chroma - write neutral chroma for a grayscale frame.
+ *
+ * The output format converter has no YUV400 path: VDPU720_YUV_OUT_FMT_NV12
+ * only covers the subsampled colour modes, and for a single component frame
+ * the hardware writes the luma plane and leaves the chroma plane untouched.
+ *
+ * Fill the plane here, before the hardware is started: once the decode is
+ * running the interrupt can complete the job and hand the buffer to
+ * userspace at any time.
+ */
+static int vdpu720_fill_chroma(struct hantro_ctx *ctx,
+ struct vb2_v4l2_buffer *dst_buf)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ u32 y_size = ctx->dst_fmt.plane_fmt[0].bytesperline *
+ ctx->dst_fmt.height;
+ u32 size = ctx->dst_fmt.plane_fmt[0].sizeimage;
+ void *dst_cpu;
+
+ /* Available because variant->dst_needs_kmap */
+ dst_cpu = vb2_plane_vaddr(&dst_buf->vb2_buf, 0);
+ if (!dst_cpu) {
+ dev_err(vpu->dev,
+ "JPEG capture buffer has no kernel mapping\n");
+ return -EINVAL;
+ }
+
+ memset(dst_cpu + y_size, 0x80, size - y_size);
+
+ return 0;
+}
+
+/**
+ * rockchip_vpu720_jpeg_dec_init() - allocate the per-context DMA side buffer
+ * @ctx: context to allocate the Q/Huffman table buffer for
+ *
+ * Return: 0 on success, -ENOMEM if the buffer could not be allocated.
+ */
+int rockchip_vpu720_jpeg_dec_init(struct hantro_ctx *ctx)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ struct hantro_jpeg_dec_hw_ctx *jpeg_ctx = jpeg_dec_ctx(ctx);
+
+ jpeg_ctx->table_base.size = VDPU720_TABLE_BUF_SIZE;
+ jpeg_ctx->table_base.cpu =
+ dma_alloc_noncoherent(vpu->dev,
+ jpeg_ctx->table_base.size,
+ &jpeg_ctx->table_base.dma,
+ DMA_TO_DEVICE, GFP_KERNEL);
+ if (!jpeg_ctx->table_base.cpu)
+ return -ENOMEM;
+
+ return 0;
+}
+
+/**
+ * rockchip_vpu720_jpeg_dec_exit() - free the per-context DMA side buffer
+ * @ctx: context the buffer belongs to
+ */
+void rockchip_vpu720_jpeg_dec_exit(struct hantro_ctx *ctx)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ struct hantro_jpeg_dec_hw_ctx *jpeg_ctx = jpeg_dec_ctx(ctx);
+
+ if (jpeg_ctx->table_base.cpu) {
+ dma_free_noncoherent(vpu->dev,
+ jpeg_ctx->table_base.size,
+ jpeg_ctx->table_base.cpu,
+ jpeg_ctx->table_base.dma,
+ DMA_TO_DEVICE);
+ jpeg_ctx->table_base.cpu = NULL;
+ }
+}
+
+/**
+ * rockchip_vpu720_jpeg_dec_run() - parse the header, fill the side buffer,
+ * program the registers and start the hardware
+ * @ctx: context holding the queues and the side buffer
+ *
+ * The source queue carries the JPEG bitstream; the destination queue
+ * carries the NV12 output buffer. Both addresses are in IOVA space
+ * managed by the device's IOMMU.
+ *
+ * Return: 0 with the hardware started, or a negative errno for a frame that
+ * cannot be decoded, in which case device_run() finishes the job.
+ */
+int rockchip_vpu720_jpeg_dec_run(struct hantro_ctx *ctx)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ struct hantro_jpeg_dec_hw_ctx *jpeg_ctx = jpeg_dec_ctx(ctx);
+ struct vb2_v4l2_buffer *src_buf, *dst_buf;
+ struct v4l2_jpeg_scan_header scan_header;
+ struct v4l2_jpeg_reference quantization_tables[4] = { };
+ struct v4l2_jpeg_reference huffman_tables[4] = { };
+ struct v4l2_jpeg_header hdr = {
+ .scan = &scan_header,
+ .quantization_tables = quantization_tables,
+ .huffman_tables = huffman_tables,
+ };
+ void *src_cpu;
+ dma_addr_t src_dma, dst_dma;
+ u8 tail[64];
+ u32 tail_len, i;
+ u32 src_len;
+ u32 hw_strm_off, scan_start, strm_start_byte, strm_len_blks, data_off;
+ u32 strm_off, strm_end;
+ int ret;
+
+ hantro_start_prepare_run(ctx);
+
+ src_buf = hantro_get_src_buf(ctx);
+ dst_buf = hantro_get_dst_buf(ctx);
+
+ src_cpu = vb2_plane_vaddr(&src_buf->vb2_buf, 0);
+ src_len = vb2_get_plane_payload(&src_buf->vb2_buf, 0);
+ src_dma = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0);
+ dst_dma = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0);
+
+ if (!src_cpu) {
+ dev_err(vpu->dev, "JPEG source buffer has no kernel mapping\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ data_off = src_buf->vb2_buf.planes[0].data_offset;
+
+ src_cpu += data_off;
+ src_len -= data_off;
+
+ if (src_len < 4) {
+ vpu_debug(1, "VPU720: skipping short JPEG buffer (src_len=%u)\n",
+ src_len);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /*
+ * Parse the JPEG header directly from the source buffer to support
+ * JFIF 1.02 with embedded thumbnails (up to ~60KB in APP0 segment).
+ * The downstream reference and hardware manual specify parsing the
+ * full stream header before writing tables to external memory.
+ */
+ ret = v4l2_jpeg_parse_header(src_cpu, src_len, &hdr);
+ if (ret < 0) {
+ /*
+ * Log first 8 bytes of the buffer for diagnostics: empty buffer
+ * (0x00...), wrong start (not 0xFF 0xD8), or malformed JPEG.
+ */
+ dev_warn_ratelimited(vpu->dev,
+ "failed to parse JPEG header: %d (src_len=%u first_bytes=%*ph)\n",
+ ret, src_len, min_t(int, src_len, 8), src_cpu);
+ goto err;
+ }
+
+ /*
+ * v4l2_jpeg_parse_header() returns as soon as it reaches the SOS
+ * marker and never looks at the entropy coded data behind it, so a
+ * JPEG that got truncated because it did not fit into the source
+ * buffer parses without an error. The hardware would decode as many
+ * MCUs as it finds and hand out a half filled frame, which is not
+ * distinguishable from a good one.
+ *
+ * Look for the EOI marker near the end of the payload. It cannot
+ * appear within the entropy coded data itself as 0xff bytes are
+ * stuffed there, so finding it means the frame is complete.
+ *
+ * Only the last few bytes are searched, which covers a frame size
+ * padded up to a 64 byte boundary and any short trailer behind the
+ * image. Looking further back is not worth it: the source buffer is
+ * uncached, so a walk over the whole payload would cost one bus
+ * transaction per byte, and a payload ending far behind its EOI is
+ * one whose bytesused was never set - in which case videobuf2
+ * substitutes the full plane length and a recycled buffer holds the
+ * previous frame's bytes back there anyway. The tail is copied out
+ * in one memcpy() for the same reason.
+ */
+ tail_len = min_t(u32, src_len, sizeof(tail));
+ memcpy(tail, src_cpu + src_len - tail_len, tail_len);
+
+ for (i = 0; i + 1 < tail_len; i++)
+ if (tail[i] == 0xff && tail[i + 1] == 0xd9)
+ break;
+
+ if (i + 1 >= tail_len) {
+ dev_err_ratelimited(vpu->dev,
+ "truncated JPEG, no EOI at the end of the %u byte payload (buffer too small?)\n",
+ src_len);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /*
+ * v4l2_jpeg_parse_header() accepts twelve bit samples for SOF1, but
+ * vdpu720_fill_regs() always programs VDPU720_PIX_DEPTH_8.
+ */
+ if (hdr.frame.precision != 8) {
+ dev_err_ratelimited(vpu->dev,
+ "unsupported JPEG sample precision %u\n",
+ hdr.frame.precision);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /*
+ * Only a single component frame and the three component layouts
+ * vdpu720_jpeg_mode() maps are decodable. It derives the mode from
+ * the luma sampling factors alone, so a two component frame would
+ * come back as one of the three component modes and the hardware
+ * would go looking for a chroma plane that is not in the bitstream.
+ * The table lengths in vdpu720_fill_regs() assume the same two cases.
+ */
+ if (hdr.frame.num_components != 1 &&
+ hdr.frame.num_components != VDPU720_NB_COMPONENTS) {
+ dev_err_ratelimited(vpu->dev,
+ "unsupported JPEG component count %u\n",
+ hdr.frame.num_components);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /*
+ * The decoder runs the whole frame in one go and vdpu720_fill_regs()
+ * sizes the table registers from the frame, while vdpu720_write_htbl()
+ * fills the side buffer from the scan. A non interleaved frame, whose
+ * first scan carries a single component, would leave the rest of the
+ * side buffer zeroed.
+ */
+ if (scan_header.num_components != hdr.frame.num_components) {
+ dev_err_ratelimited(vpu->dev,
+ "JPEG scan covers %u of %u components, non interleaved scans are not supported\n",
+ scan_header.num_components, hdr.frame.num_components);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ scan_start = hdr.ecs_offset;
+ if (scan_start >= src_len) {
+ dev_err(vpu->dev, "JPEG ECS offset beyond buffer bounds\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /*
+ * Rebuild the Q/H-table side buffer every frame. table_base is a cached
+ * (dma_alloc_noncoherent) buffer and write_qtbl()/write_htbl() stage the
+ * source tables through cached stack buffers, so the build stays on
+ * cached memory (~15us); dma_sync_single_for_device() then flushes it to
+ * DRAM before the hardware reads it.
+ */
+ memset(jpeg_ctx->table_base.cpu, 0, jpeg_ctx->table_base.size);
+
+ ret = vdpu720_write_qtbl(ctx, &hdr);
+ if (ret)
+ goto err;
+
+ ret = vdpu720_write_htbl(ctx, &hdr);
+ if (ret)
+ goto err;
+
+ dma_sync_single_for_device(vpu->dev, jpeg_ctx->table_base.dma,
+ jpeg_ctx->table_base.size, DMA_TO_DEVICE);
+
+ /*
+ * The stream register must be 16-byte aligned. Round down to the
+ * nearest 16-byte boundary and record the sub-block start byte.
+ *
+ * Both are taken from the start of the plane rather than from
+ * src_cpu. data_offset is set by userspace in VIDIOC_QBUF and
+ * videobuf2 only rejects it when it is not smaller than bytesused,
+ * so it carries arbitrary low bits. Splitting a src_dma that
+ * already includes it would leave STRM_BASE unaligned by those bits
+ * with no way to encode them, and the hardware would start reading
+ * from the wrong offset.
+ */
+ strm_off = data_off + scan_start;
+ strm_end = data_off + src_len;
+ hw_strm_off = strm_off & ~0xfU;
+ strm_start_byte = strm_off & 0xfU;
+ strm_len_blks = (ALIGN(strm_end - hw_strm_off, 16) - 1) >> 4;
+
+ ret = vdpu720_fill_regs(ctx, &hdr,
+ jpeg_ctx->table_base.dma,
+ src_dma + hw_strm_off, strm_start_byte,
+ strm_len_blks,
+ dst_dma);
+ if (ret)
+ goto err;
+
+ if (hdr.frame.num_components == 1) {
+ ret = vdpu720_fill_chroma(ctx, dst_buf);
+ if (ret)
+ goto err;
+ }
+
+ hantro_end_prepare_run(ctx, 0);
+
+ vdpu_write(vpu,
+ VDPU720_DEC_E | VDPU720_TIMEOUT_E,
+ VDPU720_REG_INT);
+
+ return 0;
+
+err:
+ hantro_end_prepare_run(ctx, ret);
+
+ return ret;
+}
+
+static int vdpu720_soft_reset(struct hantro_dev *vpu)
+{
+ u32 status;
+ int ret;
+
+ /*
+ * If the decoder is idle (DEC_E=0), set FORCE_SOFTRESET_VALID
+ * before triggering the soft reset, per downstream BSP behaviour.
+ */
+ status = vdpu_read(vpu, VDPU720_REG_INT);
+ if (!(status & VDPU720_DEC_E))
+ vdpu_write(vpu, VDPU720_FORCE_SOFTRST, VDPU720_REG_SYS);
+
+ vdpu_write(vpu, status | VDPU720_SOFT_RST_EN, VDPU720_REG_INT);
+
+ ret = readl_relaxed_poll_timeout(vpu->dec_base + VDPU720_REG_INT,
+ status,
+ status & VDPU720_SOFT_RST_RDY,
+ 5, 10000);
+ if (ret)
+ dev_warn(vpu->dev, "VPU720 soft reset timed out\n");
+
+ return ret;
+}
+
+/*
+ * vdpu720_hard_reset - pulse the block's reset lines.
+ *
+ * reset_control_reset() is not usable here. The lines come from the RK3588
+ * CRU, and rockchip_softrst_ops in drivers/clk/rockchip/softrst.c implements
+ * only .assert and .deassert, so reset_control_reset() returns -ENOTSUPP
+ * without touching the hardware. Drive the pulse by hand instead.
+ *
+ * Only reached from hantro_watchdog(), which runs from a workqueue, so
+ * sleeping between the two halves is fine.
+ */
+static int vdpu720_hard_reset(struct hantro_dev *vpu)
+{
+ int ret;
+
+ ret = reset_control_assert(vpu->resets);
+ if (ret)
+ return ret;
+
+ usleep_range(10, 20);
+
+ return reset_control_deassert(vpu->resets);
+}
+
+/**
+ * rockchip_vpu720_reset() - error recovery reset called by the watchdog
+ * @ctx: context whose job timed out
+ *
+ * Try a soft reset first; fall back to a full hardware reset (assert +
+ * deassert all resets) if the soft reset does not complete.
+ */
+void rockchip_vpu720_reset(struct hantro_ctx *ctx)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ int ret;
+
+ ret = vdpu720_soft_reset(vpu);
+ if (ret) {
+ dev_warn(vpu->dev,
+ "VPU720 falling back to hard reset\n");
+
+ ret = vdpu720_hard_reset(vpu);
+ if (ret)
+ dev_err(vpu->dev,
+ "VPU720 hard reset failed: %d\n", ret);
+ }
+
+ vdpu_write(vpu, 0, VDPU720_REG_INT);
+}
+
+irqreturn_t rockchip_vpu720_irq(int irq, void *dev_id)
+{
+ struct hantro_dev *vpu = dev_id;
+ enum vb2_buffer_state state;
+ u32 status, clr_mask;
+
+ status = vdpu_read(vpu, VDPU720_REG_INT);
+
+ /*
+ * VPU720-specific two-phase IRQ clear.
+ * Write back a masked subset of status bits before checking
+ * IRQ_RAW, as required by the VPU720 hardware.
+ */
+ clr_mask = (~(VDPU720_IRQ_CLR_COND & status)) &
+ (VDPU720_IRQ_CLR_KEEP & status);
+ vdpu_write(vpu, clr_mask, VDPU720_REG_INT);
+
+ if (!(status & VDPU720_IRQ_RAW))
+ return IRQ_NONE;
+
+ /* Fully clear IRQ */
+ vdpu_write(vpu, 0, VDPU720_REG_INT);
+
+ state = (status & VDPU720_ERR_MASK) ?
+ VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE;
+
+ if (status & VDPU720_DEC_ERR) {
+ u32 mcu_pos = vdpu_read(vpu, VDPU720_REG_DBG_MCU_POS);
+ u32 err_info = vdpu_read(vpu, VDPU720_REG_DBG_ERROR);
+
+ dev_warn_ratelimited(vpu->dev,
+ "VPU720 decode error: MCU pos=(%u,%u) flags=0x%04x [%s%s%s%s%s%s%s%s%s%s] first_idx=%u\n",
+ (u32)FIELD_GET(VDPU720_DBG_MCU_POS_X, mcu_pos),
+ (u32)FIELD_GET(VDPU720_DBG_MCU_POS_Y, mcu_pos),
+ (u32)FIELD_GET(VDPU720_DERR_FLAGS, err_info),
+ (err_info & VDPU720_DERR_DRI_SEQ) ? "dri_seq " : "",
+ (err_info & VDPU720_DERR_STREAM_FFFF) ? "ffff " : "",
+ (err_info & VDPU720_DERR_OTHER_MARK) ? "bad_mark " : "",
+ (err_info & VDPU720_DERR_MCU_CNT_L) ? "dri_early " : "",
+ (err_info & VDPU720_DERR_MCU_CNT_M) ? "dri_late " : "",
+ (err_info & VDPU720_DERR_EOI_NO_END) ? "eoi_early " : "",
+ (err_info & VDPU720_DERR_END_NO_EOI) ? "no_eoi " : "",
+ (err_info & VDPU720_DERR_OVERFLOW) ? "overflow " : "",
+ (err_info & VDPU720_DERR_HUFF_EMPTY) ? "huff_empty " : "",
+ (err_info & (VDPU720_DERR_STREAM_R0 |
+ VDPU720_DERR_STREAM_R1)) ? "stream_mark " : "",
+ (u32)FIELD_GET(VDPU720_DERR_FIRST_IDX, err_info));
+
+ /* Clear the sticky error flags so they don't bleed into the next frame */
+ vdpu_write(vpu, err_info, VDPU720_REG_DBG_ERROR);
+ }
+
+ hantro_irq_done(vpu, state);
+
+ return IRQ_HANDLED;
+}
diff --git a/drivers/media/platform/verisilicon/rockchip_vpu720_regs.h b/drivers/media/platform/verisilicon/rockchip_vpu720_regs.h
new file mode 100644
index 0000000000000..cae9052126be1
--- /dev/null
+++ b/drivers/media/platform/verisilicon/rockchip_vpu720_regs.h
@@ -0,0 +1,261 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Rockchip VPU720 JPEG decoder register definitions
+ *
+ * Derived from downstream Rockchip MPP HAL (hal_jpegd_rkv_reg.h).
+ * Copyright (C) 2020 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2026 WolfVision GmbH
+ */
+#ifndef ROCKCHIP_VPU720_REGS_H_
+#define ROCKCHIP_VPU720_REGS_H_
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/align.h>
+#include <linux/types.h>
+
+/* ------------------------------------------------------------------ */
+/* Register byte offsets from dec_base */
+/* ------------------------------------------------------------------ */
+
+/* REG0: IP version / product ID */
+#define VDPU720_REG_VERSION 0x000
+#define VDPU720_PROD_NUM GENMASK(31, 16)
+#define VDPU720_BIT_DEPTH BIT(8)
+
+/* REG1: Interrupt control and status */
+#define VDPU720_REG_INT 0x004
+#define VDPU720_DEC_E BIT(0)
+#define VDPU720_IRQ_DIS BIT(1)
+#define VDPU720_TIMEOUT_E BIT(2)
+#define VDPU720_BUF_EMPTY_E BIT(3)
+#define VDPU720_BUF_EMPTY_RELOAD BIT(4)
+#define VDPU720_SOFT_RST_EN BIT(5)
+#define VDPU720_IRQ_RAW BIT(6)
+#define VDPU720_WAIT_RESET_E BIT(7)
+#define VDPU720_IRQ BIT(8)
+#define VDPU720_DEC_RDY BIT(9)
+#define VDPU720_BUS_ERR BIT(10)
+#define VDPU720_DEC_ERR BIT(11)
+#define VDPU720_TIMEOUT BIT(12)
+#define VDPU720_BUF_EMPTY BIT(13)
+#define VDPU720_SOFT_RST_RDY BIT(14)
+
+/* Error status bits used to decide whether a hardware reset is needed */
+#define VDPU720_ERR_MASK (VDPU720_BUS_ERR | VDPU720_DEC_ERR | \
+ VDPU720_TIMEOUT | VDPU720_BUF_EMPTY)
+
+/*
+ * VPU720-specific IRQ clear mask.
+ *
+ * The VPU720 requires a two-step IRQ acknowledgment before checking
+ * VDPU720_IRQ_RAW. Compute the masked value and write it back, then
+ * write 0 to fully clear. The downstream BSP uses:
+ *
+ * clr = (~(0x00fe7f40 & status)) & (0xff0180bf & status)
+ *
+ * Preserve "status" bits from 0xff0180bf while clearing only those
+ * bits NOT already set in 0x00fe7f40.
+ */
+#define VDPU720_IRQ_CLR_KEEP 0xff0180bf
+#define VDPU720_IRQ_CLR_COND 0x00fe7f40
+
+/* REG2: System configuration */
+#define VDPU720_REG_SYS 0x008
+#define VDPU720_FORCE_SOFTRST BIT(17) /* set when dec_e=0 before soft-reset */
+#define VDPU720_FILL_DOWN_E BIT(24) /* fill bottom padding rows */
+#define VDPU720_FILL_RIGHT_E BIT(25)
+#define VDPU720_OUT_SEQ BIT(26) /* 0=raster, 1=tile */
+#define VDPU720_YUV_OUT_FMT GENMASK(29, 27)
+#define VDPU720_YUV_OUT_FMT_NATIVE 0 /* no format conversion */
+#define VDPU720_YUV_OUT_FMT_NV12 3 /* output as NV12 */
+
+/* REG3: Picture dimensions (width/height in pixels, minus 1) */
+#define VDPU720_REG_PIC_SIZE 0x00c
+#define VDPU720_PIC_W_M1 GENMASK(15, 0)
+#define VDPU720_PIC_H_M1 GENMASK(31, 16)
+
+/* REG4: JPEG picture format */
+#define VDPU720_REG_PIC_FMT 0x010
+#define VDPU720_JPEG_MODE GENMASK(2, 0)
+#define VDPU720_JPEG_MODE_YUV400 0
+#define VDPU720_JPEG_MODE_YUV411 1
+#define VDPU720_JPEG_MODE_YUV420 2
+#define VDPU720_JPEG_MODE_YUV422 3
+#define VDPU720_JPEG_MODE_YUV440 4
+#define VDPU720_JPEG_MODE_YUV444 5
+#define VDPU720_PIX_DEPTH GENMASK(6, 4)
+#define VDPU720_PIX_DEPTH_8 0
+#define VDPU720_PIX_DEPTH_12 1
+/* qtables_sel: number of Q-table sets (0..3) */
+#define VDPU720_QTBL_SEL GENMASK(9, 8)
+/* htables_sel: number of H-table sets (0..3) */
+#define VDPU720_HTBL_SEL GENMASK(13, 12)
+/* dri_e: restart interval enable */
+#define VDPU720_DRI_E BIT(15)
+/* dri_mcu_num_m1: restart interval MCU count minus 1 */
+#define VDPU720_DRI_MCU_M1 GENMASK(31, 16)
+
+/* REG5: Horizontal virtual stride */
+#define VDPU720_REG_HOR_STRIDE 0x014
+#define VDPU720_Y_HOR_STRIDE GENMASK(15, 0)
+#define VDPU720_UV_HOR_STRIDE GENMASK(31, 16)
+
+/* REG6: Vertical virtual stride (Y plane total) */
+#define VDPU720_REG_Y_VSTRIDE 0x018
+#define VDPU720_Y_VSTRIDE GENMASK(31, 4)
+
+/* REG7: Table and stride lengths */
+#define VDPU720_REG_TBL_LEN 0x01c
+#define VDPU720_QTBL_LEN GENMASK(4, 0)
+#define VDPU720_HTBL_MINCODE_LEN GENMASK(12, 8)
+#define VDPU720_HTBL_VALUE_LEN GENMASK(21, 16)
+/* bit 16 of the Y horizontal stride, low 16 bits live in REG5 */
+#define VDPU720_Y_HOR_STRIDE_H BIT(24)
+
+/* REG8: Stream length and start byte */
+#define VDPU720_REG_STRM_LEN 0x020
+#define VDPU720_STRM_START_BYTE GENMASK(3, 0)
+#define VDPU720_STRM_LEN GENMASK(31, 4)
+
+/* REG9-REG13: DMA buffer base addresses (all word-sized, in IOVA units) */
+#define VDPU720_REG_QTBL_BASE 0x024 /* Q-table side buffer, 64-byte aligned */
+#define VDPU720_REG_HTBL_MINCODE 0x028 /* H-mincode table, 64-byte aligned */
+#define VDPU720_REG_HTBL_VALUE 0x02c /* H-value table, 64-byte aligned */
+#define VDPU720_REG_STRM_BASE 0x030 /* JPEG entropy stream, 16-byte aligned */
+#define VDPU720_REG_OUT_BASE 0x034 /* NV12 output buffer, 64-byte aligned */
+
+/* REG14: Stream error handling */
+#define VDPU720_REG_STRM_ERR 0x038
+#define VDPU720_ERROR_PRC_MODE BIT(0)
+#define VDPU720_STRM_FFFF_ERR_MODE GENMASK(6, 5)
+#define VDPU720_STRM_OTHER_MODE GENMASK(8, 7)
+/* Recommended default: accept errors, skip 0xFFFF, skip unknown markers */
+#define VDPU720_STRM_ERR_DFLT (VDPU720_ERROR_PRC_MODE | \
+ FIELD_PREP_CONST(VDPU720_STRM_FFFF_ERR_MODE, 2) | \
+ FIELD_PREP_CONST(VDPU720_STRM_OTHER_MODE, 2))
+
+/* REG16: Clock gate (write 0xff to enable all internal clocks) */
+#define VDPU720_REG_CLK_GATE 0x040
+#define VDPU720_CLK_GATE_ALL 0xff
+
+/* REG30: AXI performance counter control */
+#define VDPU720_REG_PERF_CTRL 0x078
+#define VDPU720_PERF_WORK_E BIT(0)
+#define VDPU720_PERF_CLR_E BIT(1)
+#define VDPU720_PERF_CNT_TYPE BIT(3)
+#define VDPU720_PERF_RD_LAT_ID GENMASK(7, 4)
+
+/*
+ * REG31: performance-counter channel select.
+ *
+ * sw_ar/aw_count_id select which AXI channel the performance counters track.
+ * The driver leaves this register alone, as does the reference driver.
+ */
+#define VDPU720_REG_AXI_CFG 0x07c
+#define VDPU720_ADDR_ALIGN_TYPE GENMASK(1, 0)
+#define VDPU720_AR_CNT_ID_TYPE BIT(2) /* 1 = count sw_ar_count_id only */
+#define VDPU720_AW_CNT_ID_TYPE BIT(3) /* 1 = count sw_aw_count_id only */
+#define VDPU720_AR_COUNT_ID GENMASK(7, 4)
+#define VDPU720_AW_COUNT_ID GENMASK(11, 8)
+#define VDPU720_RD_TOTAL_BYTES_MODE BIT(12) /* 1 = count sw_ar_count_id bytes only */
+
+/* REG32: MCU position when first decode error occurred (read-only) */
+#define VDPU720_REG_DBG_MCU_POS 0x080
+#define VDPU720_DBG_MCU_POS_X GENMASK(15, 0) /* column in MCU units */
+#define VDPU720_DBG_MCU_POS_Y GENMASK(31, 16) /* row in MCU units */
+
+/*
+ * REG33: Detailed JPEG decode error flags.
+ *
+ * All bits are RW (write-to-clear); read them in the IRQ handler when
+ * VDPU720_DEC_ERR is set to identify exactly what went wrong.
+ */
+#define VDPU720_REG_DBG_ERROR 0x084
+#define VDPU720_DERR_DRI_SEQ BIT(0) /* DRI not at expected sequence */
+#define VDPU720_DERR_STREAM_R0 BIT(1) /* special marker 0 detected */
+#define VDPU720_DERR_STREAM_R1 BIT(2) /* special marker 1 detected */
+#define VDPU720_DERR_STREAM_FFFF BIT(3) /* 0xFFFF sequence in stream */
+#define VDPU720_DERR_OTHER_MARK BIT(4) /* unknown JPEG marker */
+#define VDPU720_DERR_MCU_CNT_L BIT(8) /* restart mark arrived too early */
+#define VDPU720_DERR_MCU_CNT_M BIT(9) /* restart mark arrived too late */
+#define VDPU720_DERR_EOI_NO_END BIT(10) /* EOI before frame complete */
+#define VDPU720_DERR_END_NO_EOI BIT(11) /* frame complete without EOI */
+#define VDPU720_DERR_OVERFLOW BIT(12) /* Huffman coefficient overflow */
+#define VDPU720_DERR_HUFF_EMPTY BIT(13) /* bitstream empty before EOI */
+#define VDPU720_DERR_FLAGS GENMASK(13, 0) /* all of the above */
+#define VDPU720_DERR_FIRST_IDX GENMASK(19, 16) /* index of first error */
+
+/*
+ * REG34-REG38: AXI bus performance counters.
+ *
+ * Read after decode completes (in the IRQ handler) to measure actual
+ * memory bandwidth consumed per frame. Counters are reset each frame
+ * by VDPU720_PERF_CLR_E in REG30. All registers are 32-bit read-only.
+ */
+#define VDPU720_REG_PERF_RD_MAX_LAT 0x088 /* peak read latency (clock cycles) */
+#define VDPU720_REG_PERF_RD_LAT_SAMP 0x08c /* read transactions above lat threshold */
+#define VDPU720_REG_PERF_RD_LAT_ACC 0x090 /* accumulated read latency sum */
+#define VDPU720_REG_PERF_RD_BYTES 0x094 /* total AXI read bytes this frame */
+#define VDPU720_REG_PERF_WR_BYTES 0x098 /* total AXI write bytes this frame */
+
+/* REG39: Hardware working cycle counter (counts clock cycles HW was active) */
+#define VDPU720_REG_PERF_CYCLES 0x09c
+
+/* ------------------------------------------------------------------ */
+/* Side-buffer layout for Q-tables and Huffman tables */
+/* */
+/* The VPU720 JPEG decoder reads quantisation tables and Huffman */
+/* tables from a contiguous DMA buffer with the following layout: */
+/* */
+/* [0, QTBL_SIZE): Q-table data (u16, raster-scan order) */
+/* [HMINCODE_OFF, +HMIN_SZ): Huffman mincode table */
+/* [HVALUE_OFF, +HVAL_SZ): Huffman value table */
+/* ------------------------------------------------------------------ */
+/* The Q-tables are per component, one entry each */
+#define VDPU720_NB_COMPONENTS 3
+#define VDPU720_QTBL_ENTRIES 64 /* 64 coefficients per table */
+#define VDPU720_QTBL_COMP_SIZE (VDPU720_QTBL_ENTRIES * sizeof(u16))
+#define VDPU720_QTBL_SIZE (VDPU720_QTBL_COMP_SIZE * VDPU720_NB_COMPONENTS)
+
+/*
+ * The Huffman tables are not per component. The hardware holds two sets and
+ * has no per component selector register, so the mapping is fixed: the first
+ * set is used for the luma component and the second one for both chroma
+ * components. A grayscale frame only needs the first.
+ */
+#define VDPU720_NB_HTBL_SETS 2
+
+/*
+ * Per-set mincode layout: 16 DC mincodes + 8 DC accaddr pairs +
+ * 16 AC mincodes + 8 AC accaddr pairs = 48 u16 = 96 bytes
+ */
+#define VDPU720_HMINCODE_SET_SIZE (48 * sizeof(u16))
+#define VDPU720_HMINCODE_SIZE (VDPU720_HMINCODE_SET_SIZE * VDPU720_NB_HTBL_SETS)
+#define VDPU720_HMINCODE_OFF VDPU720_QTBL_SIZE
+
+/* Per-set value layout: 16 DC values + 176 AC values = 192 bytes */
+#define VDPU720_HVALUE_SET_SIZE 192
+#define VDPU720_HVALUE_SIZE (VDPU720_HVALUE_SET_SIZE * VDPU720_NB_HTBL_SETS)
+#define VDPU720_HVALUE_OFF (VDPU720_HMINCODE_OFF + \
+ ALIGN(VDPU720_HMINCODE_SIZE, 64))
+
+#define VDPU720_TABLE_BUF_SIZE (VDPU720_HVALUE_OFF + VDPU720_HVALUE_SIZE)
+
+/*
+ * The three table length registers count 16 byte units, minus one. Derive
+ * them from the sizes above so that what is programmed always matches what
+ * the driver writes into the side buffer.
+ */
+#define VDPU720_TBL_LEN_UNIT 16
+#define VDPU720_TBL_LEN(bytes) ((bytes) / VDPU720_TBL_LEN_UNIT - 1)
+
+/*
+ * Huffman value sub-layout per set (192 bytes total):
+ * bytes [0..15]: DC code values (up to 12 valid entries)
+ * bytes [16..191]: AC code values (up to 162 valid entries)
+ */
+#define VDPU720_DC_VALUES_MAX 16
+#define VDPU720_AC_VALUES_MAX 176 /* 12*16 - 16 */
+
+#endif /* ROCKCHIP_VPU720_REGS_H_ */
diff --git a/drivers/media/platform/verisilicon/rockchip_vpu_hw.c b/drivers/media/platform/verisilicon/rockchip_vpu_hw.c
index 02673be9878e1..47197c60412c3 100644
--- a/drivers/media/platform/verisilicon/rockchip_vpu_hw.c
+++ b/drivers/media/platform/verisilicon/rockchip_vpu_hw.c
@@ -20,6 +20,21 @@
#define ROCKCHIP_VPU981_MIN_SIZE 64
+/*
+ * VPU720 JPEG decoder limits. The reference manual gives min 48x48 and
+ * max 65536x65536 with a step of 8 pixels, but the upper end of that range
+ * is not usable: a JPEG frame header cannot describe more than 65535
+ * pixels, the Y_VSTRIDE register field runs out at roughly 46340 square,
+ * and hantro_try_fmt() computes sizeimage as width * height * max_depth in
+ * 32 bits, which wraps beyond the same point. Cap the advertised size
+ * well below all three, still four times 4K in each direction.
+ *
+ * NV12 output uses MB_DIM (16) step for hardware alignment; the decode path
+ * rounds up height to the next MB boundary (FILL_DOWN) before writing.
+ */
+#define VPU720_JPEGD_MAX_SIZE 16384
+#define VPU720_JPEGD_STEP 8
+
/*
* Supported formats.
*/
@@ -816,3 +831,68 @@ const struct hantro_variant rk3588_vpu981_variant = {
.clk_names = rk3588_vpu981_vpu_clk_names,
.num_clocks = ARRAY_SIZE(rk3588_vpu981_vpu_clk_names)
};
+
+/* ------------------------------------------------------------------ */
+/* RK3588 VPU720 JPEG decoder */
+/* ------------------------------------------------------------------ */
+
+/*
+ * Capture format: NV12. The JPEG codec entry is listed last.
+ */
+static const struct hantro_fmt rk3588_vpu720_dec_fmts[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .codec_mode = HANTRO_MODE_NONE,
+ .frmsize = {
+ .min_width = FMT_MIN_WIDTH,
+ .max_width = VPU720_JPEGD_MAX_SIZE,
+ .step_width = MB_DIM,
+ .min_height = FMT_MIN_HEIGHT,
+ .max_height = VPU720_JPEGD_MAX_SIZE,
+ .step_height = MB_DIM,
+ },
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_JPEG,
+ .codec_mode = HANTRO_MODE_JPEG_DEC,
+ .max_depth = 2,
+ .frmsize = {
+ .min_width = FMT_MIN_WIDTH,
+ .max_width = VPU720_JPEGD_MAX_SIZE,
+ .step_width = VPU720_JPEGD_STEP,
+ .min_height = FMT_MIN_HEIGHT,
+ .max_height = VPU720_JPEGD_MAX_SIZE,
+ .step_height = VPU720_JPEGD_STEP,
+ },
+ },
+};
+
+static const struct hantro_codec_ops rk3588_vpu720_codec_ops[] = {
+ [HANTRO_MODE_JPEG_DEC] = {
+ .run = rockchip_vpu720_jpeg_dec_run,
+ .reset = rockchip_vpu720_reset,
+ .init = rockchip_vpu720_jpeg_dec_init,
+ .exit = rockchip_vpu720_jpeg_dec_exit,
+ },
+};
+
+static const struct hantro_irq rk3588_vpu720_irqs[] = {
+ { "vdpu", rockchip_vpu720_irq },
+};
+
+static const char * const rk3588_vpu720_clk_names[] = {
+ "aclk", "hclk",
+};
+
+const struct hantro_variant rk3588_vpu720_variant = {
+ .dec_fmts = rk3588_vpu720_dec_fmts,
+ .num_dec_fmts = ARRAY_SIZE(rk3588_vpu720_dec_fmts),
+ .codec = HANTRO_JPEG_DECODER,
+ .codec_ops = rk3588_vpu720_codec_ops,
+ .irqs = rk3588_vpu720_irqs,
+ .num_irqs = ARRAY_SIZE(rk3588_vpu720_irqs),
+ .clk_names = rk3588_vpu720_clk_names,
+ .num_clocks = ARRAY_SIZE(rk3588_vpu720_clk_names),
+ .src_needs_kmap = 1,
+ .dst_needs_kmap = 1,
+};
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 6/7] media: verisilicon: Enforce a minimum sizeimage for the JPEG decoder
2026-08-19 10:37 [PATCH 0/7] media: verisilicon: Add RK3588 VPU720 JPEG decoder Sascha Hauer
` (4 preceding siblings ...)
2026-08-19 10:37 ` [PATCH 5/7] media: verisilicon: Add Rockchip " Sascha Hauer
@ 2026-08-19 10:37 ` Sascha Hauer
2026-08-19 10:37 ` [PATCH 7/7] arm64: dts: rockchip: rk3588: Add VPU720 JPEG decoder node Sascha Hauer
6 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-08-19 10:37 UTC (permalink / raw)
To: Nicolas Dufresne, Benjamin Gaignard, Philipp Zabel,
Mauro Carvalho Chehab, Heiko Stuebner, Ezequiel Garcia,
Andrzej Pietrasiewicz, Hans Verkuil, Chen-Yu Tsai, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lucas Sinn
Cc: Mauro Carvalho Chehab, linux-media, linux-rockchip, linux-kernel,
linux-arm-kernel, devicetree, Sascha Hauer
From: Lucas Sinn <lucas.sinn@wolfvision.net>
The VPU720 JPEG decoder is driven like a stateful decoder: userspace
hands it a whole frame per source buffer and never looks inside the
bitstream, so it has no way of knowing how large a frame can get.
GStreamer picks a source buffer size of its own, which is large enough at
low resolutions and too small once the resolution grows.
A frame that does not fit is lost. There is no way to report a partially
consumed buffer, so the driver can only refuse it;
rockchip_vpu720_jpeg_dec_run() notices by finding no EOI marker at the end
of the payload and returns -EINVAL. Every frame then fails, which is a
poor way to tell an application that its buffers are too small.
For coded formats hantro_try_fmt() computes the worst case frame size and
uses it only when the application asks for zero. Use it as a lower bound
for the JPEG decoder instead, so an application that asks for less gets
buffers that can hold any frame of the negotiated resolution. The other
codecs are stateless, userspace parses the bitstream itself and knows the
frame sizes, so they keep the existing behaviour.
This is not free. max_depth is 2 for V4L2_PIX_FMT_JPEG, so a 1080p source
buffer cannot be smaller than 4 MiB and a 4K one not smaller than 16 MiB,
while a typical JPEG frame is an order of magnitude below that. The
alternative is to let userspace keep its own size and take the decode
failures, which is worse in practice.
Signed-off-by: Lucas Sinn <lucas.sinn@wolfvision.net>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/media/platform/verisilicon/hantro_v4l2.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/drivers/media/platform/verisilicon/hantro_v4l2.c b/drivers/media/platform/verisilicon/hantro_v4l2.c
index b9b1848e43b30..156d0c5453e5a 100644
--- a/drivers/media/platform/verisilicon/hantro_v4l2.c
+++ b/drivers/media/platform/verisilicon/hantro_v4l2.c
@@ -386,14 +386,26 @@ static int hantro_try_fmt(const struct hantro_ctx *ctx,
pix_mp->plane_fmt[0].sizeimage +=
hantro_av1_mv_size(pix_mp->width,
pix_mp->height);
- } else if (!pix_mp->plane_fmt[0].sizeimage) {
+ } else {
+ u32 sizeimage = fmt->header_size +
+ pix_mp->width * pix_mp->height * fmt->max_depth;
+
/*
* For coded formats the application can specify
* sizeimage. If the application passes a zero sizeimage,
* let's default to the maximum frame size.
+ *
+ * The JPEG decoder is the exception. Applications drive it
+ * without parsing the bitstream, so they cannot know how large
+ * a frame gets and pick a size that works at low resolutions
+ * and silently truncates frames further up. Treat the maximum
+ * as a minimum there.
*/
- pix_mp->plane_fmt[0].sizeimage = fmt->header_size +
- pix_mp->width * pix_mp->height * fmt->max_depth;
+ if (fmt->codec_mode == HANTRO_MODE_JPEG_DEC)
+ pix_mp->plane_fmt[0].sizeimage =
+ max(pix_mp->plane_fmt[0].sizeimage, sizeimage);
+ else if (!pix_mp->plane_fmt[0].sizeimage)
+ pix_mp->plane_fmt[0].sizeimage = sizeimage;
}
return 0;
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 7/7] arm64: dts: rockchip: rk3588: Add VPU720 JPEG decoder node
2026-08-19 10:37 [PATCH 0/7] media: verisilicon: Add RK3588 VPU720 JPEG decoder Sascha Hauer
` (5 preceding siblings ...)
2026-08-19 10:37 ` [PATCH 6/7] media: verisilicon: Enforce a minimum sizeimage for the " Sascha Hauer
@ 2026-08-19 10:37 ` Sascha Hauer
6 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-08-19 10:37 UTC (permalink / raw)
To: Nicolas Dufresne, Benjamin Gaignard, Philipp Zabel,
Mauro Carvalho Chehab, Heiko Stuebner, Ezequiel Garcia,
Andrzej Pietrasiewicz, Hans Verkuil, Chen-Yu Tsai, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lucas Sinn
Cc: Mauro Carvalho Chehab, linux-media, linux-rockchip, linux-kernel,
linux-arm-kernel, devicetree, Sascha Hauer
From: Lucas Sinn <lucas.sinn@wolfvision.net>
Add device tree node for the Rockchip VPU720 JPEG hardware decoder and
its IOMMU.
The VPU720 JPEG decoder is located at 0xfdb90000 with its MMU at
0xfdb90480. It requires ACLK and HCLK clocks, both aclk and hclk resets,
and resides in the VDPU power domain.
Both blocks are entirely on-SoC and have no board dependency, so they are
enabled unconditionally like the other codec blocks in this file.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Lucas Sinn <lucas.sinn@wolfvision.net>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
index 376ad04e07869..75fecd70d8878 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
@@ -1317,6 +1317,31 @@ rga: rga@fdb80000 {
power-domains = <&power RK3588_PD_VDPU>;
};
+ jpegd: video-codec@fdb90000 {
+ compatible = "rockchip,rk3588-vpu720";
+ reg = <0x0 0xfdb90000 0x0 0x400>;
+ interrupts = <GIC_SPI 129 IRQ_TYPE_LEVEL_HIGH 0>;
+ interrupt-names = "vdpu";
+ clocks = <&cru ACLK_JPEG_DECODER>, <&cru HCLK_JPEG_DECODER>;
+ clock-names = "aclk", "hclk";
+ assigned-clocks = <&cru ACLK_JPEG_DECODER>;
+ assigned-clock-rates = <600000000>;
+ resets = <&cru SRST_A_JPEG_DECODER>, <&cru SRST_H_JPEG_DECODER>;
+ reset-names = "axi", "ahb";
+ iommus = <&jpegd_mmu>;
+ power-domains = <&power RK3588_PD_VDPU>;
+ };
+
+ jpegd_mmu: iommu@fdb90480 {
+ compatible = "rockchip,rk3588-iommu", "rockchip,rk3568-iommu";
+ reg = <0x0 0xfdb90480 0x0 0x40>;
+ interrupts = <GIC_SPI 130 IRQ_TYPE_LEVEL_HIGH 0>;
+ clocks = <&cru ACLK_JPEG_DECODER>, <&cru HCLK_JPEG_DECODER>;
+ clock-names = "aclk", "iface";
+ power-domains = <&power RK3588_PD_VDPU>;
+ #iommu-cells = <0>;
+ };
+
vepu121_0: video-codec@fdba0000 {
compatible = "rockchip,rk3588-vepu121";
reg = <0x0 0xfdba0000 0x0 0x800>;
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH 1/7] media: verisilicon: Fix the cleanup when a codec ->run() fails
2026-08-19 10:37 ` [PATCH 1/7] media: verisilicon: Fix the cleanup when a codec ->run() fails Sascha Hauer
@ 2026-08-19 11:23 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-08-19 11:23 UTC (permalink / raw)
To: Sascha Hauer; +Cc: robh, devicetree, media-ci, conor+dt
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing calls to `hantro_end_prepare_run(ctx, error)` on early error paths in multiple codec `->run()` callbacks, leading to permanently incomplete V4L2 control requests.
- [High] Missing NULL pointer check for the return value of `vb2_plane_vaddr()` before dereferencing it during JPEG header assembly.
- [High] Missing synchronization to cancel `vpu->watchdog_work` during device removal, leading to a Use-After-Free.
- [High] Race condition between late hardware IRQ delivery and `hantro_watchdog()` execution causes the watchdog of the *next* job to be cancelled and the next job to be prematurely finished.
--
--- Patch [1]: [PATCH 1/7] media: verisilicon: Fix the cleanup when a codec ->run() fails ---
commit 67d0bec622e69de9d51450bf1c4cea20e9bb249c
Author: Sascha Hauer <s.hauer@pengutronix.de>
media: verisilicon: Fix the cleanup when a codec ->run() fails
This commit pairs hantro_start_prepare_run() and hantro_end_prepare_run()
on error paths to ensure V4L2 requests are properly completed. It adds an
error argument to hantro_end_prepare_run() so the watchdog is only armed
when the hardware actually starts, preventing unrelated jobs from being
aborted. Resources are also properly released in device_run() on failure.
Fixes: 892bb6ecead9 ("media: hantro: do a PM resume earlier")
Fixes: e2da465455ce ("media: hantro: Support VP9 on the G2 core")
Fixes: 727a400686a2 ("media: verisilicon: Add Rockchip AV1 decoder")
> diff --git a/drivers/media/platform/verisilicon/hantro_drv.c b/drivers/media/platform/verisilicon/hantro_drv.c
> index 2e81877f640fb..d9936f6979d2c 100644
> --- a/drivers/media/platform/verisilicon/hantro_drv.c
> +++ b/drivers/media/platform/verisilicon/hantro_drv.c
[ ... ]
> @@ -162,6 +179,9 @@ void hantro_end_prepare_run(struct hantro_ctx *ctx)
> v4l2_ctrl_request_complete(src_buf->vb2_buf.req_obj.req,
> &ctx->ctrl_handler);
>
> + if (error)
> + return;
> +
> /* Kick the watchdog. */
> schedule_delayed_work(&ctx->dev->watchdog_work,
> msecs_to_jiffies(2000));
[Severity: High]
This is a pre-existing issue, but does the device removal process in
hantro_remove() lack synchronization to cancel vpu->watchdog_work?
The driver schedules vpu->watchdog_work when a job starts. If a
userspace application is streaming and a timeout occurs precisely when
the device is unbound, the watchdog work might execute after the vpu
object is freed by devres. This could lead to a use-after-free since
cancel_delayed_work_sync() is not called during teardown.
[Severity: High]
This isn't a bug introduced by this patch, but can a late hardware IRQ
delivery cause a race condition with hantro_watchdog()?
If a job times out, hantro_watchdog() resets the hardware and completes
the job. The M2M framework then schedules the next job and arms its
watchdog. If the delayed hardware IRQ for the original job fires,
hantro_irq_done() targets the current context (the new job) and cancels
its watchdog. Could this cause the IRQ handler to erroneously call
hantro_job_finish() on the new job while the hardware is still processing
it, leading to state corruption and premature buffer returns?
> diff --git a/drivers/media/platform/verisilicon/hantro_g1_h264_dec.c b/drivers/media/platform/verisilicon/hantro_g1_h264_dec.c
> index ad5c1a6634f5c..30c2ac32cd0b0 100644
> --- a/drivers/media/platform/verisilicon/hantro_g1_h264_dec.c
> +++ b/drivers/media/platform/verisilicon/hantro_g1_h264_dec.c
[ ... ]
> @@ -264,7 +264,7 @@ int hantro_g1_h264_dec_run(struct hantro_ctx *ctx)
> set_ref(ctx);
> set_buffers(ctx, src_buf);
>
> - hantro_end_prepare_run(ctx);
> + hantro_end_prepare_run(ctx, 0);
[Severity: High]
This is a pre-existing issue, but do the early error paths in codec run
callbacks bypass hantro_end_prepare_run() after hantro_start_prepare_run()
is invoked internally?
If context preparation or reference setup fails in hantro_g1_h264_dec_run(),
hantro_g2_hevc_dec_run(), or hantro_g1_vp8_dec_run(), early returns might
miss the required call to hantro_end_prepare_run(). This can leave the
V4L2 control requests incomplete indefinitely, which could lead to a resource
leak and userspace application hang when malformed streams or invalid V4L2
parameters trigger these paths.
> diff --git a/drivers/media/platform/verisilicon/hantro_h1_jpeg_enc.c b/drivers/media/platform/verisilicon/hantro_h1_jpeg_enc.c
> index 86cc1a07026f0..78f51e92fb2e3 100644
> --- a/drivers/media/platform/verisilicon/hantro_h1_jpeg_enc.c
> +++ b/drivers/media/platform/verisilicon/hantro_h1_jpeg_enc.c
[ ... ]
> @@ -148,7 +148,7 @@ int hantro_h1_jpeg_enc_run(struct hantro_ctx *ctx)
> | H1_REG_ENC_PIC_INTRA
> | H1_REG_ENC_CTRL_EN_BIT;
>
> - hantro_end_prepare_run(ctx);
> + hantro_end_prepare_run(ctx, 0);
[Severity: High]
This isn't a bug introduced by this patch, but is there a missing NULL
pointer check for the return value of vb2_plane_vaddr() in
hantro_h1_jpeg_enc_run() before it is dereferenced?
Earlier in this function, vb2_plane_vaddr() assigns the virtual address
of the destination buffer to jpeg_ctx.buffer. If the buffer mapping fails
due to an invalid exporter or vmalloc exhaustion, it returns NULL. The
pointer is then unconditionally passed to hantro_jpeg_header_assemble(),
which calls memcpy() on it, potentially causing a kernel panic. Should
there be a check here to safely handle mapping failures?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260819-rk3588-jpegdec-v1-0-33d74cdf369c@pengutronix.de?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 5/7] media: verisilicon: Add Rockchip VPU720 JPEG decoder
2026-08-19 10:37 ` [PATCH 5/7] media: verisilicon: Add Rockchip " Sascha Hauer
@ 2026-08-19 11:24 ` sashiko-bot
2026-08-19 12:46 ` Heiko Stübner
1 sibling, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-08-19 11:24 UTC (permalink / raw)
To: Sascha Hauer; +Cc: conor+dt, media-ci, robh, devicetree
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] DMA API violation and cache coherency bug when filling chroma plane for grayscale JPEGs.
- [High] Lockless Read-Modify-Write on VDPU720_REG_INT causes destructive race between hardware IRQ and watchdog soft reset.
- [Medium] Valid JPEG frames are erroneously rejected due to a strict 64-byte tail search for the EOI marker.
- [Medium] Missing endianness conversions when writing 16-bit table entries to the DMA side buffer.
--
--- Patch [5]: [PATCH 5/7] media: verisilicon: Add Rockchip VPU720 JPEG decoder ---
commit 8ca41ed2bd2cec7b6e2c467549362041d9356521
Author: Lucas Sinn <lucas.sinn@wolfvision.net>
media: verisilicon: Add Rockchip VPU720 JPEG decoder
This commit adds support for the Rockchip VPU720 JPEG hardware decoder on
the RK3588 SoC to the verisilicon hantro driver. It introduces the
HANTRO_JPEG_DECODER codec and mode, implementing header parsing, Q/H table
side buffer generation, and handling of VPU720 specific configuration limits.
> diff --git a/drivers/media/platform/verisilicon/rockchip_vpu720_hw_jpeg_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu720_hw_jpeg_dec.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/media/platform/verisilicon/rockchip_vpu720_hw_jpeg_dec.c
[ ... ]
> +static int vdpu720_write_qtbl(struct hantro_ctx *ctx,
> + const struct v4l2_jpeg_header *hdr)
> +{
[ ... ]
> + /*
> + * Reorder zigzag → raster scan.
> + * v4l2_jpeg_zigzag_scan_index[z] = raster position of zigzag
> + * element z. JPEG Q-tables are stored in zigzag order; the
> + * hardware expects them in natural raster (row-major) order.
> + */
> + for (i = 0; i < VDPU720_QTBL_ENTRIES; i++)
> + dst[v4l2_jpeg_zigzag_scan_index[i]] = (u16)qtbl[i];
[Severity: Medium]
Will this write the 16-bit table entries correctly on a Big Endian kernel?
It looks like the Q-table elements are written using native CPU endianness.
Since the VPU720 hardware expects the DMA buffer to be in Little Endian format,
could this result in decoding failures and visual corruption on Big Endian
systems? Using explicit endian conversions like cpu_to_le16() might be
necessary here.
[ ... ]
> +static int vdpu720_write_htbl(struct hantro_ctx *ctx,
> + const struct v4l2_jpeg_header *hdr)
> +{
[ ... ]
> + vdpu720_compute_mincode(dc_bits, min_dc, acc_dc);
> + vdpu720_compute_mincode(ac_bits, min_ac, acc_ac);
> +
> + for (i = 0; i < 16; i++)
> + *p_mincode++ = min_dc[i];
[Severity: Medium]
Similarly, the Huffman mincode table entries appear to be written with
native CPU endianness. Does this also require cpu_to_le16() for the same
Little Endian hardware requirement?
[ ... ]
> +static int vdpu720_fill_chroma(struct hantro_ctx *ctx,
> + struct vb2_v4l2_buffer *dst_buf)
> +{
[ ... ]
> + /* Available because variant->dst_needs_kmap */
> + dst_cpu = vb2_plane_vaddr(&dst_buf->vb2_buf, 0);
> + if (!dst_cpu) {
> + dev_err(vpu->dev,
> + "JPEG capture buffer has no kernel mapping\n");
> + return -EINVAL;
> + }
> +
> + memset(dst_cpu + y_size, 0x80, size - y_size);
[Severity: High]
Can this memset result in data loss or corruption for the UV plane?
This memset modifies the CPU cache for a device-owned DMA buffer. Because it
is called from rockchip_vpu720_jpeg_dec_run() after the buffer has been
queued and synced for the device, it dirties the CPU cache without explicit
cache synchronization.
When the hardware completes its operations and the V4L2 core calls
dma_sync_sg_for_cpu() with DMA_FROM_DEVICE during DQBUF, the CPU cache is
unconditionally invalidated. Won't this destroy the CPU's memset writes and
leave the UV plane with uninitialized memory or stale data if non-coherent
DMABUFs are used?
[ ... ]
> +int rockchip_vpu720_jpeg_dec_run(struct hantro_ctx *ctx)
> +{
[ ... ]
> + tail_len = min_t(u32, src_len, sizeof(tail));
> + memcpy(tail, src_cpu + src_len - tail_len, tail_len);
> +
> + for (i = 0; i + 1 < tail_len; i++)
> + if (tail[i] == 0xff && tail[i + 1] == 0xd9)
> + break;
> +
> + if (i + 1 >= tail_len) {
> + dev_err_ratelimited(vpu->dev,
> + "truncated JPEG, no EOI at the end of the %u byte payload (buffer too small?)\n",
> + src_len);
> + ret = -EINVAL;
> + goto err;
> + }
[Severity: Medium]
Could this strict 64-byte tail search reject valid padded JPEG frames?
If a userspace application or upstream pipeline pads the JPEG payload
(for example, with page-aligned or container-aligned chunks) and includes
the padding in bytesused, the padding might easily exceed 62 bytes.
If the 0xff 0xd9 EOI marker falls outside the strictly checked 64-byte tail
buffer, it looks like valid frames will be rejected with -EINVAL. Could this
be made more robust by scanning backwards dynamically or parsing the stream
length instead?
[ ... ]
> +static int vdpu720_soft_reset(struct hantro_dev *vpu)
> +{
> + u32 status;
> + int ret;
> +
> + /*
> + * If the decoder is idle (DEC_E=0), set FORCE_SOFTRESET_VALID
> + * before triggering the soft reset, per downstream BSP behaviour.
> + */
> + status = vdpu_read(vpu, VDPU720_REG_INT);
> + if (!(status & VDPU720_DEC_E))
> + vdpu_write(vpu, VDPU720_FORCE_SOFTRST, VDPU720_REG_SYS);
> +
> + vdpu_write(vpu, status | VDPU720_SOFT_RST_EN, VDPU720_REG_INT);
[Severity: High]
Is there a race condition here between the watchdog soft reset and the
hardware IRQ?
vdpu720_soft_reset() is executed from the hantro_watchdog() workqueue in
process context and performs an unprotected Read-Modify-Write (RMW) on
VDPU720_REG_INT. Concurrently, rockchip_vpu720_irq() can preempt this in
hardirq context and write to the same register:
rockchip_vpu720_irq() {
...
vdpu_write(vpu, clr_mask, VDPU720_REG_INT);
...
vdpu_write(vpu, 0, VDPU720_REG_INT);
}
If an untimely IRQ fires, it could clear VDPU720_SOFT_RST_EN or
VDPU720_SOFT_RST_RDY midway through the reset sequence. Won't this cause
readl_relaxed_poll_timeout() to fail and force an unnecessary hard reset,
or corrupt the hardware state? Should this sequence acquire vpu->irqlock?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260819-rk3588-jpegdec-v1-0-33d74cdf369c@pengutronix.de?part=5
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 2/7] media: verisilicon: Complete the request on the ->run() error paths
2026-08-19 10:37 ` [PATCH 2/7] media: verisilicon: Complete the request on the ->run() error paths Sascha Hauer
@ 2026-08-19 12:19 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-08-19 12:19 UTC (permalink / raw)
To: Sascha Hauer; +Cc: conor+dt, devicetree, robh, media-ci
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Unvalidated divisor derived from user-controlled AV1 tile configuration causes divide-by-zero panic.
- [Critical] Massive out-of-bounds write on a DMA coherent buffer due to unvalidated AV1 tile dimensions.
- [High] Missing NULL pointer check for destination buffer mapping in H1 JPEG encoder.
- [High] Media request control objects are leaked when device_run() fails early, causing indefinite blocking.
--
--- Patch [2]: [PATCH 2/7] media: verisilicon: Complete the request on the ->run() error paths ---
commit f5dd21ff10c1a9217ffb61543de91059d5e0444c
Author: Sascha Hauer <s.hauer@pengutronix.de>
media: verisilicon: Complete the request on the ->run() error paths
This commit modifies several Hantro codec ->run() callbacks to ensure
hantro_end_prepare_run() is called with an error code on early return paths.
This prevents media request control objects from being leaked when the run
operations fail.
[ ... ]
> The buffers are still returned to userspace, device_run() finishes the job
> with VB2_BUF_STATE_ERROR, but nothing completes the control handler object
> bound to the media request. vb2_buffer_done() only unbinds the request
> object owned by videobuf2 itself, so num_incomplete_objects never drops to
> zero and the request stays in MEDIA_REQUEST_STATE_QUEUED forever: poll() on
> the request file descriptor never returns and MEDIA_REQUEST_IOC_REINIT
> fails with -EBUSY. The request is only cleaned up when userspace closes it.
[Severity: High]
This is a pre-existing issue, but looking at device_run() in hantro_drv.c,
if it fails early before calling ctx->codec_ops->run(), it jumps to the
err_cancel_job label:
drivers/media/platform/verisilicon/hantro_drv.c:device_run() {
...
if (ret < 0)
goto err_cancel_job;
ret = clk_bulk_enable(ctx->dev->variant->num_clocks, ctx->dev->clocks);
if (ret)
goto err_pm_put;
v4l2_m2m_buf_copy_metadata(src, dst);
if (ctx->codec_ops->run(ctx))
goto err_clk_disable;
return;
...
err_cancel_job:
hantro_job_finish_no_pm(ctx->dev, ctx, VB2_BUF_STATE_ERROR);
}
Does this error path also leak the media request control object? It finishes
the job but doesn't seem to complete the control objects, which might leave
the request in MEDIA_REQUEST_STATE_QUEUED indefinitely.
[ ... ]
> diff --git a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c
> index aa34bd2e47ad2..b273b0b56ced5 100644
> --- a/drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c
> +++ b/drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c
> @@ -143,8 +143,10 @@ int rockchip_vpu2_jpeg_enc_run(struct hantro_ctx *ctx)
>
> memset(&jpeg_ctx, 0, sizeof(jpeg_ctx));
> jpeg_ctx.buffer = vb2_plane_vaddr(&dst_buf->vb2_buf, 0);
> - if (!jpeg_ctx.buffer)
> + if (!jpeg_ctx.buffer) {
> + hantro_end_prepare_run(ctx, -ENOMEM);
> return -ENOMEM;
> + }
[Severity: High]
This isn't a bug introduced by this patch, but while reviewing this fix,
I noticed that hantro_h1_jpeg_enc_run() in hantro_h1_jpeg_enc.c appears to
be missing this same NULL pointer check entirely:
drivers/media/platform/verisilicon/hantro_h1_jpeg_enc.c:hantro_h1_jpeg_enc_run() {
...
jpeg_ctx.buffer = vb2_plane_vaddr(&dst_buf->vb2_buf, 0);
jpeg_ctx.width = ctx->dst_fmt.width;
jpeg_ctx.height = ctx->dst_fmt.height;
jpeg_ctx.quality = ctx->jpeg_quality;
hantro_jpeg_header_assemble(&jpeg_ctx);
...
}
Since vb2_plane_vaddr() can return NULL if the buffer cannot be mapped,
could this cause a NULL pointer dereference in hantro_jpeg_header_assemble()
if userspace submits an unmappable buffer?
[Severity: Critical]
This is a pre-existing issue, but I noticed another potential problem related
to V4L2 control validation in the AV1 decoder:
drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c:rockchip_vpu981_av1_dec_set_tile_info() {
...
int context_update_y =
tile_info->context_update_tile_id / tile_info->tile_cols;
int context_update_x =
tile_info->context_update_tile_id % tile_info->tile_cols;
...
}
Userspace provides tile_cols via the V4L2_CID_STATELESS_AV1_FRAME control.
If this value is not validated by the V4L2 core or the driver, could setting
tile_cols to 0 trigger a divide-by-zero kernel panic?
[Severity: Critical]
This is another pre-existing issue in the same function. Further down,
tile_cols and tile_rows are used as bounds for a nested loop that writes
to the DMA coherent buffer:
drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c:rockchip_vpu981_av1_dec_set_tile_info() {
...
for (tile0 = 0; tile0 < tile_info->tile_cols; tile0++) {
for (tile1 = 0; tile1 < tile_info->tile_rows; tile1++) {
int tile_id = tile1 * tile_info->tile_cols + tile0;
u32 start, end;
u32 y0 =
tile_info->height_in_sbs_minus_1[tile1] + 1;
u32 x0 = tile_info->width_in_sbs_minus_1[tile0] + 1;
/* tile size in SB units (width,height) */
*dst++ = x0;
...
}
The av1_dec->tile_info.cpu buffer is allocated with a fixed size
(AV1_MAX_TILES * 16 bytes). If a user provides large unvalidated values
for tile_cols and tile_rows (up to 65535), could this cause a massive
out-of-bounds write and memory corruption?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260819-rk3588-jpegdec-v1-0-33d74cdf369c@pengutronix.de?part=2
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 5/7] media: verisilicon: Add Rockchip VPU720 JPEG decoder
2026-08-19 10:37 ` [PATCH 5/7] media: verisilicon: Add Rockchip " Sascha Hauer
2026-08-19 11:24 ` sashiko-bot
@ 2026-08-19 12:46 ` Heiko Stübner
2026-08-19 14:12 ` Sascha Hauer
1 sibling, 1 reply; 13+ messages in thread
From: Heiko Stübner @ 2026-08-19 12:46 UTC (permalink / raw)
To: Nicolas Dufresne, Benjamin Gaignard, Philipp Zabel,
Mauro Carvalho Chehab, Ezequiel Garcia, Andrzej Pietrasiewicz,
Hans Verkuil, Chen-Yu Tsai, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Lucas Sinn, Sascha Hauer
Cc: Mauro Carvalho Chehab, linux-media, linux-rockchip, linux-kernel,
linux-arm-kernel, devicetree, Sascha Hauer
Hi,
Am Mittwoch, 19. August 2026, 12:37:34 Mitteleuropäische Sommerzeit schrieb Sascha Hauer:
> From: Lucas Sinn <lucas.sinn@wolfvision.net>
>
> Add support for the Rockchip VPU720 JPEG hardware decoder on RK3588
> to the verisilicon hantro driver.
>
> Hardware requirements:
> - CPU-side JPEG header parsing via v4l2_jpeg_parse_header()
> - DMA side buffer with Q-tables (zigzag->raster), Huffman mincode
> and value tables
> - VPU720-specific two-phase IRQ clear sequence
> - 16-byte stream alignment with start-byte offset
> - MCU-aligned PIC_H (e.g. 1080p YUV420 needs 1088, not 1080)
> - FILL_DOWN_E on every NV12 conversion, the output chroma is vertically
> subsampled so the hardware completes the bottom of the picture
> - DRI (restart interval) support when present
>
> Implementation adds:
> - HANTRO_JPEG_DECODER codec and HANTRO_MODE_JPEG_DEC mode
> - struct hantro_jpeg_dec_hw_ctx holding the Q/H table side buffer,
> which is rebuilt from the frame header on every run
> - src_needs_kmap and dst_needs_kmap flags for vb2_plane_vaddr() without
> DMA_ATTR_NO_KERNEL_MAPPING
> - a neutral chroma plane written by the driver for a grayscale frame.
> The output format converter has no YUV400 path, so the hardware writes
> the luma plane and leaves the chroma alone, which comes out green
> - rockchip_vpu720_jpeg_dec_run() for parse/fill/program/kick
> - rejection of a frame that carries no EOI marker, which is what a
> source buffer too small for the frame looks like
> - rejection of a frame larger than the negotiated capture format,
> whose dimensions would otherwise be programmed against strides
> taken from that format
> - IRQ handler with detailed error diagnostics (REG32/33:
> MCU position, error flags) and soft-reset
> - AXI perf counter setup (REG30)
>
> Exposes one V4L2 M2M device: JPEG input -> NV12 output.
>
> Assisted-by: Claude:claude-opus-5
> Signed-off-by: Lucas Sinn <lucas.sinn@wolfvision.net>
> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
> ---
> drivers/media/platform/verisilicon/Makefile | 1 +
> drivers/media/platform/verisilicon/hantro.h | 17 +
> drivers/media/platform/verisilicon/hantro_drv.c | 17 +-
> drivers/media/platform/verisilicon/hantro_hw.h | 17 +
> drivers/media/platform/verisilicon/hantro_v4l2.c | 38 +-
> .../verisilicon/rockchip_vpu720_hw_jpeg_dec.c | 962 +++++++++++++++++++++
> .../platform/verisilicon/rockchip_vpu720_regs.h | 261 ++++++
> .../media/platform/verisilicon/rockchip_vpu_hw.c | 80 ++
> 8 files changed, 1385 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/media/platform/verisilicon/hantro.h b/drivers/media/platform/verisilicon/hantro.h
> index 0353de154a1ec..00e0f5981cca3 100644
> --- a/drivers/media/platform/verisilicon/hantro.h
> +++ b/drivers/media/platform/verisilicon/hantro.h
> @@ -39,6 +39,7 @@ struct hantro_postproc_ops;
> #define HANTRO_HEVC_DECODER BIT(19)
> #define HANTRO_VP9_DECODER BIT(20)
> #define HANTRO_AV1_DECODER BIT(21)
> +#define HANTRO_JPEG_DECODER BIT(22)
> #define HANTRO_DECODERS 0xffff0000
>
> /**
> @@ -102,6 +103,19 @@ struct hantro_variant {
> unsigned int double_buffer : 1;
> unsigned int legacy_regs : 1;
> unsigned int late_postproc : 1;
> + /*
> + * src_needs_kmap: when set, the source queue will be allocated with
> + * a kernel virtual address so the driver can CPU-parse the bitstream
> + * (e.g. for JPEG header parsing).
> + */
there is already a struct documentation present, so there is
- no need to put that huge comment here, this can be shortened
- the actual field-documentation is missing
probably true for more places
> + unsigned int src_needs_kmap : 1;
> + /*
> + * dst_needs_kmap: when set, the capture queue will be allocated with
> + * a kernel virtual address so the driver can write the parts of a
> + * frame the hardware does not produce (e.g. the chroma plane of a
> + * grayscale JPEG).
> + */
> + unsigned int dst_needs_kmap : 1;
> const struct of_device_id *shared_devices;
> };
>
> @@ -115,6 +129,7 @@ struct hantro_variant {
> * @HANTRO_MODE_HEVC_DEC: HEVC decoder.
> * @HANTRO_MODE_VP9_DEC: VP9 decoder.
> * @HANTRO_MODE_AV1_DEC: AV1 decoder
> + * @HANTRO_MODE_JPEG_DEC: VPU720 JPEG decoder
here it worked correctly, it seems.
> */
> enum hantro_codec_mode {
> HANTRO_MODE_NONE = -1,
> @@ -125,6 +140,7 @@ enum hantro_codec_mode {
> HANTRO_MODE_HEVC_DEC,
> HANTRO_MODE_VP9_DEC,
> HANTRO_MODE_AV1_DEC,
> + HANTRO_MODE_JPEG_DEC,
> };
>
> /*
> @@ -276,6 +292,7 @@ struct hantro_ctx {
> struct hantro_hevc_dec_hw_ctx hevc_dec;
> struct hantro_vp9_dec_hw_ctx vp9_dec;
> struct hantro_av1_dec_hw_ctx av1_dec;
> + struct hantro_jpeg_dec_hw_ctx jpeg_dec;
here the documentation for the new field is missing completely
> };
> };
>
> diff --git a/drivers/media/platform/verisilicon/rockchip_vpu720_hw_jpeg_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu720_hw_jpeg_dec.c
> new file mode 100644
> index 0000000000000..81fd79911d694
> --- /dev/null
> +++ b/drivers/media/platform/verisilicon/rockchip_vpu720_hw_jpeg_dec.c
> @@ -0,0 +1,962 @@
> + /*
> + * REG2: system config – always output NV12.
> + *
> + * FILL_DOWN_E belongs to the NV12 conversion rather than to a
> + * particular pair of heights: the output chroma is vertically
> + * subsampled, so the hardware has to complete the bottom of the
> + * picture. Two cases need it:
> + *
> + * a) jpeg_height is not on an MCU boundary (e.g. YUV420 1080p:
> + * jpeg_height=1080, jpeg_height_aligned=1088, buf_height=1088).
> + * The VPU720 needs FILL_DOWN_E to complete the last MCU row's
> + * chroma reconstruction. Without it the bottom rows are corrupt
> + * even though PIC_H is already set to the MCU-aligned height.
> + *
> + * b) MCU-aligned height < buf_height (e.g. YUV422 1080p: mcu_h=8,
> + * jpeg_height_aligned=1080, buf_height=1088). The hardware fills
> + * rows 1080..1087 by repeating the last valid row.
> + *
> + * A height that is a multiple of 16 leaves nothing to fill, but the
> + * bit is set there as well: the reference driver enables it for every
> + * NV12 conversion, whatever the picture and buffer heights are.
> + *
> + * FILL_RIGHT_E does the same for the right hand edge, but there it
> + * depends on the mode. The decoder writes whole MCUs, so the last
> + * MCU column ends at ALIGN(jpeg_width, mcu_width) while the buffer is
> + * 16 pixel aligned. Only the 8 pixel MCU widths can stop short of
> + * that and need the columns in between filled; a 16 or 32 pixel MCU
> + * already reaches at least as far. The reference driver arrives at
> + * the same set through a per mode test on (width & 0xf) <= 8.
> + */
> + reg = FIELD_PREP(VDPU720_YUV_OUT_FMT, VDPU720_YUV_OUT_FMT_NV12) |
> + VDPU720_FILL_DOWN_E;
> + if (ALIGN(jpeg_width, mcu_width) < ALIGN(jpeg_width, 16))
> + reg |= VDPU720_FILL_RIGHT_E;
> + vdpu_write_relaxed(vpu, reg, VDPU720_REG_SYS);
> +
> + /*
> + * --- REG3: picture dimensions ---
can we have one consistent comment style please?
> + *
> + * PIC_W stays at the raw header width while PIC_H is rounded up to
> + * the MCU boundary. Rounding the width up the same way is not safe:
> + * mcu_width reaches 32 for YUV411, so ALIGN(jpeg_width, mcu_width)
> + * can land beyond the 16 pixel aligned buffer width and point the
> + * decoder past the end of a row. FILL_RIGHT_E above covers the cases
> + * where the MCU column stops short instead. The reference driver
> + * programs the raw width here too.
> + */
> + vdpu_write_relaxed(vpu,
> + FIELD_PREP(VDPU720_PIC_W_M1, jpeg_width - 1) |
> + FIELD_PREP(VDPU720_PIC_H_M1, jpeg_height_aligned - 1),
> + VDPU720_REG_PIC_SIZE);
> +
[...]
> +/*
> + * vdpu720_fill_chroma - write neutral chroma for a grayscale frame.
> + *
> + * The output format converter has no YUV400 path: VDPU720_YUV_OUT_FMT_NV12
> + * only covers the subsampled colour modes, and for a single component frame
> + * the hardware writes the luma plane and leaves the chroma plane untouched.
> + *
> + * Fill the plane here, before the hardware is started: once the decode is
> + * running the interrupt can complete the job and hand the buffer to
> + * userspace at any time.
> + */
> +static int vdpu720_fill_chroma(struct hantro_ctx *ctx,
> + struct vb2_v4l2_buffer *dst_buf)
[...]
> +/**
> + * rockchip_vpu720_jpeg_dec_init() - allocate the per-context DMA side buffer
> + * @ctx: context to allocate the Q/Huffman table buffer for
> + *
> + * Return: 0 on success, -ENOMEM if the buffer could not be allocated.
> + */
again comment style ( "/**", also the ctx line could use a blank line above it)
Seemingly one function before this the LLM did get it right?
Same applies for repeats below.
> +int rockchip_vpu720_jpeg_dec_init(struct hantro_ctx *ctx)
> +{
> + struct hantro_dev *vpu = ctx->dev;
> + struct hantro_jpeg_dec_hw_ctx *jpeg_ctx = jpeg_dec_ctx(ctx);
> +
> + jpeg_ctx->table_base.size = VDPU720_TABLE_BUF_SIZE;
> + jpeg_ctx->table_base.cpu =
> + dma_alloc_noncoherent(vpu->dev,
> + jpeg_ctx->table_base.size,
> + &jpeg_ctx->table_base.dma,
> + DMA_TO_DEVICE, GFP_KERNEL);
> + if (!jpeg_ctx->table_base.cpu)
> + return -ENOMEM;
> +
> + return 0;
> +}
> +
[...]
> diff --git a/drivers/media/platform/verisilicon/rockchip_vpu720_regs.h b/drivers/media/platform/verisilicon/rockchip_vpu720_regs.h
> new file mode 100644
> index 0000000000000..cae9052126be1
> --- /dev/null
> +++ b/drivers/media/platform/verisilicon/rockchip_vpu720_regs.h
> @@ -0,0 +1,261 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Rockchip VPU720 JPEG decoder register definitions
> + *
> + * Derived from downstream Rockchip MPP HAL (hal_jpegd_rkv_reg.h).
> + * Copyright (C) 2020 Rockchip Electronics Co., Ltd.
> + * Copyright (C) 2026 WolfVision GmbH
> + */
> +#ifndef ROCKCHIP_VPU720_REGS_H_
> +#define ROCKCHIP_VPU720_REGS_H_
> +
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/align.h>
> +#include <linux/types.h>
> +
> +/* ------------------------------------------------------------------ */
> +/* Register byte offsets from dec_base */
> +/* ------------------------------------------------------------------ */
less dashed lines maybe? ;-)
> +
> +/* REG0: IP version / product ID */
no need the same information when the register naming is obvious.
Same for the below registers
> +#define VDPU720_REG_VERSION 0x000
> +#define VDPU720_PROD_NUM GENMASK(31, 16)
> +#define VDPU720_BIT_DEPTH BIT(8)
> +
> +/* REG1: Interrupt control and status */
> +#define VDPU720_REG_INT 0x004
[...]
> +/* ------------------------------------------------------------------ */
> +/* Side-buffer layout for Q-tables and Huffman tables */
> +/* */
> +/* The VPU720 JPEG decoder reads quantisation tables and Huffman */
> +/* tables from a contiguous DMA buffer with the following layout: */
> +/* */
> +/* [0, QTBL_SIZE): Q-table data (u16, raster-scan order) */
> +/* [HMINCODE_OFF, +HMIN_SZ): Huffman mincode table */
> +/* [HVALUE_OFF, +HVAL_SZ): Huffman value table */
> +/* ------------------------------------------------------------------ */
> +/* The Q-tables are per component, one entry each */
again, the comment style is very strange
[...]
> @@ -816,3 +831,68 @@ const struct hantro_variant rk3588_vpu981_variant = {
> .clk_names = rk3588_vpu981_vpu_clk_names,
> .num_clocks = ARRAY_SIZE(rk3588_vpu981_vpu_clk_names)
> };
> +
> +/* ------------------------------------------------------------------ */
> +/* RK3588 VPU720 JPEG decoder */
> +/* ------------------------------------------------------------------ */
> +
> +/*
> + * Capture format: NV12. The JPEG codec entry is listed last.
> + */
> +static const struct hantro_fmt rk3588_vpu720_dec_fmts[] = {
> + {
> + .fourcc = V4L2_PIX_FMT_NV12,
> + .codec_mode = HANTRO_MODE_NONE,
> + .frmsize = {
> + .min_width = FMT_MIN_WIDTH,
> + .max_width = VPU720_JPEGD_MAX_SIZE,
> + .step_width = MB_DIM,
> + .min_height = FMT_MIN_HEIGHT,
> + .max_height = VPU720_JPEGD_MAX_SIZE,
> + .step_height = MB_DIM,
> + },
> + },
> + {
> + .fourcc = V4L2_PIX_FMT_JPEG,
> + .codec_mode = HANTRO_MODE_JPEG_DEC,
> + .max_depth = 2,
> + .frmsize = {
> + .min_width = FMT_MIN_WIDTH,
> + .max_width = VPU720_JPEGD_MAX_SIZE,
> + .step_width = VPU720_JPEGD_STEP,
> + .min_height = FMT_MIN_HEIGHT,
> + .max_height = VPU720_JPEGD_MAX_SIZE,
> + .step_height = VPU720_JPEGD_STEP,
> + },
> + },
> +};
> +
> +static const struct hantro_codec_ops rk3588_vpu720_codec_ops[] = {
> + [HANTRO_MODE_JPEG_DEC] = {
> + .run = rockchip_vpu720_jpeg_dec_run,
> + .reset = rockchip_vpu720_reset,
> + .init = rockchip_vpu720_jpeg_dec_init,
> + .exit = rockchip_vpu720_jpeg_dec_exit,
> + },
> +};
> +
> +static const struct hantro_irq rk3588_vpu720_irqs[] = {
> + { "vdpu", rockchip_vpu720_irq },
> +};
> +
> +static const char * const rk3588_vpu720_clk_names[] = {
> + "aclk", "hclk",
> +};
> +
> +const struct hantro_variant rk3588_vpu720_variant = {
> + .dec_fmts = rk3588_vpu720_dec_fmts,
> + .num_dec_fmts = ARRAY_SIZE(rk3588_vpu720_dec_fmts),
> + .codec = HANTRO_JPEG_DECODER,
> + .codec_ops = rk3588_vpu720_codec_ops,
> + .irqs = rk3588_vpu720_irqs,
> + .num_irqs = ARRAY_SIZE(rk3588_vpu720_irqs),
> + .clk_names = rk3588_vpu720_clk_names,
> + .num_clocks = ARRAY_SIZE(rk3588_vpu720_clk_names),
> + .src_needs_kmap = 1,
> + .dst_needs_kmap = 1,
> +};
could you move the individual parts to the correct places in the file?
All the other variants are clustered together, with their specific
fmt, etc definitions being in other clusters above that.
Heiko
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 5/7] media: verisilicon: Add Rockchip VPU720 JPEG decoder
2026-08-19 12:46 ` Heiko Stübner
@ 2026-08-19 14:12 ` Sascha Hauer
0 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-08-19 14:12 UTC (permalink / raw)
To: Heiko Stübner
Cc: Nicolas Dufresne, Benjamin Gaignard, Philipp Zabel,
Mauro Carvalho Chehab, Ezequiel Garcia, Andrzej Pietrasiewicz,
Hans Verkuil, Chen-Yu Tsai, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Lucas Sinn, Sascha Hauer, Mauro Carvalho Chehab,
linux-media, linux-rockchip, linux-kernel, linux-arm-kernel,
devicetree, Sascha Hauer
Hi,
On 2026-08-19 14:46, Heiko Stübner wrote:
> Hi,
>
> > +static int vdpu720_fill_chroma(struct hantro_ctx *ctx,
> > + struct vb2_v4l2_buffer *dst_buf)
>
> [...]
>
> > +/**
> > + * rockchip_vpu720_jpeg_dec_init() - allocate the per-context DMA side buffer
> > + * @ctx: context to allocate the Q/Huffman table buffer for
> > + *
> > + * Return: 0 on success, -ENOMEM if the buffer could not be allocated.
> > + */
>
> again comment style ( "/**", also the ctx line could use a blank line above it)
> Seemingly one function before this the LLM did get it right?
It's not as inconsistent as it seems. The exported functions have a
kerneldoc header with /** and the arguments described, the static
functions just have a freestyle header. Documentation/doc-guide/kernel-doc.rst
also doesn't have a blank line between the function name and the first
argument.
I could change the static function to kerneldoc as well if you prefer
that.
Sascha
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-08-19 14:12 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-19 10:37 [PATCH 0/7] media: verisilicon: Add RK3588 VPU720 JPEG decoder Sascha Hauer
2026-08-19 10:37 ` [PATCH 1/7] media: verisilicon: Fix the cleanup when a codec ->run() fails Sascha Hauer
2026-08-19 11:23 ` sashiko-bot
2026-08-19 10:37 ` [PATCH 2/7] media: verisilicon: Complete the request on the ->run() error paths Sascha Hauer
2026-08-19 12:19 ` sashiko-bot
2026-08-19 10:37 ` [PATCH 3/7] media: verisilicon: Allow the EOS event to be subscribed Sascha Hauer
2026-08-19 10:37 ` [PATCH 4/7] media: dt-bindings: Add Rockchip RK3588 VPU720 JPEG decoder Sascha Hauer
2026-08-19 10:37 ` [PATCH 5/7] media: verisilicon: Add Rockchip " Sascha Hauer
2026-08-19 11:24 ` sashiko-bot
2026-08-19 12:46 ` Heiko Stübner
2026-08-19 14:12 ` Sascha Hauer
2026-08-19 10:37 ` [PATCH 6/7] media: verisilicon: Enforce a minimum sizeimage for the " Sascha Hauer
2026-08-19 10:37 ` [PATCH 7/7] arm64: dts: rockchip: rk3588: Add VPU720 JPEG decoder node Sascha Hauer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox