* [PATCH v13 01/14] accel/rocket: request the core clocks by name
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 02/14] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
` (12 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu
From: Igor Paunovic <royalnet026@gmail.com>
rocket_core_init() hands core->clks to devm_clk_bulk_get() without ever
setting the .id members. The rocket_core array is allocated with
devm_kcalloc() in rocket_device_init(), and rocket_probe() only fills in
.rdev, .dev and .index, so all four clk_bulk_data entries are requested
with a NULL con_id (unlike core->resets, whose ids are set a few lines
above).
clk_get(dev, NULL) ends up in of_clk_get_hw(np, 0, NULL), and
of_parse_clkspec() only consults "clock-names" when a name was passed, so
the index stays 0 for all four entries. Every entry therefore ends up
holding a handle to the *first* clock of the DT "clocks" property, i.e.
ACLK_NPUn. Nothing fails: probe succeeds and the driver believes it owns
four different clocks.
The consequence is that rocket_device_runtime_resume() prepares and enables
the AXI clock four times, while hclk, pclk and - most importantly - the NPU
compute clock ("npu", SCMI_CLK_NPU on RK3588) are never prepared or enabled
by this driver at all. The NPU still works only because the Rockchip
power-domain driver sets GENPD_FLAG_PM_CLK and its attach_dev() callback
walks the device node with of_clk_get() and adds every clock to the pm_clk
list, so genpd happens to keep the remaining clocks running. The bug is
therefore latent today, but it means the driver holds no reference to the
clock that actually feeds the NPU, which stands in the way of any future
frequency scaling (OPP/devfreq) work.
Found on an Orange Pi 5 Plus (RK3588) by reading the live clock tree:
/sys/kernel/debug/clk/clk_summary shows four "fdab0000.npu" consumer
handles on aclk_npu0 (and likewise on aclk_npu1/aclk_npu2 for the other two
cores), while hclk_npu0, pclk_npu_root and scmi_clk_npu have no
"fdab0000.npu" consumer at all - their only consumers are the
"npu@fdab0000" handles created by the power-domain driver via of_clk_get().
Set the ids explicitly, in the order mandated by the binding
(Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml):
aclk, hclk, npu, pclk. After the change the driver holds one handle per
distinct clock and clk_bulk_prepare_enable() covers all four.
Note that this is a user-visible tightening for out-of-tree DTs: the old
NULL-id requests resolved by index and succeeded no matter what
"clock-names" contained, while the named requests fail probe with -ENOENT
when one of the four names is missing. That is the right outcome for
in-tree users - the binding requires exactly these four clock-names and
rk3588-base.dtsi carries them on all three cores - but a DT that relied on
the permissive lookup goes from silently running on the wrong clock handles
to not probing at all, so record the change here where git log will find
it.
Fixes: ed98261b4168 ("accel/rocket: Add a new driver for Rockchip's NPU")
Signed-off-by: Igor Paunovic <royalnet026@gmail.com>
Tested-by: Sidong Yang <sidong.yang@furiosa.ai>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # NanoPC-T6 LTS, NanoPC-T6 Plus
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Reviewed-by: Jiaxing Hu <gahing@gahingwoo.com>
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
drivers/accel/rocket/rocket_core.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/accel/rocket/rocket_core.c b/drivers/accel/rocket/rocket_core.c
index b3b2fa9ba..5dd260bac 100644
--- a/drivers/accel/rocket/rocket_core.c
+++ b/drivers/accel/rocket/rocket_core.c
@@ -28,6 +28,10 @@ int rocket_core_init(struct rocket_core *core)
if (err)
return dev_err_probe(dev, err, "failed to get resets for core %d\n", core->index);
+ core->clks[0].id = "aclk";
+ core->clks[1].id = "hclk";
+ core->clks[2].id = "npu";
+ core->clks[3].id = "pclk";
err = devm_clk_bulk_get(dev, ARRAY_SIZE(core->clks), core->clks);
if (err)
return dev_err_probe(dev, err, "failed to get clocks for core %d\n", core->index);
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 02/14] accel/rocket: take the completion register writes under job_lock
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 01/14] accel/rocket: request the core clocks by name Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 03/14] accel/rocket: wait for a running IRQ handler before resetting a core Jiaxing Hu
` (11 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu
rocket_job_handle_irq() writes OPERATION_ENABLE and INTERRUPT_CLEAR before
taking job_lock, while rocket_job_hw_submit() writes OPERATION_ENABLE from
inside it. The two can therefore race: a completion being handled on one
core can write its zero after a submit on the same core has written its
one, and stop a task that has only just started.
Nothing in tree hits this often, because the interrupt is the only
completion path and it does not overlap its own submit, but the ordering is
wrong on its own terms.
To be exact about what the lock does and does not buy: a mutex gives mutual
exclusion, not ordering, so it does not by itself stop a zero from landing
after a one. What keeps the ordinary path safe is that the handler signals
the job's done fence before the scheduler can issue the next one. The
reason the writes belong inside the guard is that stopping the block and
deciding what to start next have to be one step, which they were not.
Move both writes inside the existing scoped_guard() rather than adding a
second critical section, so stopping the block and deciding what to start
next are one atomic step.
Fixes: 0810d5ad88a1 ("accel/rocket: Add job submission IOCTL")
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Tested-by: Igor Paunovic <royalnet026@gmail.com> # RK3588, three cores, induced reset, JOB_TIMEOUT_MS=2
---
drivers/accel/rocket/rocket_job.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
index f40435505..575945015 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -345,10 +345,15 @@ static void rocket_job_handle_irq(struct rocket_core *core)
{
pm_runtime_mark_last_busy(core->dev);
- rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
- rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
+ scoped_guard(mutex, &core->job_lock) {
+ /*
+ * Stopping the block belongs under the lock. hw_submit() writes
+ * OPERATION_ENABLE too, and outside the lock this zero can land
+ * after that one and stop a task that has only just started.
+ */
+ rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
+ rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
- scoped_guard(mutex, &core->job_lock)
if (core->in_flight_job) {
if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
rocket_job_hw_submit(core, core->in_flight_job);
@@ -360,6 +365,7 @@ static void rocket_job_handle_irq(struct rocket_core *core)
pm_runtime_put_autosuspend(core->dev);
core->in_flight_job = NULL;
}
+ }
}
static void
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 03/14] accel/rocket: wait for a running IRQ handler before resetting a core
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 01/14] accel/rocket: request the core clocks by name Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 02/14] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-16 13:28 ` Igor Paunovic
2026-09-15 10:43 ` [PATCH v13 04/14] accel/rocket: let the core suspend after a reset Jiaxing Hu
` (10 subsequent siblings)
13 siblings, 1 reply; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu
rocket_reset() calls drm_sched_stop(), which stops the scheduler and
returns. It does not wait for a threaded handler that is already running,
so the comment that follows, "Remaining interrupts have been handled",
states an assumption rather than something the code arranges.
Call synchronize_irq(core->irq) after drm_sched_stop() and reword the
comment to say what holds afterwards.
It has to go before the scoped_guard(mutex, &core->job_lock) rather than
inside it. rocket_job_handle_irq() takes job_lock, so waiting for the
handler while holding that lock would be waiting for a handler that is
waiting for us. Nothing is held at that point: drm_sched_job_timedout()
drops job_list_lock before calling ->timedout_job(), and the only live
caller, rocket_job_timedout(), runs in process context, so sleeping there
is allowed.
This does not stop a handler that has already read in_flight_job from
finishing its work on the job the reset is about to drop. That window needs
the check and the register writes to be one step under the lock, which is
what the previous patch does; the two are complementary.
Mask the block before the sync as well. INTERRUPT_MASK is armed by
hw_submit() on every submit and cleared only by the hardirq, so on an
ordinary timeout it is still live and a completion can arrive after
synchronize_irq() returns. Nothing is lost by clearing it, since the next
submit arms it again.
That mask write goes UNDER job_lock, though the sync does not.
rocket_job_hw_submit() arms the same register and always runs under that
lock, while reset.pending is set here without it and read there with it, so
a submit that has already passed its check can re-arm the mask after this
clears it. The block is then left running a task with its interrupt live
while synchronize_irq() fences a handler that has already finished, which
is the same shape of race the previous patch closes for OPERATION_ENABLE.
pm_runtime_get_if_active() takes a reference on an already-active device
without invoking a callback, and pm_runtime_put_autosuspend() is
asynchronous, so holding job_lock across them cannot re-enter this driver's
runtime PM callbacks.
That write is the first register access this function has ever made, and it
is guarded, because the function holds no runtime PM reference of its own.
The only reference in the window belongs to in_flight_job, and the
completion path can have put it and cleared the pointer before the timeout
worker arrives: drm_sched_stop() sits in between and can block on
cancel_work_sync() and on a dma_fence_wait(), and it subtracts every
pending job's credits, so rocket_job_is_idle() is true and
rocket_device_runtime_suspend() will not refuse. With the autosuspend delay
elapsed the clocks are off and both NPU domains are down. A register access
in that state takes an async SError on this hardware, which is the failure
two later patches in this series describe from the power-on side.
pm_runtime_get_if_active() resumes nothing and allocates nothing; if the
core is already down there is no live interrupt to mask and the following
synchronize_irq() is all that is needed. Only a POSITIVE answer says the
device is active, and that distinction is not cosmetic: the helper tests
power.disable_depth before power.runtime_status, so -EINVAL masks a
suspended device rather than excluding one. pm_runtime_force_suspend(),
which is this driver's own system sleep callback, disables runtime PM first
and turns the clocks off second; rocket_core_fini() suspends the core and
disables before cancelling the timeout worker. Both offer -EINVAL with the
domain down, which is the SError this patch exists to avoid causing.
The mask is written with a clear of the raw status, paired the way the
completion path writes them. Masking alone leaves the DPU bit latched until
rocket_core_reset(), and the hardirq decides on raw status alone, so a
fault from the IOMMU sharing this core's line would wake the thread again
and the guarantee this patch is about would stop holding partway through
the function. Igor Paunovic asked the general form of this on v8 -- whether
rocket_reset() should hold a reference -- and it was deferred then because
nothing in the path touched a register. This patch is what makes it matter.
The deadlock this placement avoids would not have been reported. The wait
is on desc->wait_for_threads rather than on a lock, so lockdep does not
model it and it would have hung silently.
Igor Paunovic ran an induced-reset protocol on RK3588. On 19 and 25 August he
ran it with this patch and the previous one removed as well as applied, so what
those sessions show bounds the pair rather than either one of them; the
12 September session ran two patched arms and no unpatched one, so it re-tests
nothing differential. His own summary, which aggregates all three after he
re-ran the protocol on v12 as posted and corrected his earlier reports:
45 induced resets on 19 August, 102 on 25 August and 74 on 12 September,
every reset recovered, no MMU faults, no lockdep report from rocket or the
scheduler in the runs where lockdep was still armed, and of the 420
inferences scored, 384 matched the CPU reference within 1 on all 48 output
channels while 36 returned the all-0x80 buffer of a job the reset had
cancelled. The protocol bounds; it does not prove.
The all-0x80 buffer is not a differential signal. rocket_reset() calls
drm_sched_stop(), drm_sched_start() then completes the detached jobs with
-ECANCELED, and PREP_BO drops the fence error, so that buffer is what a
cancelled job looks like from userspace whatever made it miss its deadline.
Link: https://lore.kernel.org/all/20260819073530.6087-1-royalnet026@gmail.com/
Link: https://lore.kernel.org/all/CAEWPSH5mxTbUkNouxm6yecMZYvDowquhvYvhaXQ8HoMtHD5U1g@mail.gmail.com/
Link: https://lore.kernel.org/all/20260912113717.6819-1-royalnet026@gmail.com/
Suggested-by: Igor Paunovic <royalnet026@gmail.com>
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Tested-by: Igor Paunovic <royalnet026@gmail.com> # RK3588, three cores, induced reset, JOB_TIMEOUT_MS=2
---
drivers/accel/rocket/rocket_job.c | 65 +++++++++++++++++++++++++++++--
1 file changed, 62 insertions(+), 3 deletions(-)
diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
index 575945015..dfe9135d8 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -377,9 +377,68 @@ rocket_reset(struct rocket_core *core, struct drm_sched_job *bad)
drm_sched_stop(&core->sched, bad);
/*
- * Remaining interrupts have been handled, but we might still have
- * stuck jobs. Let's make sure the PM counters stay balanced by
- * manually calling pm_runtime_put_noidle().
+ * Mask the block before waiting. hw_submit() arms INTERRUPT_MASK on
+ * every submit and only the hardirq clears it, so on an ordinary
+ * timeout it is still live and a completion can arrive after the sync
+ * returns. The next submit re-arms it, so nothing is lost here.
+ *
+ * Only when the device is already awake, though. This function holds no
+ * runtime PM reference of its own: the only one in the window belongs to
+ * in_flight_job, and the completion path may have put it and cleared the
+ * pointer before the timeout worker got here. drm_sched_stop() above can
+ * block for a long time, and it drops every pending job's credits, so
+ * rocket_job_is_idle() is true and nothing keeps the core resumed. On
+ * this hardware a register access with the domain down takes an async
+ * SError, so a reset must not be the thing that causes one.
+ *
+ * Only a positive answer will do. pm_runtime_get_if_active() tests
+ * power.disable_depth before power.runtime_status, so -EINVAL MASKS a
+ * suspended device rather than excluding one: pm_runtime_force_suspend(),
+ * which is this driver's own system suspend callback, disables runtime PM
+ * first and turns the clocks off second, and rocket_core_fini() suspends
+ * the core and disables before it cancels the timeout worker. Both leave
+ * the domain down with -EINVAL on offer.
+ *
+ * Clear the raw status along with the mask, the way the completion path
+ * does. Masking alone leaves the DPU bit latched until
+ * rocket_core_reset(), and the hardirq decides on raw status alone, so a
+ * fault from the IOMMU that shares this line would wake the thread again
+ * and what the comment below asserts would stop being true.
+ *
+ * UNDER job_lock, because rocket_job_hw_submit() arms this same
+ * register and always runs under that lock. reset.pending is set here
+ * without the lock and read there with it, so a submit that has already
+ * passed its check can re-arm the mask after this clears it, and then
+ * the synchronize_irq() below fences a handler that is no longer the
+ * one that matters: the block is left running a task with its
+ * interrupt live. That is the same race the previous patch took the
+ * completion writes under this lock to close, on the other register.
+ *
+ * pm_runtime_get_if_active() does not invoke a callback -- it only
+ * takes a reference on an already-active device -- and
+ * pm_runtime_put_autosuspend() is asynchronous, so neither can re-enter
+ * this driver's runtime PM callbacks while the lock is held.
+ */
+ scoped_guard(mutex, &core->job_lock) {
+ if (pm_runtime_get_if_active(core->dev) > 0) {
+ rocket_pc_writel(core, INTERRUPT_MASK, 0x0);
+ rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
+ pm_runtime_put_autosuspend(core->dev);
+ }
+ }
+
+ /*
+ * drm_sched_stop() returns without waiting for a threaded handler that
+ * is already running, so wait for one here. This has to stay outside
+ * job_lock: the handler takes that lock, so waiting for it while
+ * holding it would deadlock instead of fencing anything.
+ */
+ synchronize_irq(core->irq);
+
+ /*
+ * No handler is running now, but we might still have stuck jobs. Let's
+ * make sure the PM counters stay balanced by manually calling
+ * pm_runtime_put_noidle().
*/
scoped_guard(mutex, &core->job_lock) {
if (core->in_flight_job)
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [PATCH v13 03/14] accel/rocket: wait for a running IRQ handler before resetting a core
2026-09-15 10:43 ` [PATCH v13 03/14] accel/rocket: wait for a running IRQ handler before resetting a core Jiaxing Hu
@ 2026-09-16 13:28 ` Igor Paunovic
0 siblings, 0 replies; 16+ messages in thread
From: Igor Paunovic @ 2026-09-16 13:28 UTC (permalink / raw)
To: Jiaxing Hu
Cc: Tomeu Vizoso, Heiko Stuebner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Joerg Roedel, Will Deacon, Robin Murphy,
Ulf Hansson, Philipp Zabel, Oded Gabbay, Elaine Zhang, Abel Vesa,
Sebastian Reichel, Sidong Yang, Uwe Kleine-König,
Chaoyi Chen, Diederik de Haas, Alexey Charkov, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Igor Paunovic
Hi Jiaxing,
Thanks for flagging the job_lock change in the cover. I re-ran the
same protocol today on v13 as posted, 2+3 and 2+3+4, three runs each,
same board, base and config as on 12 September (JOB_TIMEOUT_MS=2,
PROVE_LOCKING, DEBUG_ATOMIC_SLEEP): 73 induced resets with nothing
from the driver beyond the timeout message itself, no MMU faults, no
lockdep report or warning, and lockdep still armed at the end on both
kernels. Of the 126 inferences scored, 118 matched the CPU reference
within 1 on all 48 channels, and 8 missed on all 48 with the channels
the scorer prints reading 0x80; in the two runs traced with the
kprobe, those 5 results sit exactly where the 5 -ECANCELED completions
do.
The limits are those of my 12 September mail (all resets on core 0,
one client), plus one more: the model is a single 1x1 convolution,
which Mesa submits as a one-task job (one completion interrupt per
good inference), so hw_submit() never ran from the IRQ thread, which
the race you describe needs. So this shows the lock adds no lockdep
report or hang on this path, not that it closes the race. On that
basis the tag stands for the v13 form of 3/14, with the same comment:
Tested-by: Igor Paunovic <royalnet026@gmail.com> # RK3588, three cores, induced reset, JOB_TIMEOUT_MS=2
If 4/14 changes shape in v14, I will re-run that arm. The scripts and
the tallies were prepared with an LLM assistant; every number above
was counted from the raw files of each run (scorer output, kernel and
serial logs, kprobe trace), not from the scripts' summaries.
Regards,
Igor
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v13 04/14] accel/rocket: let the core suspend after a reset
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (2 preceding siblings ...)
2026-09-15 10:43 ` [PATCH v13 03/14] accel/rocket: wait for a running IRQ handler before resetting a core Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 05/14] accel/rocket: factor the completion tail out of the IRQ handler Jiaxing Hu
` (9 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu
rocket_reset() drops the in-flight job's runtime PM reference with
pm_runtime_put_noidle(), a bare decrement that requests nothing. The core
is left at usage_count 0 but still runtime-active with no idle request
pending, so it does not suspend until something else asks, and on a
platform whose power domain does work on power-on that work never happens.
On RK3576 that work is a bus interface reset the domain cycles when it
comes up. Without it the NPU's IOMMU stops answering, and the job after a
timeout returns a surface of the output zero point with rk_iommu reporting
that MMU_DTE_ADDR is not functioning.
Measured on a ROCK 4D in one boot, three runs, one variable between them.
With the bare put the core reads runtime-active with its rail still up
after the reset, the IOMMU reports the failure on the next attach and the
inference returns 0 of 128 channels. With the reference put back through
pm_runtime_put_autosuspend() the core reads suspended with the rail down,
there is no IOMMU message, and the same inference returns 128 of 128. A
third run repeating the first failed the same way.
It also matches the put in the completion path a few lines away, so the
reset path no longer leaves the device in a state the rest of the driver
never produces. The remaining put, on the error path in rocket_job_run(),
is a plain pm_runtime_put() and is left alone here: it unwinds a
pm_runtime_resume_and_get() that never reached the hardware, and changing
it belongs in its own patch.
Igor Paunovic ran the differential on RK3588: 45 induced resets, all of
them on core 0 with the other two cores bound but idle in his single
client protocol, with and without the two preceding patches, and the
domain dropped every single time with no MMU message on either kernel. So
this is not rocket-wide. His conditions cross a healthy block with a lowered
timeout rather than a hung one, which he was careful to say his protocol
cannot settle, but it is what scopes the change to RK3576.
Link: https://lore.kernel.org/all/20260819073530.6087-1-royalnet026@gmail.com/
Fixes: 0810d5ad88a1 ("accel/rocket: Add job submission IOCTL")
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Tested-by: Igor Paunovic <royalnet026@gmail.com> # RK3588, three cores, induced reset, JOB_TIMEOUT_MS=2
---
drivers/accel/rocket/rocket_job.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
index dfe9135d8..2a0b8af6f 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -437,12 +437,12 @@ rocket_reset(struct rocket_core *core, struct drm_sched_job *bad)
/*
* No handler is running now, but we might still have stuck jobs. Let's
- * make sure the PM counters stay balanced by manually calling
- * pm_runtime_put_noidle().
+ * make sure the PM counters stay balanced by putting the reference the
+ * job took, and request idle while doing it so the core can suspend.
*/
scoped_guard(mutex, &core->job_lock) {
if (core->in_flight_job)
- pm_runtime_put_noidle(core->dev);
+ pm_runtime_put_autosuspend(core->dev);
iommu_detach_group(NULL, core->iommu_group);
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 05/14] accel/rocket: factor the completion tail out of the IRQ handler
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (3 preceding siblings ...)
2026-09-15 10:43 ` [PATCH v13 04/14] accel/rocket: let the core suspend after a reset Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 06/14] dt-bindings: npu: rockchip: add rockchip,rk3576-rknn-core Jiaxing Hu
` (8 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu
rocket_job_handle_irq() stops the block and then either starts the job's
next task or retires the job. The second half is a step of its own and
reads better with a name, now that taking the register writes under
job_lock has moved it a level deeper inside the scoped guard.
Move it to rocket_job_next_locked(). The early return that used to leave
the handler now leaves the helper, which is the same thing here: the scoped
guard drops job_lock either way and nothing follows it.
Doing it as its own patch keeps the locking fix at the head of the series
minimal, so a bisect that stops before this one gets that fix and nothing
else. There is one caller, and no functional change.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Reviewed-by: Igor Paunovic <royalnet026@gmail.com>
---
drivers/accel/rocket/rocket_job.c | 31 ++++++++++++++++++++-----------
1 file changed, 20 insertions(+), 11 deletions(-)
diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
index 2a0b8af6f..54f9c299d 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -341,6 +341,25 @@ static struct dma_fence *rocket_job_run(struct drm_sched_job *sched_job)
return ERR_PTR(ret);
}
+/* Start the job's next task, or retire it. Caller holds job_lock. */
+static void rocket_job_next_locked(struct rocket_core *core)
+{
+ lockdep_assert_held(&core->job_lock);
+
+ if (!core->in_flight_job)
+ return;
+
+ if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
+ rocket_job_hw_submit(core, core->in_flight_job);
+ return;
+ }
+
+ iommu_detach_group(NULL, iommu_group_get(core->dev));
+ dma_fence_signal(core->in_flight_job->done_fence);
+ pm_runtime_put_autosuspend(core->dev);
+ core->in_flight_job = NULL;
+}
+
static void rocket_job_handle_irq(struct rocket_core *core)
{
pm_runtime_mark_last_busy(core->dev);
@@ -354,17 +373,7 @@ static void rocket_job_handle_irq(struct rocket_core *core)
rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
- if (core->in_flight_job) {
- if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
- rocket_job_hw_submit(core, core->in_flight_job);
- return;
- }
-
- iommu_detach_group(NULL, iommu_group_get(core->dev));
- dma_fence_signal(core->in_flight_job->done_fence);
- pm_runtime_put_autosuspend(core->dev);
- core->in_flight_job = NULL;
- }
+ rocket_job_next_locked(core);
}
}
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 06/14] dt-bindings: npu: rockchip: add rockchip,rk3576-rknn-core
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (4 preceding siblings ...)
2026-09-15 10:43 ` [PATCH v13 05/14] accel/rocket: factor the completion tail out of the IRQ handler Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 07/14] dt-bindings: power: rockchip: allow resets in a power domain node Jiaxing Hu
` (7 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu, Krzysztof Kozlowski
The RK3576 NPU has two cores of the same RKNN block the RK3588 binding
already describes, but it wires them up differently: two extra CBUF clocks,
two power domains per core, and a single reset instead of two. It also has
no NPU SRAM supply.
Widen the property ranges to cover both, then pin each SoC back to its own
shape in allOf so nothing loosens for RK3588, and keep sram-supply required
for rockchip,rk3588-rknn-core only.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
.../npu/rockchip,rk3588-rknn-core.yaml | 47 +++++++++++++++++--
1 file changed, 44 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml b/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml
index caca2a490..3b611b64c 100644
--- a/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml
+++ b/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml
@@ -21,6 +21,7 @@ properties:
compatible:
enum:
+ - rockchip,rk3576-rknn-core
- rockchip,rk3588-rknn-core
reg:
@@ -33,14 +34,18 @@ properties:
- const: core # Main NPU core processing unit registers
clocks:
- maxItems: 4
+ minItems: 4
+ maxItems: 6
clock-names:
+ minItems: 4
items:
- const: aclk
- const: hclk
- const: npu
- const: pclk
+ - const: aclk_cbuf
+ - const: hclk_cbuf
interrupts:
maxItems: 1
@@ -51,12 +56,15 @@ properties:
npu-supply: true
power-domains:
- maxItems: 1
+ minItems: 1
+ maxItems: 2
resets:
+ minItems: 1
maxItems: 2
reset-names:
+ minItems: 1
items:
- const: srst_a
- const: srst_h
@@ -75,7 +83,40 @@ required:
- resets
- reset-names
- npu-supply
- - sram-supply
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: rockchip,rk3588-rknn-core
+ then:
+ properties:
+ clocks:
+ maxItems: 4
+ clock-names:
+ maxItems: 4
+ power-domains:
+ maxItems: 1
+ resets:
+ minItems: 2
+ reset-names:
+ minItems: 2
+ required:
+ - sram-supply
+ else:
+ properties:
+ clocks:
+ minItems: 6
+ clock-names:
+ minItems: 6
+ power-domains:
+ minItems: 2
+ resets:
+ maxItems: 1
+ reset-names:
+ maxItems: 1
+ sram-supply: false
additionalProperties: false
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 07/14] dt-bindings: power: rockchip: allow resets in a power domain node
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (5 preceding siblings ...)
2026-09-15 10:43 ` [PATCH v13 06/14] dt-bindings: npu: rockchip: add rockchip,rk3576-rknn-core Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 08/14] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU Jiaxing Hu
` (6 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu, Conor Dooley
Some domains do not come up in a usable state on their own and need their
resets cycled once power is on. The RK3576 NPU domains are one
case: without it the first access after power-on takes an async SError.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
---
.../bindings/power/rockchip,power-controller.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/power/rockchip,power-controller.yaml b/Documentation/devicetree/bindings/power/rockchip,power-controller.yaml
index b41db576f..83741f048 100644
--- a/Documentation/devicetree/bindings/power/rockchip,power-controller.yaml
+++ b/Documentation/devicetree/bindings/power/rockchip,power-controller.yaml
@@ -136,6 +136,13 @@ $defs:
A number of phandles to clocks that need to be enabled
while power domain switches state.
+ resets:
+ maxItems: 1
+ description:
+ A phandle to a reset that needs to be cycled once the power domain has
+ been switched on, for domains whose logic does not come up in a usable
+ state by itself.
+
domain-supply:
description: domain regulator supply.
@@ -216,6 +223,7 @@ examples:
reg = <RK3399_PD_IEP>;
clocks = <&cru ACLK_IEP>,
<&cru HCLK_IEP>;
+ resets = <&cru SRST_A_IEP>;
pm_qos = <&qos_iep>;
#power-domain-cells = <0>;
};
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 08/14] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (6 preceding siblings ...)
2026-09-15 10:43 ` [PATCH v13 07/14] dt-bindings: power: rockchip: allow resets in a power domain node Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 09/14] pmdomain: rockchip: add optional per-domain power-on settle delay Jiaxing Hu
` (5 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu, Conor Dooley
The RK3576 NPU MMUs are rk3568-iommu compatible but take five clocks where
every other Rockchip MMU takes two, the extra three being the compute clock
and the two convolution buffer clocks.
Give them a compatible of their own and pin both sides with an allOf, so
that an rk3568-iommu cannot carry five clocks and an NPU MMU cannot carry
two. Describing the extra clocks as belonging to one SoC without saying so
in the schema, which is what a comment on a description does, leaves both
of those spellings valid.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
---
.../bindings/iommu/rockchip,iommu.yaml | 28 +++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/Documentation/devicetree/bindings/iommu/rockchip,iommu.yaml b/Documentation/devicetree/bindings/iommu/rockchip,iommu.yaml
index 6ce41d11f..83d7e7c8e 100644
--- a/Documentation/devicetree/bindings/iommu/rockchip,iommu.yaml
+++ b/Documentation/devicetree/bindings/iommu/rockchip,iommu.yaml
@@ -26,6 +26,7 @@ properties:
- items:
- enum:
- rockchip,rk3576-iommu
+ - rockchip,rk3576-npu-iommu
- rockchip,rk3588-iommu
- const: rockchip,rk3568-iommu
@@ -42,14 +43,22 @@ properties:
minItems: 1
clocks:
+ minItems: 2
items:
- description: Core clock
- description: Interface clock
+ - description: Compute clock
+ - description: Convolution buffer core clock
+ - description: Convolution buffer interface clock
clock-names:
+ minItems: 2
items:
- const: aclk
- const: iface
+ - const: npu
+ - const: aclk_cbuf
+ - const: hclk_cbuf
"#iommu-cells":
const: 0
@@ -72,6 +81,25 @@ required:
- clock-names
- "#iommu-cells"
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: rockchip,rk3576-npu-iommu
+ then:
+ properties:
+ clocks:
+ minItems: 5
+ clock-names:
+ minItems: 5
+ else:
+ properties:
+ clocks:
+ maxItems: 2
+ clock-names:
+ maxItems: 2
+
additionalProperties: false
examples:
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 09/14] pmdomain: rockchip: add optional per-domain power-on settle delay
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (7 preceding siblings ...)
2026-09-15 10:43 ` [PATCH v13 08/14] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 10/14] pmdomain: rockchip: cycle optional power-domain resets on power-on Jiaxing Hu
` (4 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu
The RK3576 NPU domains need a short settle time after the idle request is
released before the registers behind the domain answer. Without it the QoS
writes that rockchip_pmu_restore_qos() issues land while the domain is
still coming up, and the NPU throws an async SError on the first cold
power-on.
Give rockchip_domain_info an optional delay_us and wait for it between
releasing idle and restoring QoS. Rename DOMAIN_M_O_R_G to
DOMAIN_M_O_R_G_W for the settle delay it now carries; RK3576 is its only
user, so the old spelling is not kept around.
The suffix scheme does not survive this cleanly and it is worth saying so
rather than leaving it for review. The letters name fields, but R already
means two things in this family: DOMAIN_M_O_R takes r_status and r_offset
AND a regulator under one R, while DOMAIN_M_R has no repair fields and its
R is the regulator. DOMAIN_M_O_R_G's R was the repair pair -- it did not
set need_regulator at all -- so the paragraph below, which gives it one,
adds a field the name does not mention. Naming it _W_R or _RG would be
inventing a convention rather than following one. Say which you would
rather have.
While the macro is being rewritten, give it the regulator argument that
DOMAIN_M_O_R and DOMAIN_M_R already take. Without .need_regulator set,
rockchip_pd_regulator_enable() returns early for every RK3576 domain, so a
domain-supply in the device tree is never looked up and never enabled. Add
a DOMAIN_RK3576_R spelling that passes true and use it for RK3576_PD_NPU,
which is the one RK3576 domain with a rail of its own; every other domain
passes false and is unchanged.
Two things beyond the delay, both worth saying out loud because neither is
what the subject describes.
RK3576_PD_NPU also gets need_regulator here, which makes
rockchip_pm_add_one_domain() call rockchip_pd_power(pd, false) at probe on
all thirteen in-tree rk3576 boards. Only rock-4d enables an NPU core, and
it is the one that declares the supply, so on the other twelve ->power_on
never runs and the regulator is never looked up at all. It is deliberate,
and it is what the delay is for:
without the domain being forced off at probe, a bootloader that leaves the
NPU powered means ->power_on is never called and neither the delay nor
10/14's reset pulse ever runs.
This is not new behaviour in this driver. RK3588_PD_NPU has carried
need_regulator since it was added and its req_mask is 0 exactly as this
one's is, so rockchip_pd_power(pd, false) has been running at probe on every
rk3588 board, with rockchip_pmu_set_idle_request() returning immediately
because there is no request to make. Of the forty-nine in-tree rk3588 board
files, twenty-one enable an NPU core and declare the supply, twenty-six do
not enable one at all so ->power_on never runs for them, and two enable it
with no supply: quartzpro64 and youyeetoo-yy3588 take the dummy regulator
and the one line dev_warn from the regulator core today. The idle
handshake lives in the child domains and on both SoCs they keep it:
NPUTOP, NPU0 and NPU1 carry req and idle masks of their own.
If that is too much for one patch, say so and it splits.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
drivers/pmdomain/rockchip/pm-domains.c | 56 ++++++++++++++++----------
1 file changed, 34 insertions(+), 22 deletions(-)
diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c
index ba66ae719..39988efd8 100644
--- a/drivers/pmdomain/rockchip/pm-domains.c
+++ b/drivers/pmdomain/rockchip/pm-domains.c
@@ -18,6 +18,7 @@
#include <linux/of_address.h>
#include <linux/of_clk.h>
#include <linux/clk.h>
+#include <linux/delay.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/mfd/syscon.h>
@@ -59,6 +60,7 @@ struct rockchip_domain_info {
u32 pwr_offset;
u32 mem_offset;
u32 req_offset;
+ u32 delay_us;
};
struct rockchip_pmu_info {
@@ -185,7 +187,7 @@ struct rockchip_pmu {
.need_regulator = regulator, \
}
-#define DOMAIN_M_O_R_G(_name, p_offset, pwr, status, m_offset, m_status, r_status, r_offset, req, idle, ack, g_mask, wakeup) \
+#define DOMAIN_M_O_R_G_W(_name, p_offset, pwr, status, m_offset, m_status, r_status, r_offset, req, idle, ack, g_mask, delay, wakeup, regulator) \
{ \
.name = _name, \
.pwr_offset = p_offset, \
@@ -200,8 +202,10 @@ struct rockchip_pmu {
.req_mask = (req), \
.idle_mask = (idle), \
.clk_ungate_mask = (g_mask), \
+ .delay_us = (delay), \
.ack_mask = (ack), \
.active_wakeup = wakeup, \
+ .need_regulator = regulator, \
}
#define DOMAIN_M_R(_name, pwr, status, req, idle, ack, wakeup, regulator) \
@@ -258,8 +262,11 @@ struct rockchip_pmu {
#define DOMAIN_RK3568(name, pwr, req, wakeup, regulator) \
DOMAIN_M_R(name, pwr, pwr, req, req, req, wakeup, regulator)
-#define DOMAIN_RK3576(name, p_offset, pwr, status, r_status, r_offset, req, idle, g_mask, wakeup) \
- DOMAIN_M_O_R_G(name, p_offset, pwr, status, 0, r_status, r_status, r_offset, req, idle, idle, g_mask, wakeup)
+#define DOMAIN_RK3576(name, p_offset, pwr, status, r_status, r_offset, req, idle, g_mask, delay, wakeup) \
+ DOMAIN_M_O_R_G_W(name, p_offset, pwr, status, 0, r_status, r_status, r_offset, req, idle, idle, g_mask, delay, wakeup, false)
+
+#define DOMAIN_RK3576_R(name, p_offset, pwr, status, r_status, r_offset, req, idle, g_mask, delay, wakeup) \
+ DOMAIN_M_O_R_G_W(name, p_offset, pwr, status, 0, r_status, r_status, r_offset, req, idle, idle, g_mask, delay, wakeup, true)
/*
* Dynamic Memory Controller may need to coordinate with us -- see
@@ -681,6 +688,10 @@ static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on)
if (ret < 0)
goto out;
+ /* Some domains need to settle before the QoS registers answer. */
+ if (pd->info->delay_us)
+ udelay(pd->info->delay_us);
+
rockchip_pmu_restore_qos(pd);
}
@@ -1300,25 +1311,26 @@ static const struct rockchip_domain_info rk3568_pm_domains[] = {
};
static const struct rockchip_domain_info rk3576_pm_domains[] = {
- [RK3576_PD_NPU] = DOMAIN_RK3576("npu", 0x0, BIT(0), BIT(0), 0, 0x0, 0, 0, 0, false),
- [RK3576_PD_NVM] = DOMAIN_RK3576("nvm", 0x0, BIT(6), 0, BIT(6), 0x4, BIT(2), BIT(18), BIT(2), false),
- [RK3576_PD_SDGMAC] = DOMAIN_RK3576("sdgmac", 0x0, BIT(7), 0, BIT(7), 0x4, BIT(1), BIT(17), 0x6, false),
- [RK3576_PD_AUDIO] = DOMAIN_RK3576("audio", 0x0, BIT(8), 0, BIT(8), 0x4, BIT(0), BIT(16), BIT(0), false),
- [RK3576_PD_PHP] = DOMAIN_RK3576("php", 0x0, BIT(9), 0, BIT(9), 0x0, BIT(15), BIT(15), BIT(15), false),
- [RK3576_PD_SUBPHP] = DOMAIN_RK3576("subphp", 0x0, BIT(10), 0, BIT(10), 0x0, 0, 0, 0, false),
- [RK3576_PD_VOP] = DOMAIN_RK3576("vop", 0x0, BIT(11), 0, BIT(11), 0x0, 0x6000, 0x6000, 0x6000, false),
- [RK3576_PD_VO1] = DOMAIN_RK3576("vo1", 0x0, BIT(14), 0, BIT(14), 0x0, BIT(12), BIT(12), 0x7000, false),
- [RK3576_PD_VO0] = DOMAIN_RK3576("vo0", 0x0, BIT(15), 0, BIT(15), 0x0, BIT(11), BIT(11), 0x6800, false),
- [RK3576_PD_USB] = DOMAIN_RK3576("usb", 0x4, BIT(0), 0, BIT(16), 0x0, BIT(10), BIT(10), 0x6400, true),
- [RK3576_PD_VI] = DOMAIN_RK3576("vi", 0x4, BIT(1), 0, BIT(17), 0x0, BIT(9), BIT(9), BIT(9), false),
- [RK3576_PD_VEPU0] = DOMAIN_RK3576("vepu0", 0x4, BIT(2), 0, BIT(18), 0x0, BIT(7), BIT(7), 0x280, false),
- [RK3576_PD_VEPU1] = DOMAIN_RK3576("vepu1", 0x4, BIT(3), 0, BIT(19), 0x0, BIT(8), BIT(8), BIT(8), false),
- [RK3576_PD_VDEC] = DOMAIN_RK3576("vdec", 0x4, BIT(4), 0, BIT(20), 0x0, BIT(6), BIT(6), BIT(6), false),
- [RK3576_PD_VPU] = DOMAIN_RK3576("vpu", 0x4, BIT(5), 0, BIT(21), 0x0, BIT(5), BIT(5), BIT(5), false),
- [RK3576_PD_NPUTOP] = DOMAIN_RK3576("nputop", 0x4, BIT(6), 0, BIT(22), 0x0, 0x18, 0x18, 0x18, false),
- [RK3576_PD_NPU0] = DOMAIN_RK3576("npu0", 0x4, BIT(7), 0, BIT(23), 0x0, BIT(1), BIT(1), 0x1a, false),
- [RK3576_PD_NPU1] = DOMAIN_RK3576("npu1", 0x4, BIT(8), 0, BIT(24), 0x0, BIT(2), BIT(2), 0x1c, false),
- [RK3576_PD_GPU] = DOMAIN_RK3576("gpu", 0x4, BIT(9), 0, BIT(25), 0x0, BIT(0), BIT(0), BIT(0), false),
+ /* name p_offset pwr status r_status r_offset req idle g_mask delay wakeup */
+ [RK3576_PD_NPU] = DOMAIN_RK3576_R("npu", 0x0, BIT(0), BIT(0), 0, 0x0, 0, 0, 0, 0, false),
+ [RK3576_PD_NVM] = DOMAIN_RK3576("nvm", 0x0, BIT(6), 0, BIT(6), 0x4, BIT(2), BIT(18), BIT(2), 0, false),
+ [RK3576_PD_SDGMAC] = DOMAIN_RK3576("sdgmac", 0x0, BIT(7), 0, BIT(7), 0x4, BIT(1), BIT(17), 0x6, 0, false),
+ [RK3576_PD_AUDIO] = DOMAIN_RK3576("audio", 0x0, BIT(8), 0, BIT(8), 0x4, BIT(0), BIT(16), BIT(0), 0, false),
+ [RK3576_PD_PHP] = DOMAIN_RK3576("php", 0x0, BIT(9), 0, BIT(9), 0x0, BIT(15), BIT(15), BIT(15), 0, false),
+ [RK3576_PD_SUBPHP] = DOMAIN_RK3576("subphp", 0x0, BIT(10), 0, BIT(10), 0x0, 0, 0, 0, 0, false),
+ [RK3576_PD_VOP] = DOMAIN_RK3576("vop", 0x0, BIT(11), 0, BIT(11), 0x0, 0x6000, 0x6000, 0x6000, 0, false),
+ [RK3576_PD_VO1] = DOMAIN_RK3576("vo1", 0x0, BIT(14), 0, BIT(14), 0x0, BIT(12), BIT(12), 0x7000, 0, false),
+ [RK3576_PD_VO0] = DOMAIN_RK3576("vo0", 0x0, BIT(15), 0, BIT(15), 0x0, BIT(11), BIT(11), 0x6800, 0, false),
+ [RK3576_PD_USB] = DOMAIN_RK3576("usb", 0x4, BIT(0), 0, BIT(16), 0x0, BIT(10), BIT(10), 0x6400, 0, true),
+ [RK3576_PD_VI] = DOMAIN_RK3576("vi", 0x4, BIT(1), 0, BIT(17), 0x0, BIT(9), BIT(9), BIT(9), 0, false),
+ [RK3576_PD_VEPU0] = DOMAIN_RK3576("vepu0", 0x4, BIT(2), 0, BIT(18), 0x0, BIT(7), BIT(7), 0x280, 0, false),
+ [RK3576_PD_VEPU1] = DOMAIN_RK3576("vepu1", 0x4, BIT(3), 0, BIT(19), 0x0, BIT(8), BIT(8), BIT(8), 0, false),
+ [RK3576_PD_VDEC] = DOMAIN_RK3576("vdec", 0x4, BIT(4), 0, BIT(20), 0x0, BIT(6), BIT(6), BIT(6), 0, false),
+ [RK3576_PD_VPU] = DOMAIN_RK3576("vpu", 0x4, BIT(5), 0, BIT(21), 0x0, BIT(5), BIT(5), BIT(5), 0, false),
+ [RK3576_PD_NPUTOP] = DOMAIN_RK3576("nputop", 0x4, BIT(6), 0, BIT(22), 0x0, 0x18, 0x18, 0x18, 15, false),
+ [RK3576_PD_NPU0] = DOMAIN_RK3576("npu0", 0x4, BIT(7), 0, BIT(23), 0x0, BIT(1), BIT(1), 0x1a, 15, false),
+ [RK3576_PD_NPU1] = DOMAIN_RK3576("npu1", 0x4, BIT(8), 0, BIT(24), 0x0, BIT(2), BIT(2), 0x1c, 15, false),
+ [RK3576_PD_GPU] = DOMAIN_RK3576("gpu", 0x4, BIT(9), 0, BIT(25), 0x0, BIT(0), BIT(0), BIT(0), 0, false),
};
static const struct rockchip_domain_info rk3588_pm_domains[] = {
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 10/14] pmdomain: rockchip: cycle optional power-domain resets on power-on
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (8 preceding siblings ...)
2026-09-15 10:43 ` [PATCH v13 09/14] pmdomain: rockchip: add optional per-domain power-on settle delay Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 11/14] accel/rocket: select the per-core clock and reset counts from match data Jiaxing Hu
` (3 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu
Some Rockchip domains come out of power-on with their bus interface in an
undefined state. On the RK3576 NPU this shows up as a hang on the first
register access after the domain is switched on, and pulsing the domain's
resets at this point clears it.
Take the domain node's resets if it has any, and pulse them between
releasing idle and restoring QoS. The resets are optional, so domains that
do not list any are unaffected.
The cycle goes before the settle delay 9/14 adds, not after it. A domain
that asks for both is asking to settle before the QoS registers answer, and
a reset deasserted after the delay would leave nothing between the deassert
and rockchip_pmu_restore_qos(). On RK3576 PD_NPU0 and PD_NPU1 ask for both,
and the reset they cycle is SRST_A_RKNN0/1_BIU, the bus interface those QoS
writes go through.
It only runs when the domain actually changes state: rockchip_pd_power()
returns early when the hardware already reads the state being asked for. A
bootloader that leaves the NPU powered would therefore skip both this and
the delay, which is why 9/14 gives RK3576_PD_NPU need_regulator and forces
the domain off at probe.
No in-tree DTS puts resets in a power-domain node today, so every other
Rockchip SoC takes the optional get's NULL and is unchanged.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
drivers/pmdomain/rockchip/pm-domains.c | 27 ++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c
index 39988efd8..6cc8d6efd 100644
--- a/drivers/pmdomain/rockchip/pm-domains.c
+++ b/drivers/pmdomain/rockchip/pm-domains.c
@@ -19,6 +19,7 @@
#include <linux/of_clk.h>
#include <linux/clk.h>
#include <linux/delay.h>
+#include <linux/reset.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/mfd/syscon.h>
@@ -103,6 +104,7 @@ struct rockchip_pm_domain {
struct clk_bulk_data *clks;
struct device_node *node;
struct regulator *supply;
+ struct reset_control *resets;
};
struct rockchip_pmu {
@@ -688,6 +690,21 @@ static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on)
if (ret < 0)
goto out;
+ /*
+ * Optional: some domains need their resets cycled once power
+ * is on. This goes BEFORE the settle delay, not after: a
+ * domain that asks for both is asking to settle before the
+ * QoS registers answer, and a reset deasserted after the
+ * delay would leave nothing between it and the QoS writes.
+ * On RK3576 the reset being cycled is the NPU core's bus
+ * interface, which is what those writes go through.
+ */
+ if (pd->resets) {
+ reset_control_assert(pd->resets);
+ usleep_range(10, 20);
+ reset_control_deassert(pd->resets);
+ }
+
/* Some domains need to settle before the QoS registers answer. */
if (pd->info->delay_us)
udelay(pd->info->delay_us);
@@ -861,6 +878,14 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
if (error)
goto err_put_clocks;
+ pd->resets = of_reset_control_array_get_optional_exclusive(node);
+ if (IS_ERR(pd->resets)) {
+ error = dev_err_probe(pmu->dev, PTR_ERR(pd->resets),
+ "%pOFn: failed to get resets\n", node);
+ pd->resets = NULL;
+ goto err_unprepare_clocks;
+ }
+
pd->num_qos = of_count_phandle_with_args(node, "pm_qos",
NULL);
@@ -931,6 +956,7 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
clk_bulk_unprepare(pd->num_clks, pd->clks);
err_put_clocks:
clk_bulk_put(pd->num_clks, pd->clks);
+ reset_control_put(pd->resets);
return error;
}
@@ -949,6 +975,7 @@ static void rockchip_pm_remove_one_domain(struct rockchip_pm_domain *pd)
clk_bulk_unprepare(pd->num_clks, pd->clks);
clk_bulk_put(pd->num_clks, pd->clks);
+ reset_control_put(pd->resets);
/* protect the zeroing of pm->num_clks */
mutex_lock(&pd->pmu->mutex);
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 11/14] accel/rocket: select the per-core clock and reset counts from match data
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (9 preceding siblings ...)
2026-09-15 10:43 ` [PATCH v13 10/14] pmdomain: rockchip: cycle optional power-domain resets on power-on Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 12/14] accel/rocket: add RK3576 NPU (RKNN) support Jiaxing Hu
` (2 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu
The RK3576 carries the same RKNN block with a different set of clocks and
resets, so the counts cannot stay compile-time constants. Add a soc_data
struct to the of_device_id match data and take the bulk counts from it.
RK3588 keeps four clocks and two resets, so nothing changes for it, and the
arrays keep their present sizes: the SoC that needs a longer one grows it
in the patch that adds the names.
rocket_core_reset() is switched over as well. It is the same array, and
leaving it on ARRAY_SIZE() would walk entries that were never acquired once
a SoC asks for fewer.
soc is checked at the top of rocket_probe(), before anything is allocated
and before the per-core fields are stored. of_device_get_match_data()
returns NULL for a device that bound by name rather than by compatible, and
such a device has no of_node, so it was never counted by the walk of
matching nodes that sizes rdev->cores[]. Storing into that array first and
checking afterwards would be writing past the end under the check's own
premise, and returning from there is the one error path in this function
that would skip the unwind the path below it does.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
drivers/accel/rocket/rocket_core.c | 8 ++++----
drivers/accel/rocket/rocket_core.h | 7 +++++++
drivers/accel/rocket/rocket_drv.c | 26 +++++++++++++++++++++++---
3 files changed, 34 insertions(+), 7 deletions(-)
diff --git a/drivers/accel/rocket/rocket_core.c b/drivers/accel/rocket/rocket_core.c
index 5dd260bac..b202d1581 100644
--- a/drivers/accel/rocket/rocket_core.c
+++ b/drivers/accel/rocket/rocket_core.c
@@ -23,7 +23,7 @@ int rocket_core_init(struct rocket_core *core)
core->resets[0].id = "srst_a";
core->resets[1].id = "srst_h";
- err = devm_reset_control_bulk_get_exclusive(&pdev->dev, ARRAY_SIZE(core->resets),
+ err = devm_reset_control_bulk_get_exclusive(&pdev->dev, core->soc->num_resets,
core->resets);
if (err)
return dev_err_probe(dev, err, "failed to get resets for core %d\n", core->index);
@@ -32,7 +32,7 @@ int rocket_core_init(struct rocket_core *core)
core->clks[1].id = "hclk";
core->clks[2].id = "npu";
core->clks[3].id = "pclk";
- err = devm_clk_bulk_get(dev, ARRAY_SIZE(core->clks), core->clks);
+ err = devm_clk_bulk_get(dev, core->soc->num_clks, core->clks);
if (err)
return dev_err_probe(dev, err, "failed to get clocks for core %d\n", core->index);
@@ -109,9 +109,9 @@ void rocket_core_fini(struct rocket_core *core)
void rocket_core_reset(struct rocket_core *core)
{
- reset_control_bulk_assert(ARRAY_SIZE(core->resets), core->resets);
+ reset_control_bulk_assert(core->soc->num_resets, core->resets);
udelay(10);
- reset_control_bulk_deassert(ARRAY_SIZE(core->resets), core->resets);
+ reset_control_bulk_deassert(core->soc->num_resets, core->resets);
}
diff --git a/drivers/accel/rocket/rocket_core.h b/drivers/accel/rocket/rocket_core.h
index f6d738285..ba74c5339 100644
--- a/drivers/accel/rocket/rocket_core.h
+++ b/drivers/accel/rocket/rocket_core.h
@@ -27,9 +27,16 @@
#define rocket_core_writel(core, reg, value) \
writel(value, (core)->core_iomem + (REG_CORE_##reg) - REG_CORE_S_STATUS)
+/* Per-SoC differences, selected by the of_device_id match data. */
+struct rocket_soc_data {
+ unsigned int num_clks; /* clk_bulk count */
+ unsigned int num_resets; /* reset_bulk count */
+};
+
struct rocket_core {
struct device *dev;
struct rocket_device *rdev;
+ const struct rocket_soc_data *soc;
unsigned int index;
int irq;
diff --git a/drivers/accel/rocket/rocket_drv.c b/drivers/accel/rocket/rocket_drv.c
index 8bbbce594..7ed64c131 100644
--- a/drivers/accel/rocket/rocket_drv.c
+++ b/drivers/accel/rocket/rocket_drv.c
@@ -159,8 +159,22 @@ static const struct drm_driver rocket_drm_driver = {
static int rocket_probe(struct platform_device *pdev)
{
+ const struct rocket_soc_data *soc = of_device_get_match_data(&pdev->dev);
int ret;
+ /*
+ * soc is dereferenced without a check by every one of its users, and
+ * rocket_core_init() below is the first of them. A device that bound
+ * by name rather than by compatible has no match data, so fail before
+ * anything is allocated rather than at the first dereference: the
+ * number of cores comes from a walk of matching DT nodes, and a device
+ * with no of_node was never counted by it.
+ */
+ if (!soc) {
+ dev_err(&pdev->dev, "no match data for this device\n");
+ return -ENODEV;
+ }
+
if (rdev == NULL) {
/* First core probing, initialize DRM device. */
rdev = rocket_device_init(drm_dev, &rocket_drm_driver);
@@ -176,6 +190,7 @@ static int rocket_probe(struct platform_device *pdev)
rdev->cores[core].rdev = rdev;
rdev->cores[core].dev = &pdev->dev;
+ rdev->cores[core].soc = soc;
rdev->cores[core].index = core;
rdev->num_cores++;
@@ -213,8 +228,13 @@ static void rocket_remove(struct platform_device *pdev)
}
}
+static const struct rocket_soc_data rk3588_soc_data = {
+ .num_clks = 4,
+ .num_resets = 2,
+};
+
static const struct of_device_id dt_match[] = {
- { .compatible = "rockchip,rk3588-rknn-core" },
+ { .compatible = "rockchip,rk3588-rknn-core", .data = &rk3588_soc_data },
{}
};
MODULE_DEVICE_TABLE(of, dt_match);
@@ -240,7 +260,7 @@ static int rocket_device_runtime_resume(struct device *dev)
if (core < 0)
return -ENODEV;
- err = clk_bulk_prepare_enable(ARRAY_SIZE(rdev->cores[core].clks), rdev->cores[core].clks);
+ err = clk_bulk_prepare_enable(rdev->cores[core].soc->num_clks, rdev->cores[core].clks);
if (err) {
dev_err(dev, "failed to enable (%d) clocks for core %d\n", err, core);
return err;
@@ -260,7 +280,7 @@ static int rocket_device_runtime_suspend(struct device *dev)
if (!rocket_job_is_idle(&rdev->cores[core]))
return -EBUSY;
- clk_bulk_disable_unprepare(ARRAY_SIZE(rdev->cores[core].clks), rdev->cores[core].clks);
+ clk_bulk_disable_unprepare(rdev->cores[core].soc->num_clks, rdev->cores[core].clks);
return 0;
}
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 12/14] accel/rocket: add RK3576 NPU (RKNN) support
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (10 preceding siblings ...)
2026-09-15 10:43 ` [PATCH v13 11/14] accel/rocket: select the per-core clock and reset counts from match data Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 13/14] arm64: dts: rockchip: add NPU (RKNN) nodes to rk3576 Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 14/14] arm64: dts: rockchip: enable the NPU on rk3576-rock-4d Jiaxing Hu
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu
The RK3576 has two cores of the same RKNN block and a few platform
differences:
- the CBUF (convolution buffer) has its own clock domain, so the core
needs six clocks rather than four;
- there is no per-core hclk reset. The CRU has SRST_A_RKNN0 and
SRST_A_RKNN1 but no SRST_H_RKNN0 or SRST_H_RKNN1, so a core takes one
reset where RK3588 takes two;
- the NPU spans two power domains, and a device with more than one is
skipped by the driver-core single-domain auto-attach, so the list has
to be attached explicitly;
- PC_TASK_CON packs the task number with sixteen bits rather than
twelve, moving the two controls above it up by four and adding a third.
That last one is the reason this series has been reporting, since v3, that
the block accepts exactly one task per reset. rocket_registers.h is
generated from the RK3588 description, so writing it unchanged to an RK3576
asks for task_number 0x7001, which is 28673 tasks, and puts
TASK_COUNT_CLEAR inside the sixteen-bit task number, where it inflates the
count rather than clearing it. The counter is then only ever cleared by a
reset.
The layout was confirmed by Chaoyi Chen of Rockchip, including the third
control at BIT(18), task_last_layer_clear, which belongs on every submit
alongside the count clear:
https://lore.kernel.org/all/4f300b78-d96d-4d98-8819-dc292b0c9b97@rock-chips.com/
With that written correctly a job of several tasks runs to completion, the
completion interrupt arrives, and /proc/interrupts counts up. A convolution
submitted three times with three different inputs is byte exact against the
CPU reference each time, with no reset in between and with nothing retiring
the job but the interrupt.
Counting the cores now walks the driver's own match table instead of a
second, hand-kept list of compatibles. The array sized from that count is
indexed by every core that goes on to probe, so the two lists cannot be
allowed to disagree.
All of it hangs off the soc_data added earlier, so the RK3588 path keeps
its existing counts and behaviour.
The match table moves to rocket_drv.h so rocket_device.c can walk it with
for_each_matching_node() rather than repeating a for_each_compatible_node()
loop per SoC, which also keeps num_cores in step with the table that sizes
the array it counts into. The declaration needs struct of_device_id, taken
from <linux/device-id/of.h> rather than <linux/mod_devicetable.h>, which
carries every subsystem's tables with it.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
drivers/accel/rocket/rocket_core.c | 20 ++++++++++++++
drivers/accel/rocket/rocket_core.h | 8 +++---
drivers/accel/rocket/rocket_device.c | 7 ++++-
drivers/accel/rocket/rocket_drv.c | 16 +++++++++---
drivers/accel/rocket/rocket_drv.h | 2 ++
drivers/accel/rocket/rocket_job.c | 39 +++++++++++++++++++++++++---
6 files changed, 81 insertions(+), 11 deletions(-)
diff --git a/drivers/accel/rocket/rocket_core.c b/drivers/accel/rocket/rocket_core.c
index b202d1581..91f690176 100644
--- a/drivers/accel/rocket/rocket_core.c
+++ b/drivers/accel/rocket/rocket_core.c
@@ -8,6 +8,7 @@
#include <linux/err.h>
#include <linux/iommu.h>
#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
@@ -21,6 +22,7 @@ int rocket_core_init(struct rocket_core *core)
u32 version;
int err = 0;
+ /* RK3576 has no per-core hclk reset, so it takes srst_a alone. */
core->resets[0].id = "srst_a";
core->resets[1].id = "srst_h";
err = devm_reset_control_bulk_get_exclusive(&pdev->dev, core->soc->num_resets,
@@ -32,6 +34,9 @@ int rocket_core_init(struct rocket_core *core)
core->clks[1].id = "hclk";
core->clks[2].id = "npu";
core->clks[3].id = "pclk";
+ /* RK3576 clocks the CBUF separately; the compute path stalls without these. */
+ core->clks[4].id = "aclk_cbuf";
+ core->clks[5].id = "hclk_cbuf";
err = devm_clk_bulk_get(dev, core->soc->num_clks, core->clks);
if (err)
return dev_err_probe(dev, err, "failed to get clocks for core %d\n", core->index);
@@ -60,6 +65,21 @@ int rocket_core_init(struct rocket_core *core)
if (err)
return err;
+ /*
+ * RK3576 spans two power domains, and a multi-domain device is skipped
+ * by the driver-core single-domain auto-attach, so attach the list here.
+ * This goes before the first thing that would have to be unwound, so a
+ * failure can simply return.
+ */
+ if (core->soc->multi_power_domain) {
+ struct dev_pm_domain_list *pd_list;
+
+ err = devm_pm_domain_attach_list(dev, NULL, &pd_list);
+ if (err < 0)
+ return dev_err_probe(dev, err,
+ "failed to attach NPU power domains\n");
+ }
+
core->iommu_group = iommu_group_get(dev);
err = rocket_job_init(core);
diff --git a/drivers/accel/rocket/rocket_core.h b/drivers/accel/rocket/rocket_core.h
index ba74c5339..8c8d1f453 100644
--- a/drivers/accel/rocket/rocket_core.h
+++ b/drivers/accel/rocket/rocket_core.h
@@ -29,8 +29,10 @@
/* Per-SoC differences, selected by the of_device_id match data. */
struct rocket_soc_data {
- unsigned int num_clks; /* clk_bulk count */
- unsigned int num_resets; /* reset_bulk count */
+ unsigned int num_clks; /* clk_bulk count: 4 base, 6 with CBUF */
+ unsigned int num_resets; /* reset_bulk count: 2 base, 1 on RK3576 */
+ bool multi_power_domain; /* device spans more than one PM domain */
+ bool task_con_16bit; /* PC_TASK_CON uses the 16-bit task number */
};
struct rocket_core {
@@ -43,7 +45,7 @@ struct rocket_core {
void __iomem *pc_iomem;
void __iomem *cna_iomem;
void __iomem *core_iomem;
- struct clk_bulk_data clks[4];
+ struct clk_bulk_data clks[6];
struct reset_control_bulk_data resets[2];
struct iommu_group *iommu_group;
diff --git a/drivers/accel/rocket/rocket_device.c b/drivers/accel/rocket/rocket_device.c
index 46e6ee1e7..923add5bd 100644
--- a/drivers/accel/rocket/rocket_device.c
+++ b/drivers/accel/rocket/rocket_device.c
@@ -9,6 +9,7 @@
#include <linux/of.h>
#include "rocket_device.h"
+#include "rocket_drv.h"
struct rocket_device *rocket_device_init(struct platform_device *pdev,
const struct drm_driver *rocket_drm_driver)
@@ -27,7 +28,11 @@ struct rocket_device *rocket_device_init(struct platform_device *pdev,
ddev = &rdev->ddev;
dev_set_drvdata(dev, rdev);
- for_each_compatible_node(core_node, NULL, "rockchip,rk3588-rknn-core")
+ /*
+ * Count over the same match table the platform driver binds with, so
+ * that a core added there is counted here without a second edit.
+ */
+ for_each_matching_node(core_node, rocket_dt_match)
if (of_device_is_available(core_node))
num_cores++;
diff --git a/drivers/accel/rocket/rocket_drv.c b/drivers/accel/rocket/rocket_drv.c
index 7ed64c131..f387b4656 100644
--- a/drivers/accel/rocket/rocket_drv.c
+++ b/drivers/accel/rocket/rocket_drv.c
@@ -231,13 +231,23 @@ static void rocket_remove(struct platform_device *pdev)
static const struct rocket_soc_data rk3588_soc_data = {
.num_clks = 4,
.num_resets = 2,
+ .multi_power_domain = false,
+ .task_con_16bit = false,
};
-static const struct of_device_id dt_match[] = {
+static const struct rocket_soc_data rk3576_soc_data = {
+ .num_clks = 6,
+ .num_resets = 1,
+ .multi_power_domain = true,
+ .task_con_16bit = true,
+};
+
+const struct of_device_id rocket_dt_match[] = {
{ .compatible = "rockchip,rk3588-rknn-core", .data = &rk3588_soc_data },
+ { .compatible = "rockchip,rk3576-rknn-core", .data = &rk3576_soc_data },
{}
};
-MODULE_DEVICE_TABLE(of, dt_match);
+MODULE_DEVICE_TABLE(of, rocket_dt_match);
static int find_core_for_dev(struct device *dev)
{
@@ -296,7 +306,7 @@ static struct platform_driver rocket_driver = {
.driver = {
.name = "rocket",
.pm = pm_ptr(&rocket_pm_ops),
- .of_match_table = dt_match,
+ .of_match_table = rocket_dt_match,
},
};
diff --git a/drivers/accel/rocket/rocket_drv.h b/drivers/accel/rocket/rocket_drv.h
index 2c673bb99..0cd692a66 100644
--- a/drivers/accel/rocket/rocket_drv.h
+++ b/drivers/accel/rocket/rocket_drv.h
@@ -6,10 +6,12 @@
#include <drm/drm_mm.h>
#include <drm/gpu_scheduler.h>
+#include <linux/device-id/of.h>
#include "rocket_device.h"
extern const struct dev_pm_ops rocket_pm_ops;
+extern const struct of_device_id rocket_dt_match[];
struct rocket_iommu_domain {
struct iommu_domain *domain;
diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
index 54f9c299d..c862f8bc7 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -21,6 +21,30 @@
#define JOB_TIMEOUT_MS 500
+/*
+ * PC_TASK_CON packs the task number with control bits above it, and neither
+ * the width of the number nor the count of the controls is the same on every
+ * SoC. rocket_registers.h is generated from the RK3588 description, where the
+ * task number is twelve bits and there are two:
+ *
+ * RK3588 BIT[11:0] task_number, BIT[12] pp_en, BIT[13] count_clear
+ * RK3576 BIT[15:0] task_number, BIT[16] pp_en, BIT[17] count_clear,
+ * BIT[18] last_layer_clear
+ *
+ * The RK3576 layout was confirmed by Chaoyi Chen of Rockchip:
+ * https://lore.kernel.org/all/4f300b78-d96d-4d98-8819-dc292b0c9b97@rock-chips.com/
+ *
+ * Writing the RK3588 layout to an RK3576 therefore asks for task_number
+ * 0x7001, that is 28673 tasks, and lands the count clear on a bit that does
+ * nothing. The task counter is then only ever cleared by a reset, which is
+ * exactly the "one task per reset" behaviour this series has been reporting
+ * since v3.
+ */
+#define RK3576_PC_TASK_CON_TASK_NUMBER(n) ((n) & 0xffff)
+#define RK3576_PC_TASK_CON_PP_EN BIT(16)
+#define RK3576_PC_TASK_CON_COUNT_CLEAR BIT(17)
+#define RK3576_PC_TASK_CON_LAST_LAYER_CLEAR BIT(18)
+
static struct rocket_job *
to_rocket_job(struct drm_sched_job *sched_job)
{
@@ -142,10 +166,17 @@ static void rocket_job_hw_submit(struct rocket_core *core, struct rocket_job *jo
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);
- rocket_pc_writel(core, TASK_CON, PC_TASK_CON_RESERVED_0(1) |
- PC_TASK_CON_TASK_COUNT_CLEAR(1) |
- PC_TASK_CON_TASK_NUMBER(1) |
- PC_TASK_CON_TASK_PP_EN(1));
+ if (core->soc->task_con_16bit)
+ rocket_pc_writel(core, TASK_CON,
+ RK3576_PC_TASK_CON_LAST_LAYER_CLEAR |
+ RK3576_PC_TASK_CON_COUNT_CLEAR |
+ RK3576_PC_TASK_CON_PP_EN |
+ RK3576_PC_TASK_CON_TASK_NUMBER(1));
+ else
+ rocket_pc_writel(core, TASK_CON, PC_TASK_CON_RESERVED_0(1) |
+ PC_TASK_CON_TASK_COUNT_CLEAR(1) |
+ PC_TASK_CON_TASK_NUMBER(1) |
+ PC_TASK_CON_TASK_PP_EN(1));
rocket_pc_writel(core, TASK_DMA_BASE_ADDR, PC_TASK_DMA_BASE_ADDR_DMA_BASE_ADDR(0x0));
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 13/14] arm64: dts: rockchip: add NPU (RKNN) nodes to rk3576
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (11 preceding siblings ...)
2026-09-15 10:43 ` [PATCH v13 12/14] accel/rocket: add RK3576 NPU (RKNN) support Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
2026-09-15 10:43 ` [PATCH v13 14/14] arm64: dts: rockchip: enable the NPU on rk3576-rock-4d Jiaxing Hu
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu
Add the two RKNN cores and their IOMMUs. Both cores are disabled by
default; boards enable what they wire up.
PD_NPU0 and PD_NPU1 are siblings under PD_NPUTOP and hold one core each,
but the convolution buffer and the DSU sit above them: ACLK_RKNN_CBUF,
HCLK_RKNN_CBUF and CLK_RKNN_DSU0 belong to the block rather than to either
core, and PD_NPUTOP already lists all three. Add them to both core domains
as well, so a core domain switching state has the clocks of the path it
shares running, and give each core domain the BIU reset that the pmdomain
driver now cycles once power is on.
Each core lists both core domains, its own first, so that a core in use has
the whole block powered. Whether a single core can reach the shared path
with the sibling domain off is not something this series establishes;
listing both is the description that has been tested here. The IOMMU in
front of each core lists that core's domain only.
Label the outer PD_NPU node so a board can attach the NPU rail to the
domain that gates the block.
Clock the NPU inside the voltage its rail is given. CLK_RKNN_DSU0 clocks
both cores and the CBUF they share, nothing in mainline sets its rate, and
the block comes up at 786.432 MHz. Rockchip's OPP table for this NPU asks
800 mV of its 800 MHz step at the worst leakage bins, and nothing in
mainline sets the rail either, so a board that follows this DTS runs the
NPU above the step whose voltage it happens to boot with.
On a ROCK 4D with both cores enabled and vdd_npu_s0 at the 750 mV its PMIC
comes up with, two jobs in flight at once make the second core write single
words of its output wrong: the right value with a bit of the accumulator
set, always the same position in the array. Either core alone is exact.
Four device trees, same board, kernel and userspace, four passes of 5400
rows each, every row compared with the same multiply done one row at a
time:
786 MHz, 750 mV 13 to 20 wrong rows a pass
594 MHz, 750 mV 0, 0, 0, 0
786 MHz, 800 mV 0, 0, 0, 0
786 MHz, 850 mV 0, 0, 0, 0
594 MHz is a divider off GPLL and sits between that table's 500 and 600 MHz
steps, both of which ask 725 mV at every leakage bin, so it is inside the
voltage a board that describes no NPU rail already provides.
The trade it buys is a core against a clock, and both halves are measured.
The rate lives in the device tree, so the two clocks cannot share a boot,
which means this comparison is across boots and has to clear the noise of
one. Twenty readings of a single arm inside one boot, nothing changed
between them, span 2.5%; across boots it can only be worse. So the 4.0 to
4.2% below clears that floor by under a factor of two, and the 26 to 37%
clears it by ten. Five runs an arm, the arms alternating inside a boot,
one warm-up a model discarded, medians of five:
decode tok/s 594 MHz 786 MHz
Llama-3.2-1B 17.85 18.60 two cores
11.17 13.77 one core
SmolLM2-135M 41.46 43.12 two cores
38.26 41.90 one core
Losing 192 MHz costs 4.0 to 4.2% of decode with both cores running. Losing
a core costs 26 to 37% on the 1B model, at either clock. The rate is the
cheaper of the two by six to nine times.
The two arms cross-check each other: the clock is worth 23% on ONE core
against 4% on two. With both cores running the bottleneck is no longer the
clock, which is why this configuration can afford to give up 192 MHz.
A core is worth much less on a small model, 2.8 to 7.7% on 135M, where the
second core's dispatch overhead is not repaid. TTFT moves by under 2%
either way, so none of this says anything about prefill.
An OPP table with the rail attached is the proper answer, and it wants
driver support this series does not have.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
arch/arm64/boot/dts/rockchip/rk3576.dtsi | 86 +++++++++++++++++++++++-
1 file changed, 83 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/boot/dts/rockchip/rk3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576.dtsi
index d418bfc04..e9cd11b58 100644
--- a/arch/arm64/boot/dts/rockchip/rk3576.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3576.dtsi
@@ -1042,7 +1042,7 @@ power: power-controller {
#address-cells = <1>;
#size-cells = <0>;
- power-domain@RK3576_PD_NPU {
+ pd_npu: power-domain@RK3576_PD_NPU {
reg = <RK3576_PD_NPU>;
#power-domain-cells = <1>;
#address-cells = <1>;
@@ -1070,14 +1070,22 @@ power-domain@RK3576_PD_NPUTOP {
power-domain@RK3576_PD_NPU0 {
reg = <RK3576_PD_NPU0>;
clocks = <&cru HCLK_RKNN_ROOT>,
- <&cru ACLK_RKNN0>;
+ <&cru ACLK_RKNN0>,
+ <&cru CLK_RKNN_DSU0>,
+ <&cru ACLK_RKNN_CBUF>,
+ <&cru HCLK_RKNN_CBUF>;
+ resets = <&cru SRST_A_RKNN0_BIU>;
pm_qos = <&qos_npu_m0>;
#power-domain-cells = <0>;
};
power-domain@RK3576_PD_NPU1 {
reg = <RK3576_PD_NPU1>;
clocks = <&cru HCLK_RKNN_ROOT>,
- <&cru ACLK_RKNN1>;
+ <&cru ACLK_RKNN1>,
+ <&cru CLK_RKNN_DSU0>,
+ <&cru ACLK_RKNN_CBUF>,
+ <&cru HCLK_RKNN_CBUF>;
+ resets = <&cru SRST_A_RKNN1_BIU>;
pm_qos = <&qos_npu_m1>;
#power-domain-cells = <0>;
};
@@ -1261,6 +1269,78 @@ power-domain@RK3576_PD_VO1 {
};
};
+ rknn_core_0: npu@27700000 {
+ compatible = "rockchip,rk3576-rknn-core";
+ reg = <0x0 0x27700000 0x0 0x1000>,
+ <0x0 0x27701000 0x0 0x1000>,
+ <0x0 0x27703000 0x0 0x1000>;
+ reg-names = "pc", "cna", "core";
+ interrupts = <GIC_SPI 247 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_RKNN0>, <&cru HCLK_RKNN_ROOT>,
+ <&cru CLK_RKNN_DSU0>, <&cru PCLK_NPUTOP_ROOT>,
+ <&cru ACLK_RKNN_CBUF>, <&cru HCLK_RKNN_CBUF>;
+ clock-names = "aclk", "hclk", "npu", "pclk",
+ "aclk_cbuf", "hclk_cbuf";
+ assigned-clocks = <&cru CLK_RKNN_DSU0>;
+ assigned-clock-rates = <594000000>;
+ resets = <&cru SRST_A_RKNN0>;
+ reset-names = "srst_a";
+ power-domains = <&power RK3576_PD_NPU0>, <&power RK3576_PD_NPU1>;
+ iommus = <&rknn_mmu_0>;
+ status = "disabled";
+ };
+
+ rknn_mmu_0: iommu@27702000 {
+ compatible = "rockchip,rk3576-npu-iommu", "rockchip,rk3568-iommu";
+ reg = <0x0 0x27702000 0x0 0x100>,
+ <0x0 0x27702100 0x0 0x100>;
+ interrupts = <GIC_SPI 247 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_RKNN0>, <&cru HCLK_RKNN_ROOT>,
+ <&cru CLK_RKNN_DSU0>, <&cru ACLK_RKNN_CBUF>,
+ <&cru HCLK_RKNN_CBUF>;
+ clock-names = "aclk", "iface", "npu",
+ "aclk_cbuf", "hclk_cbuf";
+ #iommu-cells = <0>;
+ power-domains = <&power RK3576_PD_NPU0>;
+ status = "disabled";
+ };
+
+ rknn_core_1: npu@27708000 {
+ compatible = "rockchip,rk3576-rknn-core";
+ reg = <0x0 0x27708000 0x0 0x1000>,
+ <0x0 0x27709000 0x0 0x1000>,
+ <0x0 0x2770b000 0x0 0x1000>;
+ reg-names = "pc", "cna", "core";
+ interrupts = <GIC_SPI 248 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_RKNN1>, <&cru HCLK_RKNN_ROOT>,
+ <&cru CLK_RKNN_DSU0>, <&cru PCLK_NPUTOP_ROOT>,
+ <&cru ACLK_RKNN_CBUF>, <&cru HCLK_RKNN_CBUF>;
+ clock-names = "aclk", "hclk", "npu", "pclk",
+ "aclk_cbuf", "hclk_cbuf";
+ assigned-clocks = <&cru CLK_RKNN_DSU0>;
+ assigned-clock-rates = <594000000>;
+ resets = <&cru SRST_A_RKNN1>;
+ reset-names = "srst_a";
+ power-domains = <&power RK3576_PD_NPU1>, <&power RK3576_PD_NPU0>;
+ iommus = <&rknn_mmu_1>;
+ status = "disabled";
+ };
+
+ rknn_mmu_1: iommu@2770a000 {
+ compatible = "rockchip,rk3576-npu-iommu", "rockchip,rk3568-iommu";
+ reg = <0x0 0x2770a000 0x0 0x100>,
+ <0x0 0x2770a100 0x0 0x100>;
+ interrupts = <GIC_SPI 248 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_RKNN1>, <&cru HCLK_RKNN_ROOT>,
+ <&cru CLK_RKNN_DSU0>, <&cru ACLK_RKNN_CBUF>,
+ <&cru HCLK_RKNN_CBUF>;
+ clock-names = "aclk", "iface", "npu",
+ "aclk_cbuf", "hclk_cbuf";
+ #iommu-cells = <0>;
+ power-domains = <&power RK3576_PD_NPU1>;
+ status = "disabled";
+ };
+
gpu: gpu@27800000 {
compatible = "rockchip,rk3576-mali", "arm,mali-bifrost";
reg = <0x0 0x27800000 0x0 0x20000>;
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 14/14] arm64: dts: rockchip: enable the NPU on rk3576-rock-4d
2026-09-15 10:43 [PATCH v13 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (12 preceding siblings ...)
2026-09-15 10:43 ` [PATCH v13 13/14] arm64: dts: rockchip: add NPU (RKNN) nodes to rk3576 Jiaxing Hu
@ 2026-09-15 10:43 ` Jiaxing Hu
13 siblings, 0 replies; 16+ messages in thread
From: Jiaxing Hu @ 2026-09-15 10:43 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, abel.vesa, sebastian.reichel, sidong.yang,
u.kleine-koenig, chaoyi.chen, diederik, alchark, dri-devel,
linux-rockchip, iommu, linux-pm, devicetree, linux-arm-kernel,
linux-kernel, Jiaxing Hu
Enable both RKNN cores and their IOMMUs on the Radxa ROCK 4D, and hand
vdd_npu_s0 to the NPU power domain as its domain-supply, so the rail is
switched by the domain that gates the block.
Measured on a ROCK 4D with this in place. The rail's regulator debugfs
reports open_count 1, so the domain is the consumer that took it. A sampler
running beside an inference caught use_count at 1, and three reads at rest
report use_count 0 with the rail disabled, so it follows the domain rather
than staying on. Over the same run the genpd active_time of all four NPU
domains rises by roughly 60ms per inference, and the inferences either side
of that are 128 of 128 channels against the CPU reference.
Both cores keep npu-supply on the same rail, which the binding requires.
v11 enabled rknn_core_0 alone and left the second to whoever could test it;
it has been tested since. Both cores probe, each with its IOMMU, and a
runtime that deals its submits across the two returns text identical to the
one-core run over nine language models.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
.../boot/dts/rockchip/rk3576-rock-4d.dts | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts
index 272af1012..93d59c0a9 100644
--- a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts
@@ -722,6 +722,10 @@ &pcie0 {
status = "okay";
};
+&pd_npu {
+ domain-supply = <&vdd_npu_s0>;
+};
+
&pinctrl {
hdmi {
hdmi_tx_on_h: hdmi-tx-on-h {
@@ -779,6 +783,24 @@ wifi_en_h: wifi-en-h {
};
};
+&rknn_core_0 {
+ npu-supply = <&vdd_npu_s0>;
+ status = "okay";
+};
+
+&rknn_mmu_0 {
+ status = "okay";
+};
+
+&rknn_core_1 {
+ npu-supply = <&vdd_npu_s0>;
+ status = "okay";
+};
+
+&rknn_mmu_1 {
+ status = "okay";
+};
+
&sai6 {
status = "okay";
};
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread