Skip to content

Commit 90241cd

Browse files
SympleNZalchark
authored andcommitted
media: rkvdec: prime VDPU383 deblock state on RK3576 power-up
The VDPU383 video decoder on the RK3576 comes out of power-up with its internal H.264 deblocking-context state uninitialised. Decoding then intermittently produces wrong pixels at the horizontal deblock edges (luma rows 4, 12 and 13 within each 16-row macroblock row), which propagate through the following P-frames. The corruption is non-deterministic and its rate varies between boards (a few percent up to ~40%); it affects H.264 only - HEVC and VP9 on the same IP are fine. The Rockchip vendor stack (BSP/MPP) avoids this by running a one-shot priming decode of a small canned bitstream at every decoder power-up (rk3576_workaround_init / rk3576_workaround_run); the mainline driver omits it. Port that priming sequence: allocate the descriptor buffer once at probe and run the priming decode on every pm_runtime resume, which catches every power-up. It is scoped to the VDPU383 variant and is best-effort - a failure is logged and decoding continues. With the priming in place, an H.264 stream that reproduced the race decodes bit-exact against avdec_h264 across 64 consecutive runs, versus the reproduced baseline race without it. The priming runs once per power-up and does not affect steady-state throughput. No devicetree change is required: the "link" register bank the priming sequence uses is already mapped. Signed-off-by: Simon Wright <simon@symple.nz> Signed-off-by: Alexey Charkov <alchark@flipper.net>
1 parent 87cab05 commit 90241cd

4 files changed

Lines changed: 211 additions & 2 deletions

File tree

drivers/media/platform/rockchip/rkvdec/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ rockchip-vdec-y += \
1212
rkvdec-vdpu381-hevc.o \
1313
rkvdec-vdpu383-h264.o \
1414
rkvdec-vdpu383-hevc.o \
15-
rkvdec-vp9.o
15+
rkvdec-vp9.o \
16+
rkvdec-rk3576-workaround.o
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Rockchip RK3576 (VDPU383) power-up priming workaround.
4+
*
5+
* The VDPU383 decoder on the RK3576 comes out of power-up with its internal
6+
* deblocking-context state uninitialised. Without priming, H.264 decode
7+
* intermittently produces wrong pixels at the horizontal deblock edges (luma
8+
* rows 4, 12 and 13 within each 16-row macroblock row), propagating through
9+
* subsequent P-frames. The failure is non-deterministic and its rate varies
10+
* between boards.
11+
*
12+
* The Rockchip BSP avoids this by running a one-shot priming decode of a small
13+
* canned bitstream at every decoder power-up (rk3576_workaround_init /
14+
* rk3576_workaround_run). This ports that sequence: the priming buffer is
15+
* allocated once at probe and the priming decode is run on every pm_runtime
16+
* resume (which catches every power-up). It is best-effort - on failure a
17+
* warning is logged and decoding continues (degraded for H.264).
18+
*
19+
* The descriptor layout and the link-bank register kick sequence are
20+
* transcribed from the BSP rk3576_workaround_{init,run}; the 64-byte header is
21+
* an opaque IP-init blob taken verbatim from the BSP.
22+
*/
23+
24+
#include <linux/delay.h>
25+
#include <linux/dma-mapping.h>
26+
#include <linux/errno.h>
27+
#include <linux/io.h>
28+
#include <linux/kernel.h>
29+
#include <linux/string.h>
30+
#include <linux/types.h>
31+
32+
#include "rkvdec.h"
33+
34+
/* Opaque 64-byte IP-init header blob, verbatim from the BSP .rodata. */
35+
static const u8 rkvdec_rk3576_warmup_hdr[64] = {
36+
0x00, 0x00, 0x01, 0x65, 0x88, 0x82, 0x0b, 0x01,
37+
0x2f, 0x08, 0xc5, 0x00, 0x01, 0x51, 0x78, 0xe0,
38+
0x00, 0x24, 0xf7, 0x1c, 0x00, 0x04, 0xcc, 0xeb,
39+
0x89, 0xd7, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
40+
0x40, 0x26, 0x00, 0x10, 0x04, 0x08, 0x00, 0x08,
41+
0x80, 0x01, 0x00, 0x00, 0x00, 0x40, 0x01, 0xd8,
42+
0x07, 0x7c, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00,
43+
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
44+
};
45+
46+
/* "rk" recognition markers the IP looks for in the priming descriptor. */
47+
#define RK3576_WARMUP_SENTINEL_HI 0x76543210u
48+
#define RK3576_WARMUP_SENTINEL_HI_PLUS2 0x76543212u
49+
/* BSP allocates 8 KiB; the descriptor lives at +0x1000. */
50+
#define RK3576_WARMUP_BUF_SIZE 0x2000u
51+
52+
/* Lay out the priming descriptor in the (zeroed) DMA buffer. Offsets and
53+
* constants are transcribed verbatim from the BSP rk3576_workaround_init.
54+
*/
55+
static void rkvdec_rk3576_warmup_populate(void *buf, dma_addr_t iova)
56+
{
57+
u8 *b8 = buf;
58+
u32 *b32 = buf;
59+
u32 iova_lo = lower_32_bits(iova);
60+
61+
memcpy(&b8[0], &rkvdec_rk3576_warmup_hdr[0], 32);
62+
memcpy(&b8[64], &rkvdec_rk3576_warmup_hdr[32], 16);
63+
memcpy(&b8[80], &rkvdec_rk3576_warmup_hdr[48], 8);
64+
memcpy(&b8[88], &rkvdec_rk3576_warmup_hdr[56], 4);
65+
66+
b32[4128 / 4] = 0x00000001u;
67+
b32[4148 / 4] = 0x0000ffffu;
68+
b32[4160 / 4] = 0x00000101u;
69+
b32[4176 / 4] = 0xffffffffu;
70+
b32[4180 / 4] = 0x3ff3ffffu;
71+
72+
b32[4352 / 4] = RK3576_WARMUP_SENTINEL_HI;
73+
b32[4356 / 4] = 0x00000000u;
74+
b32[4360 / 4] = 0x00000020u;
75+
b32[4364 / 4] = 0x000000a8u;
76+
b32[4368 / 4] = 0x00000002u;
77+
b32[4372 / 4] = 0x00000002u;
78+
b32[4376 / 4] = 0x00000040u;
79+
80+
b32[4608 / 4] = iova_lo;
81+
b32[4612 / 4] = iova_lo + 0x140u;
82+
b32[4620 / 4] = iova_lo + 0x040u;
83+
b32[4656 / 4] = iova_lo + 0x240u;
84+
b32[4660 / 4] = 0x000000c0u;
85+
b32[4688 / 4] = iova_lo + 0x340u;
86+
b32[4692 / 4] = 0x00000200u;
87+
b32[4768 / 4] = iova_lo + 0x540u;
88+
b32[4960 / 4] = iova_lo + 0xb40u;
89+
b32[4100 / 4] = RK3576_WARMUP_SENTINEL_HI_PLUS2;
90+
b32[4096 / 4] = iova_lo + 0x540u;
91+
b32[4104 / 4] = iova_lo + 0x1400u;
92+
b32[4108 / 4] = iova_lo + 0x1020u;
93+
b32[4112 / 4] = iova_lo + 0x1100u;
94+
b32[4116 / 4] = iova_lo + 0x1200u;
95+
96+
wmb(); /* descriptor must reach RAM before the HW reads it */
97+
}
98+
99+
/*
100+
* Allocate + populate the priming buffer (once, at probe). Device-managed, so
101+
* it is freed automatically on device teardown. Returns 0 or negative errno.
102+
*/
103+
int rkvdec_rk3576_warmup_alloc(struct device *dev, void **out_cpu,
104+
dma_addr_t *out_dma)
105+
{
106+
void *cpu;
107+
dma_addr_t dma;
108+
109+
cpu = dmam_alloc_coherent(dev, RK3576_WARMUP_BUF_SIZE, &dma, GFP_KERNEL);
110+
if (!cpu)
111+
return -ENOMEM;
112+
113+
rkvdec_rk3576_warmup_populate(cpu, dma);
114+
*out_cpu = cpu;
115+
*out_dma = dma;
116+
return 0;
117+
}
118+
119+
/*
120+
* Run the priming decode through the link bank. Clocks and the power domain
121+
* must be on (true in pm_runtime resume). The priming decode raises no
122+
* completion IRQ - only the status register - so it is polled (~20 ms budget).
123+
* Returns 0 on clean completion, -EIO on HW error status, -ETIMEDOUT on no
124+
* completion.
125+
*/
126+
int rkvdec_rk3576_warmup_run(void __iomem *link_base, dma_addr_t buf_iova)
127+
{
128+
u32 status = 0;
129+
int i;
130+
131+
/* Kick sequence, verbatim from the BSP rk3576_workaround_run. */
132+
writel(0x8000u, link_base + 0x58); /* ip_en: BIT(15) only */
133+
writel(0x7ffffu, link_base + 0x54); /* ip watchdog */
134+
writel(0x10001u, link_base + 0x00); /* ccu/init */
135+
writel(lower_32_bits(buf_iova) + 0x1000u,
136+
link_base + 0x04); /* cfg_addr -> descriptor */
137+
writel(0x1u, link_base + 0x08); /* link_mode = 1 */
138+
writel(0x1u, link_base + 0x18); /* link_en = 1 */
139+
wmb(); /* commit config before cfg_done kicks the HW */
140+
writel(0x1u, link_base + 0x0c); /* cfg_done */
141+
142+
for (i = 0; i < 200; i++) {
143+
usleep_range(100, 150);
144+
status = readl(link_base + 0x4c);
145+
if (status)
146+
break;
147+
}
148+
149+
/* Teardown: clear irq + status, zero the config registers. */
150+
writel(0xffff0000u, link_base + 0x48);
151+
writel(0xffff0000u, link_base + 0x4c);
152+
writel(0x0u, link_base + 0x00);
153+
writel(0x0u, link_base + 0x08);
154+
writel(0x0u, link_base + 0x18);
155+
wmb();
156+
writel(0x0u, link_base + 0x58);
157+
158+
if (i >= 200)
159+
return -ETIMEDOUT;
160+
if (status & 0x3fe) /* err_mask */
161+
return -EIO;
162+
return 0;
163+
}

drivers/media/platform/rockchip/rkvdec/rkvdec.c

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1817,6 +1817,24 @@ static int rkvdec_probe(struct platform_device *pdev)
18171817

18181818
vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32));
18191819

1820+
/*
1821+
* RK3576/VDPU383 needs a power-up priming decode (see
1822+
* rkvdec-rk3576-workaround.c). Allocate the priming buffer once here;
1823+
* it is run on every pm_runtime resume. Best-effort.
1824+
*/
1825+
if (rkvdec->variant == &vdpu383_variant) {
1826+
ret = rkvdec_rk3576_warmup_alloc(&pdev->dev,
1827+
&rkvdec->rk3576_warmup_cpu,
1828+
&rkvdec->rk3576_warmup_dma);
1829+
if (ret)
1830+
dev_warn(&pdev->dev,
1831+
"rk3576 warmup alloc failed: %d (H.264 may corrupt)\n",
1832+
ret);
1833+
else
1834+
dev_info(&pdev->dev,
1835+
"RK3576 H.264 deblock-priming workaround enabled\n");
1836+
}
1837+
18201838
irq = platform_get_irq(pdev, 0);
18211839
if (irq <= 0)
18221840
return -ENXIO;
@@ -1881,8 +1899,27 @@ static void rkvdec_remove(struct platform_device *pdev)
18811899
static int rkvdec_runtime_resume(struct device *dev)
18821900
{
18831901
struct rkvdec_dev *rkvdec = dev_get_drvdata(dev);
1902+
int ret;
1903+
1904+
ret = clk_bulk_prepare_enable(rkvdec->num_clocks, rkvdec->clocks);
1905+
if (ret)
1906+
return ret;
1907+
1908+
/*
1909+
* RK3576/VDPU383: prime internal deblock-context state after every
1910+
* power-up (clocks + power domain are up here). Without it H.264
1911+
* decode races and corrupts horizontal deblock edges. Best-effort.
1912+
*/
1913+
if (rkvdec->variant == &vdpu383_variant && rkvdec->rk3576_warmup_cpu) {
1914+
ret = rkvdec_rk3576_warmup_run(rkvdec->link,
1915+
rkvdec->rk3576_warmup_dma);
1916+
if (ret)
1917+
dev_warn_ratelimited(dev,
1918+
"rk3576 warmup on resume failed: %d\n",
1919+
ret);
1920+
}
18841921

1885-
return clk_bulk_prepare_enable(rkvdec->num_clocks, rkvdec->clocks);
1922+
return 0;
18861923
}
18871924

18881925
static int rkvdec_runtime_suspend(struct device *dev)

drivers/media/platform/rockchip/rkvdec/rkvdec.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ struct rkvdec_dev {
136136
struct clk *axi_clk;
137137
void __iomem *regs;
138138
void __iomem *link;
139+
/* RK3576/VDPU383 power-up priming buffer; see
140+
* rkvdec-rk3576-workaround.c
141+
*/
142+
void *rk3576_warmup_cpu;
143+
dma_addr_t rk3576_warmup_dma;
139144
struct mutex vdev_lock; /* serializes ioctls */
140145
struct delayed_work watchdog_work;
141146
struct gen_pool *sram_pool;
@@ -180,6 +185,9 @@ void rkvdec_run_preamble(struct rkvdec_ctx *ctx, struct rkvdec_run *run);
180185
void rkvdec_run_postamble(struct rkvdec_ctx *ctx, struct rkvdec_run *run);
181186
void rkvdec_memcpy_toio(void __iomem *dst, void *src, size_t len);
182187
void rkvdec_schedule_watchdog(struct rkvdec_dev *rkvdec, u32 timeout_threshold);
188+
int rkvdec_rk3576_warmup_alloc(struct device *dev, void **out_cpu,
189+
dma_addr_t *out_dma);
190+
int rkvdec_rk3576_warmup_run(void __iomem *link_base, dma_addr_t buf_iova);
183191

184192
void rkvdec_quirks_disable_qos(struct rkvdec_ctx *ctx);
185193

0 commit comments

Comments
 (0)