Re: [RFC PATCH v2 6/8] accel/rocket: add RK3576 NPU (RKNN) support
From: Alexey Charkov
Date: Tue Jul 21 2026 - 09:03:26 EST
Hi Jiaxing,
On Sat, Jul 18, 2026 at 7:11 AM Jiaxing Hu <gahing@xxxxxxxxxxxxx> wrote:
>
> Add RK3576 support to the rocket DRM accelerator driver (used with the
> Mesa Teflon TFLite delegate). Per-SoC differences are selected by new
> of_device_id match data (struct rocket_soc_data) so the RK3588 path stays
> unchanged:
>
> - match rockchip,rk3576-rknn-core; iterate its nodes at probe
> - RK3576 takes six clocks (adds the CBUF domain ACLK/HCLK_RKNN_CBUF):
> the CNA fills the CBUF and CORE reads from it, so the compute path
> stalls without them. RK3588 keeps its four clocks.
> - RK3576 requests one reset (srst_a); its BIU reset (srst_h) is driven
> from the power domain. RK3588 keeps both.
> - RK3576 spans two power domains (PD_NPU0 + PD_NPU1) and attaches the
> list explicitly; RK3588 is single-domain and keeps the driver-core
> auto-attach.
> - RK3576 has no maskable completion interrupt (PC_DONE is read-only in
> INTERRUPT_MASK), so it polls PC_DONE via an hrtimer; RK3588 keeps the
> DPU completion IRQ.
> - guard rocket_job_timedout() MMIO behind pm_runtime_active()
>
> Tested on a Radxa ROCK 4D: the NPU probes, powers on, brings up its
> IOMMUs and runs submitted jobs to completion. Full multi-layer inference
> is not yet correct on this SoC (only the first operation per power session
> produces valid output); see the cover letter.
>
> Signed-off-by: Jiaxing Hu <gahing@xxxxxxxxxxxxx>
> ---
> drivers/accel/rocket/rocket_core.c | 39 +++++++-
> drivers/accel/rocket/rocket_core.h | 22 ++++-
> drivers/accel/rocket/rocket_device.c | 4 +
> drivers/accel/rocket/rocket_drv.c | 22 ++++-
> drivers/accel/rocket/rocket_job.c | 127 +++++++++++++++++++++++++--
> 5 files changed, 200 insertions(+), 14 deletions(-)
[...]
> diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
> index 2f1861f96..ce3f7c92e 100644
> --- a/drivers/accel/rocket/rocket_job.c
> +++ b/drivers/accel/rocket/rocket_job.c
> @@ -7,6 +7,7 @@
> #include <drm/drm_file.h>
> #include <drm/drm_gem.h>
> #include <drm/rocket_accel.h>
> +#include <linux/hrtimer.h>
> #include <linux/interrupt.h>
> #include <linux/iommu.h>
> #include <linux/platform_device.h>
> @@ -20,6 +21,16 @@
>
> #define JOB_TIMEOUT_MS 500
>
> +/*
> + * RK3576: INTERRUPT_MASK bits 28-29 are read-only (hardware rejects the write),
> + * so the PC_DONE completion signal cannot be routed to the GIC via the normal
> + * interrupt-mask path. We poll OPERATION_ENABLE every RK3576_POLL_INTERVAL_NS
> + * instead of waiting for a completion IRQ.
> + */
> +#define PC_INTERRUPT_MASK_RK3576_PC_DONE_0 0x10000000u
> +#define PC_INTERRUPT_MASK_RK3576_PC_DONE_1 0x20000000u
> +#define RK3576_POLL_INTERVAL_NS 1000000LL /* 1 ms */
> +
> static struct rocket_job *
> to_rocket_job(struct drm_sched_job *sched_job)
> {
> @@ -137,8 +148,24 @@ static void rocket_job_hw_submit(struct rocket_core *core, struct rocket_job *jo
> rocket_pc_writel(core, REGISTER_AMOUNTS,
> PC_REGISTER_AMOUNTS_PC_DATA_AMOUNT((task->regcmd_count + 1) / 2 - 1));
>
> - rocket_pc_writel(core, INTERRUPT_MASK, PC_INTERRUPT_MASK_DPU_0 | PC_INTERRUPT_MASK_DPU_1);
> - rocket_pc_writel(core, INTERRUPT_CLEAR, PC_INTERRUPT_CLEAR_DPU_0 | PC_INTERRUPT_CLEAR_DPU_1);
> + if (core->soc->poll_completion) {
> + /*
> + * RK3576: PC_DONE (bits 28-29) is read-only in INTERRUPT_MASK, so
> + * it cannot be routed to the GIC; enable the DMA-error interrupts
> + * and poll PC_DONE via the hrtimer started below.
> + */
> + rocket_pc_writel(core, INTERRUPT_MASK,
> + PC_INTERRUPT_MASK_DMA_READ_ERROR |
> + PC_INTERRUPT_MASK_DMA_WRITE_ERROR);
> + rocket_pc_writel(core, INTERRUPT_CLEAR,
> + PC_INTERRUPT_MASK_RK3576_PC_DONE_0 |
> + PC_INTERRUPT_MASK_RK3576_PC_DONE_1);
> + } else {
> + rocket_pc_writel(core, INTERRUPT_MASK,
> + PC_INTERRUPT_MASK_DPU_0 | PC_INTERRUPT_MASK_DPU_1);
> + rocket_pc_writel(core, INTERRUPT_CLEAR,
> + PC_INTERRUPT_CLEAR_DPU_0 | PC_INTERRUPT_CLEAR_DPU_1);
> + }
This whole polled-interrupt part looks very suspicious. Does the
vendor stack do the same thing?
It's hard to believe Rockchip would release performance-critical and
hype-heavy silicon which can't properly raise the most often triggered
interrupt in its critical path, so there must be something else going
on here.
One theory could be that the interrupt bits are not "read-only" per
se, but rather gated inactive by some internal hardware state which
must be cleared before jobs are submitted (e.g. some buffers not
drained, or init sequence not completed, or voltages/frequencies not
set, or memory mappings not written out, ...). If so, this same
hardware state (or something related to it) may well be the cause of
your observed "first (forced) job completes, others don't".
Best regards,
Alexey