dri-devel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v9 00/13] accel/rocket: RK3576 NPU (RKNN) enablement
@ 2026-08-24 11:08 Jiaxing Hu
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
  0 siblings, 1 reply; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:08 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
	dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
	linux-arm-kernel, linux-kernel, Jiaxing Hu

Based on Igor Paunovic's "[PATCH v2] accel/rocket: request the core
clocks by name", as v6 through v8 were.

  https://lore.kernel.org/linux-rockchip/20260729130743.128876-1-royalnet026@gmail.com/

Tested on a Radxa ROCK 4D, on next-20260814.

This adds the RK3576 NPU to accel/rocket, which today supports RK3588
only. The RK3576 carries two cores of the same RKNN block, wired up
differently. Two extra convolution buffer clocks, two power domains per
core, one reset rather than two, no NPU SRAM rail, and a PC_TASK_CON
that packs the task number into sixteen bits rather than twelve.

What changed since v8

A new 3/13, and the rest is answers to the v8 thread.

RK3576 did not recover from a job timeout. The block came back with
MMU_DTE_ADDR complaining and every following inference returned the
output zero point. rocket_reset() called pm_runtime_put_noidle(), which
drops the usage count without starting the idle path, so the core stayed
runtime-active, the power domain never dropped, the BIU reset the domain
cycles on power-on never fired, and the MMU never answered again.

pm_runtime_put_autosuspend() is the whole fix. Measured three runs in one
boot on a ROCK 4D with one variable between them, each forcing a timeout
and then running the same convolution:

  put                after the timeout          the next inference
  put_noidle         runtime-active, rail up    0 of 128, MMU_DTE_ADDR
  put_autosuspend    suspended, rail down       128 of 128, no message
  put_noidle again   runtime-active, rail up    0 of 128, MMU_DTE_ADDR

The third run is there so the failure reads as deterministic rather than
intermittent.

Igor Paunovic ran the differential on RK3588: 45 induced resets across
three cores, with and without 1/13 and 2/13, and the domain dropped every
time with no MMU message on either kernel. That is what scopes this to
RK3576. An earlier draft of 3/13 carried Reported-by on his name and that
was wrong. He called it "your non-recovery" and said he could not
reproduce it, and the put_noidle against put_autosuspend split was mine.
He is on 3/13 for the RK3588 result, which is what he contributed.

2/13 now masks the block before synchronize_irq(). 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. Igor Paunovic raised this and
wrote the line.

That write is guarded by pm_runtime_get_if_active(), and it needs to be.
It is the first register access rocket_reset() has ever made, and the
function holds no runtime PM reference of its own. The only one in the
window belongs to in_flight_job, and the completion path can have put it
before the timeout worker arrives. drm_sched_stop() sits in between, can
block, and subtracts every pending job's credits, so nothing keeps the
core resumed. A register access with the domain down takes an async
SError on this hardware, which is the failure 6/13 and 8/13 describe from
the power-on side. Igor asked the general form of this on v8, whether
rocket_reset() should hold a reference, and it was deferred because
nothing in the path touched a register. 2/13 is what makes it matter.

His Tested-by on the v8 shape of that patch is deliberately not carried
here, because this is not the patch he tested. 1/13 is unchanged and
keeps his.

11/13 includes <linux/device-id/of.h> rather than
<linux/mod_devicetable.h>, which Uwe Kleine-Koenig asked for. The driver
needed nothing else out of the wider header.

5/13 carries a git note naming the base and the one prerequisite, so the
dependency is in the patch rather than only in this letter, which Rob
Herring's bot asked for. dt_binding_check is clean on all three bindings
the series touches with dtschema 2026.6 and yamllint 1.38.0, and
CHECK_DTBS is clean on all 13 rk3576 and all 48 rk3588 dtbs.

Two things I have left alone and would rather hear about than guess at.
8/13 does three things: it adds the settle delay, renames a macro, and
gives RK3576_PD_NPU a regulator, which also makes every RK3576 board
force that domain off at probe. It may want splitting. And 12/13 gives
each core both NPU domains, which is the description that has been
tested here rather than the topology; if it should be one domain per
core, 5/13's minItems has to change with it.

Nothing else moved. The completion path, the register field layout v7
corrected, and the rail and reset arrangement v8 settled are all
unchanged.

Jiaxing Hu (13):
  accel/rocket: take the completion register writes under job_lock
  accel/rocket: wait for a running IRQ handler before resetting a core
  accel/rocket: let the core suspend after a reset
  accel/rocket: factor the completion tail out of the IRQ handler
  dt-bindings: npu: rockchip: add rockchip,rk3576-rknn-core
  dt-bindings: power: rockchip: allow resets in a power domain node
  dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU
  pmdomain/rockchip: add optional per-domain power-on settle delay
  pmdomain/rockchip: cycle optional power-domain resets on power-on
  accel/rocket: select the per-core clock and reset counts from match
    data
  accel/rocket: add RK3576 NPU (RKNN) support
  arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes
  arm64: dts: rockchip: rk3576-rock-4d: enable NPU

 .../bindings/iommu/rockchip,iommu.yaml        |  28 +++++
 .../npu/rockchip,rk3588-rknn-core.yaml        |  47 ++++++-
 .../power/rockchip,power-controller.yaml      |   8 ++
 .../boot/dts/rockchip/rk3576-rock-4d.dts      |  13 ++
 arch/arm64/boot/dts/rockchip/rk3576.dtsi      |  82 +++++++++++-
 drivers/accel/rocket/rocket_core.c            |  28 ++++-
 drivers/accel/rocket/rocket_core.h            |  11 +-
 drivers/accel/rocket/rocket_device.c          |   7 +-
 drivers/accel/rocket/rocket_drv.c             |  28 ++++-
 drivers/accel/rocket/rocket_drv.h             |   2 +
 drivers/accel/rocket/rocket_job.c             | 119 ++++++++++++++----
 drivers/pmdomain/rockchip/pm-domains.c        |  75 +++++++----
 12 files changed, 385 insertions(+), 63 deletions(-)


base-commit: d589af98928d20eb39b04ecce3eecbe7ec802222
-- 
2.43.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock
  2026-08-24 11:08 [PATCH v9 00/13] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
@ 2026-08-24 11:08 ` Jiaxing Hu
  2026-08-24 11:08   ` [PATCH v9 02/13] accel/rocket: wait for a running IRQ handler before resetting a core Jiaxing Hu
                     ` (11 more replies)
  0 siblings, 12 replies; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:08 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, 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.

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
---
 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 3141f210f..5f0f9682e 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

base-commit: d589af98928d20eb39b04ecce3eecbe7ec802222
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v9 02/13] accel/rocket: wait for a running IRQ handler before resetting a core
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
@ 2026-08-24 11:08   ` Jiaxing Hu
  2026-08-25 12:31     ` Igor Paunovic
  2026-08-24 11:08   ` [PATCH v9 03/13] accel/rocket: let the core suspend after a reset Jiaxing Hu
                     ` (10 subsequent siblings)
  11 siblings, 1 reply; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:08 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, 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, and both callers,
rocket_job_timedout() and rocket_reset_work(), run 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 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. 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.

Suggested-by: Igor Paunovic <royalnet026@gmail.com>
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
 drivers/accel/rocket/rocket_job.c | 34 ++++++++++++++++++++++++++++---
 1 file changed, 31 insertions(+), 3 deletions(-)

diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
index 5f0f9682e..3c0ed4605 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -377,9 +377,37 @@ 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.
+	 */
+	if (pm_runtime_get_if_active(core->dev) > 0) {
+		rocket_pc_writel(core, INTERRUPT_MASK, 0x0);
+		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 related	[flat|nested] 28+ messages in thread

* [PATCH v9 03/13] accel/rocket: let the core suspend after a reset
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
  2026-08-24 11:08   ` [PATCH v9 02/13] accel/rocket: wait for a running IRQ handler before resetting a core Jiaxing Hu
@ 2026-08-24 11:08   ` Jiaxing Hu
  2026-08-25 12:32     ` Igor Paunovic
  2026-08-24 11:08   ` [PATCH v9 04/13] accel/rocket: factor the completion tail out of the IRQ handler Jiaxing Hu
                     ` (9 subsequent siblings)
  11 siblings, 1 reply; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:08 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, 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
get_sync() that never reached the hardware, and changing it belongs in
its own patch.

Igor Paunovic ran the differential on RK3588: 45 induced resets across
three cores, 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>
---
 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 3c0ed4605..a89ab49e1 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -406,12 +406,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 related	[flat|nested] 28+ messages in thread

* [PATCH v9 04/13] accel/rocket: factor the completion tail out of the IRQ handler
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
  2026-08-24 11:08   ` [PATCH v9 02/13] accel/rocket: wait for a running IRQ handler before resetting a core Jiaxing Hu
  2026-08-24 11:08   ` [PATCH v9 03/13] accel/rocket: let the core suspend after a reset Jiaxing Hu
@ 2026-08-24 11:08   ` Jiaxing Hu
  2026-08-24 11:08   ` [PATCH v9 05/13] dt-bindings: npu: rockchip: add rockchip, rk3576-rknn-core Jiaxing Hu
                     ` (8 subsequent siblings)
  11 siblings, 0 replies; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:08 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, 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 a89ab49e1..69e29f40f 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 related	[flat|nested] 28+ messages in thread

* [PATCH v9 05/13] dt-bindings: npu: rockchip: add rockchip, rk3576-rknn-core
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
                     ` (2 preceding siblings ...)
  2026-08-24 11:08   ` [PATCH v9 04/13] accel/rocket: factor the completion tail out of the IRQ handler Jiaxing Hu
@ 2026-08-24 11:08   ` Jiaxing Hu
  2026-08-25 12:32     ` Igor Paunovic
  2026-08-24 11:08   ` [PATCH v9 06/13] dt-bindings: power: rockchip: allow resets in a power domain node Jiaxing Hu
                     ` (7 subsequent siblings)
  11 siblings, 1 reply; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:08 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, 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 related	[flat|nested] 28+ messages in thread

* [PATCH v9 06/13] dt-bindings: power: rockchip: allow resets in a power domain node
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
                     ` (3 preceding siblings ...)
  2026-08-24 11:08   ` [PATCH v9 05/13] dt-bindings: npu: rockchip: add rockchip, rk3576-rknn-core Jiaxing Hu
@ 2026-08-24 11:08   ` Jiaxing Hu
  2026-08-24 16:24     ` Conor Dooley
  2026-08-24 11:08   ` [PATCH v9 07/13] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU Jiaxing Hu
                     ` (6 subsequent siblings)
  11 siblings, 1 reply; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:08 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
	dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
	linux-arm-kernel, linux-kernel, Jiaxing Hu

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>
---
 .../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 related	[flat|nested] 28+ messages in thread

* [PATCH v9 07/13] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
                     ` (4 preceding siblings ...)
  2026-08-24 11:08   ` [PATCH v9 06/13] dt-bindings: power: rockchip: allow resets in a power domain node Jiaxing Hu
@ 2026-08-24 11:08   ` Jiaxing Hu
  2026-08-24 16:25     ` Conor Dooley
  2026-08-24 11:08   ` [PATCH v9 08/13] pmdomain/rockchip: add optional per-domain power-on settle delay Jiaxing Hu
                     ` (5 subsequent siblings)
  11 siblings, 1 reply; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:08 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, 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 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>
---
 .../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 related	[flat|nested] 28+ messages in thread

* [PATCH v9 08/13] pmdomain/rockchip: add optional per-domain power-on settle delay
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
                     ` (5 preceding siblings ...)
  2026-08-24 11:08   ` [PATCH v9 07/13] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU Jiaxing Hu
@ 2026-08-24 11:08   ` Jiaxing Hu
  2026-08-24 11:31     ` Abel Vesa
  2026-08-24 11:08   ` [PATCH v9 09/13] pmdomain/rockchip: cycle optional power-domain resets on power-on Jiaxing Hu
                     ` (4 subsequent siblings)
  11 siblings, 1 reply; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:08 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, 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, since the suffixes name the fields the macro sets and
this one now also carries a wakeup delay; RK3576 is its only user, so
the old spelling is not kept around.

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.

Signed-off-by: Jiaxing Hu <gahing@gahingwoo.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 related	[flat|nested] 28+ messages in thread

* [PATCH v9 09/13] pmdomain/rockchip: cycle optional power-domain resets on power-on
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
                     ` (6 preceding siblings ...)
  2026-08-24 11:08   ` [PATCH v9 08/13] pmdomain/rockchip: add optional per-domain power-on settle delay Jiaxing Hu
@ 2026-08-24 11:08   ` Jiaxing Hu
  2026-08-24 11:30     ` Abel Vesa
  2026-08-24 11:08   ` [PATCH v9 10/13] accel/rocket: select the per-core clock and reset counts from match data Jiaxing Hu
                     ` (3 subsequent siblings)
  11 siblings, 1 reply; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:08 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, 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.

Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
 drivers/pmdomain/rockchip/pm-domains.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c
index 39988efd8..8f2fd8a83 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 {
@@ -692,6 +694,13 @@ static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on)
 		if (pd->info->delay_us)
 			udelay(pd->info->delay_us);
 
+		/* Optional: some domains need their resets cycled after power-on. */
+		if (pd->resets) {
+			reset_control_assert(pd->resets);
+			usleep_range(10, 20);
+			reset_control_deassert(pd->resets);
+		}
+
 		rockchip_pmu_restore_qos(pd);
 	}
 
@@ -861,6 +870,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 +948,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 +967,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 related	[flat|nested] 28+ messages in thread

* [PATCH v9 10/13] accel/rocket: select the per-core clock and reset counts from match data
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
                     ` (7 preceding siblings ...)
  2026-08-24 11:08   ` [PATCH v9 09/13] pmdomain/rockchip: cycle optional power-domain resets on power-on Jiaxing Hu
@ 2026-08-24 11:08   ` Jiaxing Hu
  2026-08-24 11:09   ` [PATCH v9 11/13] accel/rocket: add RK3576 NPU (RKNN) support Jiaxing Hu
                     ` (2 subsequent siblings)
  11 siblings, 0 replies; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:08 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, 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.

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  | 12 +++++++++---
 3 files changed, 20 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..6e7dc91c5 100644
--- a/drivers/accel/rocket/rocket_drv.c
+++ b/drivers/accel/rocket/rocket_drv.c
@@ -176,6 +176,7 @@ static int rocket_probe(struct platform_device *pdev)
 
 	rdev->cores[core].rdev = rdev;
 	rdev->cores[core].dev = &pdev->dev;
+	rdev->cores[core].soc = of_device_get_match_data(&pdev->dev);
 	rdev->cores[core].index = core;
 
 	rdev->num_cores++;
@@ -213,8 +214,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 +246,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 +266,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 related	[flat|nested] 28+ messages in thread

* [PATCH v9 11/13] accel/rocket: add RK3576 NPU (RKNN) support
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
                     ` (8 preceding siblings ...)
  2026-08-24 11:08   ` [PATCH v9 10/13] accel/rocket: select the per-core clock and reset counts from match data Jiaxing Hu
@ 2026-08-24 11:09   ` Jiaxing Hu
  2026-08-24 11:09   ` [PATCH v9 12/13] arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes Jiaxing Hu
  2026-08-24 11:09   ` [PATCH v9 13/13] arm64: dts: rockchip: rk3576-rock-4d: enable NPU Jiaxing Hu
  11 siblings, 0 replies; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:09 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, 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 three controls above it up by four.

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 on a bit that does nothing. The counter is then only
ever cleared by a reset.

The layout was confirmed by Chaoyi Chen of Rockchip, including a fourth
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    | 38 +++++++++++++++++++++++++---
 6 files changed, 80 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 6e7dc91c5..e46962949 100644
--- a/drivers/accel/rocket/rocket_drv.c
+++ b/drivers/accel/rocket/rocket_drv.c
@@ -217,13 +217,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)
 {
@@ -282,7 +292,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 69e29f40f..2a272c2ef 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -21,6 +21,29 @@
 
 #define JOB_TIMEOUT_MS 500
 
+/*
+ * PC_TASK_CON packs the task number with three controls, and the field widths
+ * are not the same on every SoC. rocket_registers.h is generated from the
+ * RK3588 description, where the task number is twelve bits:
+ *
+ *   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 +165,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 related	[flat|nested] 28+ messages in thread

* [PATCH v9 12/13] arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
                     ` (9 preceding siblings ...)
  2026-08-24 11:09   ` [PATCH v9 11/13] accel/rocket: add RK3576 NPU (RKNN) support Jiaxing Hu
@ 2026-08-24 11:09   ` Jiaxing Hu
  2026-08-24 11:09   ` [PATCH v9 13/13] arm64: dts: rockchip: rk3576-rock-4d: enable NPU Jiaxing Hu
  11 siblings, 0 replies; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:09 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, 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.

Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
 arch/arm64/boot/dts/rockchip/rk3576.dtsi | 82 +++++++++++++++++++++++-
 1 file changed, 79 insertions(+), 3 deletions(-)

diff --git a/arch/arm64/boot/dts/rockchip/rk3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576.dtsi
index b0c0d3c8b..2d0133cdf 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,74 @@ 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";
+			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";
+			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 related	[flat|nested] 28+ messages in thread

* [PATCH v9 13/13] arm64: dts: rockchip: rk3576-rock-4d: enable NPU
  2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
                     ` (10 preceding siblings ...)
  2026-08-24 11:09   ` [PATCH v9 12/13] arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes Jiaxing Hu
@ 2026-08-24 11:09   ` Jiaxing Hu
  11 siblings, 0 replies; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-24 11:09 UTC (permalink / raw)
  To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing
  Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
	dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
	linux-arm-kernel, linux-kernel, Jiaxing Hu

Enable rknn_core_0 and its IOMMU 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.

rknn_core_0 keeps npu-supply on the same rail, which the binding
requires. Only rknn_core_0 is enabled: the driver binds one core per
node and the second core is left to whoever can test it.

Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
 arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts | 13 +++++++++++++
 1 file changed, 13 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..79e039bee 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,15 @@ wifi_en_h: wifi-en-h {
 	};
 };
 
+&rknn_core_0 {
+	npu-supply = <&vdd_npu_s0>;
+	status = "okay";
+};
+
+&rknn_mmu_0 {
+	status = "okay";
+};
+
 &sai6 {
 	status = "okay";
 };
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH v9 09/13] pmdomain/rockchip: cycle optional power-domain resets on power-on
  2026-08-24 11:08   ` [PATCH v9 09/13] pmdomain/rockchip: cycle optional power-domain resets on power-on Jiaxing Hu
@ 2026-08-24 11:30     ` Abel Vesa
  0 siblings, 0 replies; 28+ messages in thread
From: Abel Vesa @ 2026-08-24 11:30 UTC (permalink / raw)
  To: Jiaxing Hu
  Cc: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing, royalnet026, u.kleine-koenig,
	chaoyi.chen, diederik, alchark, dri-devel, linux-rockchip, iommu,
	linux-pm, devicetree, linux-arm-kernel, linux-kernel

On 26-08-24 23:08:58, Jiaxing Hu wrote:
> 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.
> 
> Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>

Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v9 08/13] pmdomain/rockchip: add optional per-domain power-on settle delay
  2026-08-24 11:08   ` [PATCH v9 08/13] pmdomain/rockchip: add optional per-domain power-on settle delay Jiaxing Hu
@ 2026-08-24 11:31     ` Abel Vesa
  0 siblings, 0 replies; 28+ messages in thread
From: Abel Vesa @ 2026-08-24 11:31 UTC (permalink / raw)
  To: Jiaxing Hu
  Cc: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing, royalnet026, u.kleine-koenig,
	chaoyi.chen, diederik, alchark, dri-devel, linux-rockchip, iommu,
	linux-pm, devicetree, linux-arm-kernel, linux-kernel

On 26-08-24 23:08:57, Jiaxing Hu wrote:
> 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, since the suffixes name the fields the macro sets and
> this one now also carries a wakeup delay; RK3576 is its only user, so
> the old spelling is not kept around.
> 
> 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.
> 
> Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>

Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v9 06/13] dt-bindings: power: rockchip: allow resets in a power domain node
  2026-08-24 11:08   ` [PATCH v9 06/13] dt-bindings: power: rockchip: allow resets in a power domain node Jiaxing Hu
@ 2026-08-24 16:24     ` Conor Dooley
  0 siblings, 0 replies; 28+ messages in thread
From: Conor Dooley @ 2026-08-24 16:24 UTC (permalink / raw)
  To: Jiaxing Hu
  Cc: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing, royalnet026, u.kleine-koenig,
	chaoyi.chen, diederik, alchark, dri-devel, linux-rockchip, iommu,
	linux-pm, devicetree, linux-arm-kernel, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1774 bytes --]

On Mon, Aug 24, 2026 at 11:08:55PM +1200, Jiaxing Hu wrote:
> 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.

Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable

> 
> Signed-off-by: Jiaxing Hu <gahing@gahingwoo.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
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v9 07/13] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU
  2026-08-24 11:08   ` [PATCH v9 07/13] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU Jiaxing Hu
@ 2026-08-24 16:25     ` Conor Dooley
  0 siblings, 0 replies; 28+ messages in thread
From: Conor Dooley @ 2026-08-24 16:25 UTC (permalink / raw)
  To: Jiaxing Hu
  Cc: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing, royalnet026, u.kleine-koenig,
	chaoyi.chen, diederik, alchark, dri-devel, linux-rockchip, iommu,
	linux-pm, devicetree, linux-arm-kernel, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 75 bytes --]

Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v9 02/13] accel/rocket: wait for a running IRQ handler before resetting a core
  2026-08-24 11:08   ` [PATCH v9 02/13] accel/rocket: wait for a running IRQ handler before resetting a core Jiaxing Hu
@ 2026-08-25 12:31     ` Igor Paunovic
  2026-08-25 12:45       ` Igor Paunovic
  0 siblings, 1 reply; 28+ messages in thread
From: Igor Paunovic @ 2026-08-25 12:31 UTC (permalink / raw)
  To: Jiaxing Hu
  Cc: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing, u.kleine-koenig, chaoyi.chen,
	diederik, alchark, dri-devel, linux-rockchip, iommu, linux-pm,
	devicetree, linux-arm-kernel, linux-kernel

Hi Jiaxing,

As promised, the 19 August protocol re-run on v9, as-is.

Setup, identical to the run reported against v8 1-2/12: same
board (RK3588, Orange Pi 5 Plus), same personal 7.2-rc6 tree,
same config (PROVE_LOCKING=y, DEBUG_ATOMIC_SLEEP=y), all three
cores bound with scheduler-driven placement, and the same local
test-only patch lowering JOB_TIMEOUT_MS to 2 ms. The serial
console was captured on a second machine for the whole session.
Two passes per kernel, at console_loglevel 8 and 4. The only
difference from 19 August: the two rocket changes on top of the
base are now v9 1/13 and 2/13 instead of the v8 pair. Both
applied cleanly.

v9 arm: two passes, 12 and 11 induced "NPU job timed out"
resets. Every reset recovered, every inference matched the CPU
oracle within 1 (48/48), including the one issued after a
forced autosuspend and resume. No MMU faults, no raw reset
messages, no lockdep hits, nothing on the serial console.

Differential arm, fresh in the same session (same base with the
two patches removed, same config, same timeout): five runs -
the protocol pair at loglevel 8 and 4, plus three extra runs at
loglevel 8 to gauge repeatability. Reset counts 8/10/12/8/15,
all recovered. Four runs clean. One of the extra runs came back
with something I had not seen before: the inference after the
forced autosuspend "succeeded" but returned a constant buffer -
all 48 output channels uniformly 128 (0x80), which is not the
output zero point of this model, while the CPU reference varies
normally. Zero kernel messages, zero lockdep hits, nothing on
the serial console. A job that signals completion while its
output buffer is never written is exactly the silent flavour of
the race these two patches close, and in 102 induced resets
across nine runs today it appeared only on the arm that does
not carry them. Full artifacts are preserved (scorer output,
runtime and genpd state, journal, serial log) if anyone wants
them.

Before testing I also compared the tagged patches against their
v8 counterparts: 1/13 is byte-identical to v8 1/12 up to the
base-commit trailer, and 4/13 is identical to v8 3/12, so the
tags they carry describe exactly what was tested here.

For this patch:

Tested-by: Igor Paunovic <royalnet026@gmail.com> # RK3588, three
cores, induced reset, differential base, JOB_TIMEOUT_MS=2

Regards,
Igor

On Mon, Aug 24, 2026 at 1:09 PM Jiaxing Hu <gahing@gahingwoo.com> wrote:
>
> 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, and both callers,
> rocket_job_timedout() and rocket_reset_work(), run 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 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. 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.
>
> Suggested-by: Igor Paunovic <royalnet026@gmail.com>
> Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
> ---
>  drivers/accel/rocket/rocket_job.c | 34 ++++++++++++++++++++++++++++---
>  1 file changed, 31 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
> index 5f0f9682e..3c0ed4605 100644
> --- a/drivers/accel/rocket/rocket_job.c
> +++ b/drivers/accel/rocket/rocket_job.c
> @@ -377,9 +377,37 @@ 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.
> +        */
> +       if (pm_runtime_get_if_active(core->dev) > 0) {
> +               rocket_pc_writel(core, INTERRUPT_MASK, 0x0);
> +               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] 28+ messages in thread

* Re: [PATCH v9 03/13] accel/rocket: let the core suspend after a reset
  2026-08-24 11:08   ` [PATCH v9 03/13] accel/rocket: let the core suspend after a reset Jiaxing Hu
@ 2026-08-25 12:32     ` Igor Paunovic
  2026-08-25 12:45       ` Igor Paunovic
  0 siblings, 1 reply; 28+ messages in thread
From: Igor Paunovic @ 2026-08-25 12:32 UTC (permalink / raw)
  To: Jiaxing Hu
  Cc: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing, u.kleine-koenig, chaoyi.chen,
	diederik, alchark, dri-devel, linux-rockchip, iommu, linux-pm,
	devicetree, linux-arm-kernel, linux-kernel

Hi Jiaxing,

On top of the as-is protocol re-run I reported on 2/13, I ran
one more arm with this patch added on top of 1/13 and 2/13,
same machine, same tree, same config, same JOB_TIMEOUT_MS=2.

Two passes (console_loglevel 8 and 4), 13 and 13 induced
resets, all recovered, oracle 48/48 throughout, including after
a forced autosuspend and resume.

The behavioural change is visible on RK3588. On the 1+2 arm, a
round that ends in a timeout leaves the affected core
runtime-active even through a forced autosuspend - the leak
from my earlier report. With this patch applied the same
sequence leaves all three cores suspended, and the next
inference still scores 48/48.

Tested-by: Igor Paunovic <royalnet026@gmail.com> # RK3588, three
cores, induced reset, JOB_TIMEOUT_MS=2

Regards,
Igor

On Mon, Aug 24, 2026 at 1:09 PM Jiaxing Hu <gahing@gahingwoo.com> wrote:
>
> 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
> get_sync() that never reached the hardware, and changing it belongs in
> its own patch.
>
> Igor Paunovic ran the differential on RK3588: 45 induced resets across
> three cores, 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>
> ---
>  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 3c0ed4605..a89ab49e1 100644
> --- a/drivers/accel/rocket/rocket_job.c
> +++ b/drivers/accel/rocket/rocket_job.c
> @@ -406,12 +406,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] 28+ messages in thread

* Re: [PATCH v9 05/13] dt-bindings: npu: rockchip: add rockchip, rk3576-rknn-core
  2026-08-24 11:08   ` [PATCH v9 05/13] dt-bindings: npu: rockchip: add rockchip, rk3576-rknn-core Jiaxing Hu
@ 2026-08-25 12:32     ` Igor Paunovic
  2026-08-27  1:49       ` Jiaxing Hu
  0 siblings, 1 reply; 28+ messages in thread
From: Igor Paunovic @ 2026-08-25 12:32 UTC (permalink / raw)
  To: Jiaxing Hu
  Cc: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
	ulfh, p.zabel, ogabbay, zhangqing, u.kleine-koenig, chaoyi.chen,
	diederik, alchark, dri-devel, linux-rockchip, iommu, linux-pm,
	devicetree, linux-arm-kernel, linux-kernel, Krzysztof Kozlowski

Hi Jiaxing,

One small thing I noticed while applying the series for the
RK3588 re-run: the cover letter says this patch carries a git
note naming the base and the one prerequisite, but the posted
mail has no Notes section - perhaps format-patch ran without
--notes. Mentioning it only because Rob's bot asked on v8 for
the dependency to be recorded in the patch itself, and it would
be a pity to collect the same remark again on v10.

Regards,
Igor

On Mon, Aug 24, 2026 at 1:10 PM Jiaxing Hu <gahing@gahingwoo.com> wrote:
>
> 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] 28+ messages in thread

* Re: [PATCH v9 02/13] accel/rocket: wait for a running IRQ handler before resetting a core
  2026-08-25 12:31     ` Igor Paunovic
@ 2026-08-25 12:45       ` Igor Paunovic
  0 siblings, 0 replies; 28+ messages in thread
From: Igor Paunovic @ 2026-08-25 12:45 UTC (permalink / raw)
  To: Jiaxing Hu, Tomeu Vizoso, Oded Gabbay
  Cc: Igor Paunovic, Heiko Stuebner, Chaoyi Chen, Alexey Charkov,
	Joerg Roedel, Will Deacon, Robin Murphy, dri-devel,
	linux-rockchip, iommu, linux-pm, linux-arm-kernel, linux-kernel

My mail client wrapped the tag comment in the previous mail, which
would truncate the disclosure when the trailer is collected. Here it
is again, on one line:

Tested-by: Igor Paunovic <royalnet026@gmail.com> # RK3588, three cores, induced reset, differential base, JOB_TIMEOUT_MS=2

Sorry for the noise.

Igor

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v9 03/13] accel/rocket: let the core suspend after a reset
  2026-08-25 12:32     ` Igor Paunovic
@ 2026-08-25 12:45       ` Igor Paunovic
  0 siblings, 0 replies; 28+ messages in thread
From: Igor Paunovic @ 2026-08-25 12:45 UTC (permalink / raw)
  To: Jiaxing Hu, Tomeu Vizoso, Oded Gabbay
  Cc: Igor Paunovic, Heiko Stuebner, Chaoyi Chen, Alexey Charkov,
	Joerg Roedel, Will Deacon, Robin Murphy, dri-devel,
	linux-rockchip, iommu, linux-pm, linux-arm-kernel, linux-kernel

My mail client wrapped the tag comment in the previous mail, which
would truncate the disclosure when the trailer is collected. Here it
is again, on one line:

Tested-by: Igor Paunovic <royalnet026@gmail.com> # RK3588, three cores, induced reset, JOB_TIMEOUT_MS=2

Sorry for the noise.

Igor

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v9 05/13] dt-bindings: npu: rockchip: add rockchip, rk3576-rknn-core
  2026-08-25 12:32     ` Igor Paunovic
@ 2026-08-27  1:49       ` Jiaxing Hu
  2026-08-27 17:48         ` Igor Paunovic
  0 siblings, 1 reply; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-27  1:49 UTC (permalink / raw)
  To: royalnet026; +Cc: linux-rockchip, dri-devel

Hi Igor,

Three things: the note you caught, the oc = 64 answer I have owed you since the
17th, and your SIZE_E question from the 20th. The last two turned out to have
the same source, and it was sitting on my disk the whole time.

First the note, because you were right and I was wrong about why. send-v9.sh
never passed --notes, so whatever was on the commit could not have reached the
mail. grep on the patch file I actually sent says

  $ grep -c '^Notes:' rfc-send-v9/v9-0005-*.patch
  0

so nothing was lost in a rebase and nothing needed recovering; the note is on
the commit and format-patch --notes emits it. The flag was missing, which is
exactly what you said. v10's send script regenerates with --notes and then
refuses to send unless the number of patches carrying a Notes block is exactly
one. Thank you for saying it before v10 rather than after.

Now oc = 64, concretely, as you asked: SIZE_E_2 = 1.

The expression, which is what the merge request emits and what I should have
sent instead of the sentence about 0x124 and 0x024:

  (DIV_ROUND_UP(oc, FEATURE_ATOMIC_SIZE) & 1) == 0 ? 0x80011111 : 0x80011011

At oc = 64 that is DIV_ROUND_UP(64, 16) = 4, even, so 0x80011111, so
SIZE_E_2 = 1. Your reading of the merge request line was right in every detail.
The sentence was mine and it named the arms by the wrong predicate, so discard
it and take the expression. You are also right that we should name the arms by
value and by count, so from here: oc = 64, 0x80011111, SIZE_E_2 = 1.

I did not have to derive that. geom/g_oc64.rknn, compiled at ic 16, 80x80,
k = 5, stride 2, oc = 64, has DPU 0x4050 = 0x80011111, and so do six more at
oc = 64. Two others, pp_oc64 at k = 3 stride 1 and pw48x64w56 at ic 48, 56x56,
k = 1, read 0x80021111, which differs only in RESERVED_0 (see below) and has
SIZE_E_2 = 1 as well. Nine vendor models at oc = 64 on this disk, all
SIZE_E_2 = 1, across three kernel sizes, both strides, and spatial sizes from
1x1 to 112x112.

Which brings me to your question from the 20th, whether different output
channel counts would exercise different SIZE_E fields, and whether two shapes
can rule out one that leans on SIZE_E_1.

I have 94 compiled vendor .rknn on disk from earlier rounds: oc 4 to 1024, ic 3
to 1024, 1x1 to 224x224, k = 1, 3 and 5, stride 1 and 2, regular and depthwise.
Reading DPU 0x4050 out of every one of them and decoding it against
registers.xml:

  SIZE_E_0   4     in all 94, it never moves
  SIZE_E_1   0     in all 81 regular models
             1     in all 13 depthwise models
  SIZE_E_2   0..1  regular, 0..3 depthwise

So the answer is no, and for a sharper reason than "we have not seen it move".
SIZE_E_1's axis is the depthwise flag, not the channel count. No output channel
count can be the shape that leans on it, because oc does not select it at all.
What does is regular against depthwise, and the driver already emits a
different word entirely on the depthwise path, whose SIZE_E_1 is 1, which is
the vendor's depthwise value on all thirteen.

That also means my comment in the driver was weaker than the truth. "SIZE_E_1
left at 0 because two shapes is not every shape" was honest, but 0 is what the
vendor emits on all 81 regular models, and upstream's 1 is what it emits on
depthwise. It is not an unexplained traced constant, and I have corrected the
comment to say so.

Your padding reading holds too, taken literally and with the depthwise padding
you pointed at. Depthwise pads to 64 output channels, so the last bank holds
one to four atoms of 16:

  oc            16  32  48  64  80  96  112  128  256  1024
  last bank     16  32  48  64  16  32   48   64   64    64
  atoms in it    1   2   3   4   1   2    3    4    4     4
  predicted      0   1   2   3   0   1    2    3    3     3
  observed       0   1   2   3   0   1    2    3    3     3

13 of 13 depthwise models, ten distinct counts, no exceptions, including the
three the driver's comment says it predicted rather than fitted: 16, 80 and
112. I had been carrying that as (atoms - 1) & 3, which gets the same numbers
and says nothing. Your form is the reason for them.

And while I had all 94 open I scored both candidate readings against the 81
regular models:

  parity, DIV_ROUND_UP(oc,16) even    0 wrong of 81
  modulo, oc % 32 == 0                1 wrong of 81

The one point where they disagree in that corpus is ocp56, oc = 56, ic = 64,
56x56, k = 1, stride 1, and the vendor emits 0x80021111, SIZE_E_2 = 1, which is
the parity answer. Every other model has an oc where the two forms agree, which
is why the original ten point sweep could not choose between them.

That is oc = 56 again. The board picked out a different model at the same
count, pw64x56w56, as the one shape that times out under the modulo form. The
board and the vendor's own compiler arrive at the same discriminating count
from two directions, which is better evidence than either alone and better than
I claimed at the time.

One thing I cannot explain, flagged rather than claimed. RESERVED_0 is 34 in
most of the regular models and 66 in a subset of them: 0x80011111 against
0x80021111, which inside the field is its bit 5 against its bit 6, both over a
constant 2. It does not correlate with oc, ic, spatial size, kernel size or
stride. Both values appear at oc 16, 64 and 128, at k = 1, 3 and 5, and at both
strides. What it does track is which batch of models it came from, which makes
a toolkit setting more likely than a geometry, but I have not established that.
The driver emits 34 always and the board is correct with it, so this is an open
thread rather than a defect I know about. Say the word and I will send you the
table.

v10 goes out shortly, and I said I would tell you here when it does. It is v9
plus six tags and the note, with no code change: I diffed every patch body
against its v9 counterpart and twelve of thirteen are byte identical, the
thirteenth differing only by the Notes block. Your two tags are on the patches
you sent them for, with the comments as you re-sent them on one line, so 02/13
carries "differential base" and 03/13 does not.

And thank you for the differential arm. The run that signalled success with an
output buffer that was never written, all 48 channels 0x80 and nothing in the
log, is the clearest statement of what these two patches close that anyone has
produced, including me.

Regards,
Jiaxing

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v9 05/13] dt-bindings: npu: rockchip: add rockchip, rk3576-rknn-core
  2026-08-27  1:49       ` Jiaxing Hu
@ 2026-08-27 17:48         ` Igor Paunovic
  2026-08-31  4:07           ` Jiaxing Hu
  0 siblings, 1 reply; 28+ messages in thread
From: Igor Paunovic @ 2026-08-27 17:48 UTC (permalink / raw)
  To: Jiaxing Hu; +Cc: linux-rockchip, dri-devel

Hi Jiaxing,

The depthwise finding is the best kind of answer - the axis was
never mine to guess, and 94 models beat any amount of guessing.
Thank you for closing oc = 64 as well.

Yes, please send the RESERVED_0 table. A constant that follows the
model series rather than the geometry smells like a toolkit setting,
and the table is the way to corner it.

Looking forward to v10.

Regards,
Igor

On Thu, Aug 27, 2026 at 3:49 AM Jiaxing Hu <gahing@gahingwoo.com> wrote:
>
> Hi Igor,
>
> Three things: the note you caught, the oc = 64 answer I have owed you since the
> 17th, and your SIZE_E question from the 20th. The last two turned out to have
> the same source, and it was sitting on my disk the whole time.
>
> First the note, because you were right and I was wrong about why. send-v9.sh
> never passed --notes, so whatever was on the commit could not have reached the
> mail. grep on the patch file I actually sent says
>
>   $ grep -c '^Notes:' rfc-send-v9/v9-0005-*.patch
>   0
>
> so nothing was lost in a rebase and nothing needed recovering; the note is on
> the commit and format-patch --notes emits it. The flag was missing, which is
> exactly what you said. v10's send script regenerates with --notes and then
> refuses to send unless the number of patches carrying a Notes block is exactly
> one. Thank you for saying it before v10 rather than after.
>
> Now oc = 64, concretely, as you asked: SIZE_E_2 = 1.
>
> The expression, which is what the merge request emits and what I should have
> sent instead of the sentence about 0x124 and 0x024:
>
>   (DIV_ROUND_UP(oc, FEATURE_ATOMIC_SIZE) & 1) == 0 ? 0x80011111 : 0x80011011
>
> At oc = 64 that is DIV_ROUND_UP(64, 16) = 4, even, so 0x80011111, so
> SIZE_E_2 = 1. Your reading of the merge request line was right in every detail.
> The sentence was mine and it named the arms by the wrong predicate, so discard
> it and take the expression. You are also right that we should name the arms by
> value and by count, so from here: oc = 64, 0x80011111, SIZE_E_2 = 1.
>
> I did not have to derive that. geom/g_oc64.rknn, compiled at ic 16, 80x80,
> k = 5, stride 2, oc = 64, has DPU 0x4050 = 0x80011111, and so do six more at
> oc = 64. Two others, pp_oc64 at k = 3 stride 1 and pw48x64w56 at ic 48, 56x56,
> k = 1, read 0x80021111, which differs only in RESERVED_0 (see below) and has
> SIZE_E_2 = 1 as well. Nine vendor models at oc = 64 on this disk, all
> SIZE_E_2 = 1, across three kernel sizes, both strides, and spatial sizes from
> 1x1 to 112x112.
>
> Which brings me to your question from the 20th, whether different output
> channel counts would exercise different SIZE_E fields, and whether two shapes
> can rule out one that leans on SIZE_E_1.
>
> I have 94 compiled vendor .rknn on disk from earlier rounds: oc 4 to 1024, ic 3
> to 1024, 1x1 to 224x224, k = 1, 3 and 5, stride 1 and 2, regular and depthwise.
> Reading DPU 0x4050 out of every one of them and decoding it against
> registers.xml:
>
>   SIZE_E_0   4     in all 94, it never moves
>   SIZE_E_1   0     in all 81 regular models
>              1     in all 13 depthwise models
>   SIZE_E_2   0..1  regular, 0..3 depthwise
>
> So the answer is no, and for a sharper reason than "we have not seen it move".
> SIZE_E_1's axis is the depthwise flag, not the channel count. No output channel
> count can be the shape that leans on it, because oc does not select it at all.
> What does is regular against depthwise, and the driver already emits a
> different word entirely on the depthwise path, whose SIZE_E_1 is 1, which is
> the vendor's depthwise value on all thirteen.
>
> That also means my comment in the driver was weaker than the truth. "SIZE_E_1
> left at 0 because two shapes is not every shape" was honest, but 0 is what the
> vendor emits on all 81 regular models, and upstream's 1 is what it emits on
> depthwise. It is not an unexplained traced constant, and I have corrected the
> comment to say so.
>
> Your padding reading holds too, taken literally and with the depthwise padding
> you pointed at. Depthwise pads to 64 output channels, so the last bank holds
> one to four atoms of 16:
>
>   oc            16  32  48  64  80  96  112  128  256  1024
>   last bank     16  32  48  64  16  32   48   64   64    64
>   atoms in it    1   2   3   4   1   2    3    4    4     4
>   predicted      0   1   2   3   0   1    2    3    3     3
>   observed       0   1   2   3   0   1    2    3    3     3
>
> 13 of 13 depthwise models, ten distinct counts, no exceptions, including the
> three the driver's comment says it predicted rather than fitted: 16, 80 and
> 112. I had been carrying that as (atoms - 1) & 3, which gets the same numbers
> and says nothing. Your form is the reason for them.
>
> And while I had all 94 open I scored both candidate readings against the 81
> regular models:
>
>   parity, DIV_ROUND_UP(oc,16) even    0 wrong of 81
>   modulo, oc % 32 == 0                1 wrong of 81
>
> The one point where they disagree in that corpus is ocp56, oc = 56, ic = 64,
> 56x56, k = 1, stride 1, and the vendor emits 0x80021111, SIZE_E_2 = 1, which is
> the parity answer. Every other model has an oc where the two forms agree, which
> is why the original ten point sweep could not choose between them.
>
> That is oc = 56 again. The board picked out a different model at the same
> count, pw64x56w56, as the one shape that times out under the modulo form. The
> board and the vendor's own compiler arrive at the same discriminating count
> from two directions, which is better evidence than either alone and better than
> I claimed at the time.
>
> One thing I cannot explain, flagged rather than claimed. RESERVED_0 is 34 in
> most of the regular models and 66 in a subset of them: 0x80011111 against
> 0x80021111, which inside the field is its bit 5 against its bit 6, both over a
> constant 2. It does not correlate with oc, ic, spatial size, kernel size or
> stride. Both values appear at oc 16, 64 and 128, at k = 1, 3 and 5, and at both
> strides. What it does track is which batch of models it came from, which makes
> a toolkit setting more likely than a geometry, but I have not established that.
> The driver emits 34 always and the board is correct with it, so this is an open
> thread rather than a defect I know about. Say the word and I will send you the
> table.
>
> v10 goes out shortly, and I said I would tell you here when it does. It is v9
> plus six tags and the note, with no code change: I diffed every patch body
> against its v9 counterpart and twelve of thirteen are byte identical, the
> thirteenth differing only by the Notes block. Your two tags are on the patches
> you sent them for, with the comments as you re-sent them on one line, so 02/13
> carries "differential base" and 03/13 does not.
>
> And thank you for the differential arm. The run that signalled success with an
> output buffer that was never written, all 48 channels 0x80 and nothing in the
> log, is the clearest statement of what these two patches close that anyone has
> produced, including me.
>
> Regards,
> Jiaxing

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v9 05/13] dt-bindings: npu: rockchip: add rockchip, rk3576-rknn-core
  2026-08-27 17:48         ` Igor Paunovic
@ 2026-08-31  4:07           ` Jiaxing Hu
  2026-09-02 10:01             ` Igor Paunovic
  0 siblings, 1 reply; 28+ messages in thread
From: Jiaxing Hu @ 2026-08-31  4:07 UTC (permalink / raw)
  To: royalnet026; +Cc: linux-rockchip, dri-devel

Hi Igor,

Here is the table. It does not say what I told you it said, so the
retraction comes first.

I wrote that RESERVED_0 "follows the model series rather than the
geometry". You read that as a toolkit setting and said the table was the
way to corner it. It cornered my own claim instead.

It is not the model series. All 94 files carry the same toolkit build
string, 2.3.2 (@2025-04-03T08:26:16), so there is no version difference
to hang it on. Worse for my sentence: g_pw24 carries BOTH values in one
compile of one model -- the 512 to 1024 pointwise reads 34 and the one
pixel 16x16 op the compiler adds for itself reads 66. Ten of the dwbig_*
files do the same. A per-model constant cannot be right.

It is not the geometry either, and that half I had tested too weakly. I
had checked oc, ic, spatial size, kernel and stride one at a time. Fixing
the WHOLE CNA geometry gives 116 classes over the 364 regular dispatches
and twelve of them carry both values. The cleanest pair is g_cal against
bias_k5: one ONNX Conv each, ic 16, oc 128, 80x80 in, k = 5, stride 2,
same toolkit, and the two .onnx sources are the same 205601 bytes. Their
register streams are 138 words each and differ in five: 0x4044, 0x4050,
0x40ac, 0x40b0 and 0x501c. Three of those five are the output zero point
and the requant pair, which differ between any two models with different
weights. What is left is 0x4044, 0x4050 and 0x501c, and it is the same
three in every conflicting pair.

What it does follow is DPU 0x4044, exactly:

    RESERVED_0 34   <->  0x4044 = 1     260 dispatches
    RESERVED_0 66   <->  0x4044 = 0     104 dispatches

364 of 364, no exceptions, and 0x501c moves with them (0x710 against
0x14/0x114). Sweeping every register in the stream, 0x4044 is the only
one that partitions the two groups one to one. So it is not a loose
constant with no company: it is one third of a single per-op decision in
the bias and scale path of the output stage. What selects that arm I
still cannot tell you.

Two more things the old claim got wrong. There is a THIRD value: 38, on
all 58 depthwise dispatches, which my "34 against 66" never mentioned. 38
is 34 with one more field bit, so on the 16-against-17 word bit axis
depthwise sits with the 34 group while its 0x4044 reads 0 -- which breaks
the lockstep above, and is why I state it as a regular datapath result
only. And the RK3576 encoder has no depthwise path at all
(rkt_ml_operation_supported declines it), so the driver never emits the
38 word: that row is about the vendor and about upstream's RK3588
encoder, not about ours.

For what we ship: rkt_regcmd.c emits 0x4044 = 1, RESERVED_0 = 34 and
0x501c = 0x710 -- the 34 arm in all three at once, never mixed. That is
consistent, and it is the arm 260 of the 364 vendor dispatches take.

Nothing below was run on hardware. It is decoding of files already on
this disk, one op per register run, 0 of the 422 dispatches undecodable.
The script that produces every number in it is small enough to send if
you want to re-run it against your own corpus.

DPU 0x4050 RESERVED_0 across 94 vendor-compiled .rknn on this disk
====================================================================

Regenerate with reserved0-build.py, which reads only the files in
vendor-capture/geom and prints this whole page, numbers included.

How the value is obtained
-------------------------

A .rknn carries the register command stream the vendor runtime
submits, as little-endian u64 words

    [63:48] target   [47:16] value   [15:0] register

with target 0x0201 CNA, 0x0801 CORE, 0x1001 DPU, 0x2001 DPU_RDMA. A
maximal run of such words is one dispatch. In this corpus every run of
20 words or more carries exactly one CNA block and exactly one write
of DPU 0x4050, so a value and the geometry it is tested against always
come from the same op and there is no matching to guess at.

The word is BS_OW_CFG. Split as upstream's registers.xml splits it:

    RGP_CNTER  31:28   TP_ORG_EN  27   RESERVED_0  26:11
    SIZE_E_2   10:8    SIZE_E_1   7:5  SIZE_E_0     4:2
    OD_BYPASS  1       OW_SRC     0

So RESERVED_0 is bits 26:11, and the two values in question are

    0x80011111 -> RESERVED_0 = 34 = field bits 5 and 1 = word bits 16, 12
    0x80021111 -> RESERVED_0 = 66 = field bits 6 and 1 = word bits 17, 12

One word bit apart, 16 against 17, over a constant word bit 12. The
geometry columns come from the CNA registers of the same run:

    ic  = (0x1028 & 0xffff) + 1     oc = (0x1024 & 0xffff) + 1
    k   = sqrt(0x1020 / ic)         stride from 0x1014, which is (s<<3)|s

Three notes on reading those columns honestly.

  * The oc register holds the count rounded up to 2, so a 41 channel
    output reads 42. pq_ic and pq_oc are the two rows where that shows.
  * On the depthwise path, CORE 0x3018 mode 0x0a, the oc register reads
    2 and the channel count is in the ic register. Those rows carry the
    ic one and are marked dw in the path column.
  * On the first convolution path, CORE 0x3018 mode 0x81, three input
    channels are packed and the ic register reads 12, so the ic and k
    columns on those seven rows are the packed form and not the source
    shape. They are marked 1st. All seven read 34 either way.

Nothing here was run on hardware. These are numbers read out of files
the vendor toolkit produced, and what the bits mean was arrived at by
trial and error against those files and against the board.


The table
---------

One row per file, for the first dispatch in the file. The last column
is what the file records about itself, input>output with the batch
dropped. A + on the name means the file holds more than one distinct
RESERVED_0 across its dispatches; those are broken out below.

model           0x4050     R_0 path    ic    oc  k  s  file says
----------------------------------------------------------------

  RESERVED_0 = 34   (regular convolutions)   61 files
a_lin           0x80011111  34 reg     64    64  1  1  64x8x8>64x8x8
a_lin2          0x80011111  34 reg     64    64  1  1  64x8x8>64x8x8
a_lin_m1        0x80011111  34 reg     64    64  1  1  64x1x1>64x1x1
a_relu          0x80011111  34 reg     64    64  1  1  64x8x8>64x8x8
c_32            0x80011011  34 reg     32    16  3  1  32x80x80>16x80x80
c_48            0x80011011  34 reg     48    16  3  1  48x80x80>16x80x80
c_64            0x80011011  34 reg     64    16  3  1  64x80x80>16x80x80
fc_224          0x80011111  34 1st     12    32  2  2  3x224x224>32x112x112
fc_cal127       0x80011111  34 1st     12    32  2  2  3x224x224>32x112x112
fc_cal250       0x80011111  34 1st     12    32  2  2  3x224x224>32x112x112
fc_cal255       0x80011111  34 1st     12    32  2  2  3x224x224>32x112x112
fc_x1           0x80011111  34 1st     12    32  2  2  3x224x224>32x112x112
fc_x16          0x80011111  34 1st     12    32  2  2  3x224x224>32x112x112
fc_x4           0x80011111  34 1st     12    32  2  2  3x224x224>32x112x112
g_cal           0x80011111  34 reg     16   128  5  2  16x80x80>128x40x40
g_cal_k1        0x80011111  34 reg     16   128  1  2  16x80x80>128x40x40
g_cal_k3        0x80011111  34 reg     16   128  3  2  16x80x80>128x40x40
g_cal_oc16      0x80011011  34 reg     16    16  5  2  16x80x80>16x40x40
g_cal_s1        0x80011111  34 reg     16   128  5  1  16x80x80>128x80x80
g_k3s1          0x80011011  34 reg     16    16  3  1  16x80x80>16x80x80
g_md003         0x80011011  34 reg     16    16  1  1  16x160x160>16x160x160
g_md003_80      0x80011011  34 reg     16    16  1  1  16x80x80>16x80x80
g_md003_oc128   0x80011111  34 reg     16   128  1  1  16x80x80>128x80x80
g_md003s2       0x80011011  34 reg     16    16  1  2  16x160x160>16x80x80
g_oc112         0x80011011  34 reg     16   112  5  2  16x80x80>112x40x40
g_oc144         0x80011011  34 reg     16   144  5  2  16x80x80>144x40x40
g_oc160         0x80011111  34 reg     16   160  5  2  16x80x80>160x40x40
g_oc32          0x80011111  34 reg     16    32  5  2  16x80x80>32x40x40
g_oc48          0x80011011  34 reg     16    48  5  2  16x80x80>48x40x40
g_oc64          0x80011111  34 reg     16    64  5  2  16x80x80>64x40x40
g_oc80          0x80011011  34 reg     16    80  5  2  16x80x80>80x40x40
g_oc96          0x80011111  34 reg     16    96  5  2  16x80x80>96x40x40
g_pw2           0x80011111  34 reg     32    64  1  1  32x112x112>64x112x112
g_pw24+         0x80011111  34 reg    512  1024  1  1  512x7x7>1024x7x7
p0_k1           0x80011011  34 reg     16    16  1  1  16x80x80>16x80x80
p0_k5           0x80011011  34 reg     16    16  5  1  16x80x80>16x76x76
p1_k3           0x80011011  34 reg     16    16  3  1  16x80x80>16x80x80
p1_k5           0x80011011  34 reg     16    16  5  1  16x80x80>16x78x78
p2_k5           0x80011011  34 reg     16    16  5  1  16x80x80>16x80x80
sv_k3           0x80011111  34 reg     16   128  3  2  16x80x80>128x40x40
sv_k5           0x80011111  34 reg     16   128  5  2  16x80x80>128x40x40
sv_null_a       0x80011111  34 reg     16   128  5  2  16x80x80>128x40x40
sv_null_b       0x80011111  34 reg     16   128  5  2  16x80x80>128x40x40
sv_oc128        0x80011111  34 reg     16   128  5  2  16x80x80>128x40x40
sv_oc64         0x80011111  34 reg     16    64  5  2  16x80x80>64x40x40
sv_rg           0x80011111  34 reg     32    32  3  1  32x112x112>32x112x112
sv_rgu          0x80011111  34 reg     32    32  3  1  32x112x112>32x112x112
sv_sc_base      0x80011111  34 reg     16   128  5  2  16x80x80>128x40x40
sv_sc_moved     0x80011111  34 reg     16   128  5  2  16x80x80>128x40x40
sv_wt_a         0x80011111  34 reg     16   128  5  2  16x80x80>128x40x40
sv_wt_b         0x80011111  34 reg     16   128  5  2  16x80x80>128x40x40
w_112           0x80011011  34 reg     16    16  3  1  16x112x112>16x112x112
w_120           0x80011011  34 reg     16    16  3  1  16x120x120>16x120x120
w_128           0x80011011  34 reg     16    16  3  1  16x128x128>16x128x128
w_144           0x80011011  34 reg     16    16  3  1  16x144x144>16x144x144
w_160           0x80011011  34 reg     16    16  3  1  16x160x160>16x160x160
w_40            0x80011011  34 reg     16    16  3  1  16x40x40>16x40x40
w_56            0x80011011  34 reg     16    16  3  1  16x56x56>16x56x56
w_64            0x80011011  34 reg     16    16  3  1  16x64x64>16x64x64
w_80            0x80011011  34 reg     16    16  3  1  16x80x80>16x80x80
w_96            0x80011011  34 reg     16    16  3  1  16x96x96>16x96x96

  RESERVED_0 = 66   (regular convolutions)   20 files
bias_k3         0x80021111  66 reg     16   128  3  2  16x80x80>128x40x40
bias_k5         0x80021111  66 reg     16   128  5  2  16x80x80>128x40x40
bias_ramp       0x80021011  66 reg     16    16  3  1  16x80x80>16x80x80
lane_ic         0x80021011  66 reg     16    16  3  1  16x80x80>16x80x80
lane_oc         0x80021011  66 reg     16    16  3  1  16x80x80>16x80x80
ocp34           0x80021011  66 reg     64    34  1  1  64x56x56>34x56x56
ocp40           0x80021011  66 reg     64    40  1  1  64x56x56>40x56x56
ocp56           0x80021111  66 reg     64    56  1  1  64x56x56>56x56x56
pp_k3           0x80021011  66 reg     16    16  3  1  16x80x80>16x80x80
pp_k3b          0x80021011  66 reg     16    16  3  1  16x80x80>16x80x80
pp_oc64         0x80021111  66 reg     16    64  3  1  16x80x80>64x80x80
pp_p1           0x80021011  66 reg     16    16  1  1  16x80x80>16x80x80
pp_p3           0x80021011  66 reg     16    16  3  1  16x80x80>16x80x80
pp_p5           0x80021011  66 reg     16    16  5  1  16x80x80>16x80x80
pp_pw           0x80021011  66 reg     64     4  1  1  64x80x80>4x80x80
pq_ic           0x80021011  66 reg     64    42  1  1  64x56x56>41x56x56
pq_oc           0x80021011  66 reg     64    42  1  1  64x56x56>41x56x56
pw48x64w56      0x80021111  66 reg     48    64  1  1  48x56x56>64x56x56
tq_ic           0x80021011  66 reg     48    40  1  1  48x40x40>40x40x40
tq_oc           0x80021011  66 reg     48    40  1  1  48x40x40>40x40x40

  RESERVED_0 = 38   (depthwise; a third value, see below)   13 files
dwbig_1024+     0x00013333  38 dw    1024     2  3  1  1024x7x7>1024x7x7
dwbig_112+      0x00013233  38 dw     112     2  3  1  112x7x7>112x7x7
dwbig_128+      0x00013333  38 dw     128     2  3  1  128x7x7>128x7x7
dwbig_16+       0x00013033  38 dw      16     2  3  1  16x7x7>16x7x7
dwbig_256+      0x00013333  38 dw     256     2  3  1  256x7x7>256x7x7
dwbig_32+       0x00013133  38 dw      32     2  3  1  32x7x7>32x7x7
dwbig_48+       0x00013233  38 dw      48     2  3  1  48x7x7>48x7x7
dwbig_64+       0x00013333  38 dw      64     2  3  1  64x7x7>64x7x7
dwbig_80+       0x00013033  38 dw      80     2  3  1  80x7x7>80x7x7
dwbig_96+       0x00013133  38 dw      96     2  3  1  96x7x7>96x7x7
g_dw1           0x00013133  38 dw      32     2  3  1  32x112x112>32x112x112
sv_dw           0x00013133  38 dw      32     2  3  1  32x112x112>32x112x112
sv_dwu          0x00013133  38 dw      32     2  3  1  32x112x112>32x112x112


The counts
----------

  files                                  94
  dispatches in them                     422
  dispatches that could not be decoded   0
  regular dispatches                     364
  depthwise dispatches                   58

  by dispatch, RESERVED_0
    34    260
    38     58
    66    104

  by file, first dispatch
    34     61
    38     13
    66     20

  distinct 0x4050 words, 8 of them
    word         count  RGP  R_0  S_E2 S_E1 S_E0 OD   OW
    0x80011111   140  8    34   1    0    4    0    1
    0x80011011   120  8    34   0    0    4    0    1
    0x80021011    84  8    66   0    0    4    0    1
    0x00013133    26  0    38   1    1    4    1    1
    0x80021111    20  8    66   1    0    4    0    1
    0x00013333    16  0    38   3    1    4    1    1
    0x00013233     8  0    38   2    1    4    1    1
    0x00013033     8  0    38   0    1    4    1    1

  the other fields, for completeness
    RGP_CNTER  regular {8: 364}
               dw      {0: 58}
    TP_ORG_EN  regular {0: 364}
               dw      {0: 58}
    SIZE_E_0   regular {4: 364}
               dw      {4: 58}
    SIZE_E_1   regular {0: 364}
               dw      {1: 58}
    SIZE_E_2   regular {0: 204, 1: 160}
               dw      {0: 8, 1: 26, 2: 8, 3: 16}
    OD_BYPASS  regular {0: 364}
               dw      {1: 58}
    OW_SRC     regular {1: 364}
               dw      {1: 58}


Does it follow the geometry?
----------------------------

No. Grouping the 364 regular dispatches by their full CNA geometry --
ic, oc, kernel, stride, input width, rows, output width, pixels and
surface -- gives 116 distinct classes, and 12 of them contain both
values. Read that as 5 facts and not 12: a model appears in several
classes because the compiler splits it into CBUF windows of different
heights, and each window is its own class here. Same geometry,
different RESERVED_0, in the same corpus:

  ic 16  oc 128  k 5  stride 2  80x80 in, 1600 pixels out
    34: g_cal, sv_k5, sv_null_a, sv_null_b, sv_oc128, sv_sc_base,
        sv_sc_moved, sv_wt_a, sv_wt_b
    66: bias_k5
  ic 16  oc 16  k 3  stride 1  80x80 in, 6400 pixels out
    34: g_k3s1, p1_k3, w_80
    66: bias_ramp, lane_ic, lane_oc, pp_k3, pp_k3b, pp_p3
  ic 16  oc 16  k 3  stride 1  80x41 in, 3200 pixels out
    34: g_k3s1, p1_k3, w_80
    66: bias_ramp, lane_ic, lane_oc, pp_k3, pp_k3b, pp_p3
  ic 16  oc 128  k 5  stride 2  80x41 in, 800 pixels out
    34: g_cal, sv_k5, sv_null_a, sv_null_b, sv_oc128, sv_sc_base,
        sv_sc_moved, sv_wt_a, sv_wt_b
    66: bias_k5
  ic 16  oc 128  k 5  stride 2  80x42 in, 800 pixels out
    34: g_cal, sv_k5, sv_null_a, sv_null_b, sv_oc128, sv_sc_base,
        sv_sc_moved, sv_wt_a, sv_wt_b
    66: bias_k5
  ic 16  oc 128  k 3  stride 2  80x80 in, 1600 pixels out
    34: g_cal_k3, sv_k3
    66: bias_k3
  ic 16  oc 16  k 1  stride 1  80x80 in, 6400 pixels out
    34: g_md003_80, p0_k1
    66: pp_p1
  ic 16  oc 16  k 1  stride 1  80x40 in, 3200 pixels out
    34: g_md003_80, p0_k1
    66: pp_p1
  ic 16  oc 16  k 5  stride 1  80x80 in, 6400 pixels out
    34: p2_k5
    66: pp_p5
  ic 16  oc 16  k 5  stride 1  80x42 in, 3200 pixels out
    34: p2_k5
    66: pp_p5
  ic 16  oc 128  k 3  stride 2  80x40 in, 800 pixels out
    34: g_cal_k3, sv_k3
    66: bias_k3
  ic 16  oc 128  k 3  stride 2  80x41 in, 800 pixels out
    34: g_cal_k3, sv_k3
    66: bias_k3

The cleanest single pair is g_cal against bias_k5. Both are one ONNX
Conv, ic 16, oc 128, 80x80 input, k = 5, stride 2, compiled by the
same toolkit build, and the two .onnx sources are the same 205601
bytes -- they differ in the weight and bias VALUES and in nothing
else. Their first dispatches are 138 registers each and differ in
five of them:
    t=1001 r=4044   g_cal 00000001   bias_k5 00000000
    t=1001 r=4050   g_cal 80011111   bias_k5 80021111
    t=1001 r=40ac   g_cal fffffff8   bias_k5 fffffff5
    t=1001 r=40b0   g_cal 000064f9   bias_k5 00006f37
    t=2001 r=501c   g_cal 00000710   bias_k5 00000114

Three more same-geometry pairs, one from each of the other groups:

  g_k3s1 against pp_k3: 6 registers differ, 4044 4050 40ac 40b0 40b4 501c
  g_cal_k3 against bias_k3: 6 registers differ, 4044 4050 40ac 40b0 40b4 501c
  p2_k5 against pp_p5: 6 registers differ, 4044 4050 40ac 40b0 40b4 501c

0x40ac, 0x40b0 and 0x40b4 are the output zero point, the requant
multiplier and the requant shift. Those differ between any two models
with different weights and say nothing here. What is left in every
pair is 0x4044, 0x4050 and 0x501c, the same three every time.


Does it follow the model series?
--------------------------------

Not that either, and one form of the guess dies immediately: all 94
files carry the same toolkit build string,

    2.3.2 (@2025-04-03T08:26:16)   94 files

so it is not a toolkit version difference.

And a per-model constant cannot be right, because 11 files hold more
than one value inside a single compile:

  g_pw24, 512x7x7>1024x7x7, one Conv in the source graph.
  Its dispatches, by byte offset in the file:
    0x4980   0x80011111  R_0 34      ic 512   oc 1024  k 1  49 px
    0x4e00   0x80011111  R_0 34      ic 512   oc 1024  k 1  49 px
    0x5280   0x80011111  R_0 34      ic 512   oc 1024  k 1  28 px
    0x5700   0x80021011  R_0 66      ic 16    oc 16    k 1  1 px
    0x5b80   0x80011111  R_0 34      ic 512   oc 1024  k 1  21 px
    0x6000   0x80021011  R_0 66      ic 16    oc 16    k 1  1 px

  and the same shape in the other 10, all of them dwbig_*:
  dwbig_1024, 1024x7x7>1024x7x7, one Conv in the source graph.
  Its dispatches, by byte offset in the file:
    0x6d80   0x00013333  R_0 38  dw  ic 1024  oc 2     k 3  49 px
    0x7200   0x00013333  R_0 38  dw  ic 1024  oc 2     k 3  49 px
    0x7680   0x00013333  R_0 38  dw  ic 1024  oc 2     k 3  28 px
    0x7b00   0x80021011  R_0 66      ic 16    oc 16    k 1  1 px
    0x7f80   0x00013333  R_0 38  dw  ic 1024  oc 2     k 3  21 px
    0x8400   0x80021011  R_0 66      ic 16    oc 16    k 1  1 px
    ... and the same pattern in dwbig_112, dwbig_128, dwbig_16, ...

g_pw24 is the one that settles it. One Conv in the source graph, one
compile, one file, and its two REGULAR dispatches disagree: the 512 to
1024 pointwise reads 34 and the one-pixel 16 by 16 op the compiler
adds for itself reads 66. Whatever selects the value is decided per
dispatch, not per model and not per batch of models.


What it does follow
-------------------

DPU 0x4044, exactly, on every regular dispatch in the corpus:

    RESERVED_0 34   <->  0x4044 = 0x00000001    260 dispatches
    RESERVED_0 66   <->  0x4044 = 0x00000000    104 dispatches

364 of 364, no exceptions. 0x2001/0x501c moves with them:

    RESERVED_0 34   ->  0x501c 0x710 (260)
    RESERVED_0 66   ->  0x501c 0x14 (22), 0x114 (82)

Sweeping every register in the stream, 0x4044 is the ONLY one whose
value partitions the two RESERVED_0 groups one-to-one, and 0x501c the
only other whose value sets are disjoint between them. So the bit is
not a loose constant with no company: it is one third of a single
per-op decision in the bias-and-scale path of the output stage.

The depthwise dispatches are the honest limit on that. They read
    RESERVED_0 38, 0x4044 = 0x0, 0x501c = 0x510, 58 dispatches
38 is 34 with field bit 2 -- word bit 13 -- added, so on the word-bit
16-against-17 axis depthwise sits with the 34 group while its 0x4044
sits with the 66 group. The lockstep is a statement about the regular
datapath only, and I am not claiming more than that.

What selects the arm in the first place is still open. It is not the
shape, not the file, not the toolkit build, and not the day the file
was compiled: 2026-08-08 and 2026-08-09 each produced both arms.


What this means for the driver
------------------------------

rkt_regcmd.c emits, for a regular convolution,

    0x4044 = 0x00000001
    0x4050 = 0x80011011 or 0x80011111   (RESERVED_0 = 34)
    0x501c = 0x00000710

which is the 34 arm in all three registers at once. It is the arm the
vendor takes on 260 of its 364 regular dispatches, and the driver never
mixes an 0x4044 from one arm with a 0x4050 from the other. That is
the part I could not say in the earlier mail and can say now.

The RK3576 encoder has no depthwise path at all -- fill_regcmd_rk3576
covers a regular convolution and rkt_ml_operation_supported declines
depthwise before it is reached -- so the driver never emits the 38
word. That is an observation about the vendor's depthwise datapath
and about upstream's RK3588 encoder, not about this one.

The field is also not free: moved on its own to upstream's 0, on a 5x5
at 128 output channels and a pointwise at 88, the output stayed
identical to the baseline and the job timed out. So it is a completion
field, and 34 is the value that completes.


What of the claim I sent
------------------------

The counting half stands. 34 and 66 are the only two RESERVED_0 values
on regular convolutions, 34 is the common one, 260 dispatches against
104, and 61 files against 20 by first dispatch.

Three things in it were wrong or too small, and I would rather correct
them here than let them stand.

1. "It does not correlate with oc, ic, spatial size, kernel size or
   stride" is true but it was the weak version of the test. The strong
   version is the one you asked for: hold the WHOLE geometry fixed.
   12 geometry classes then carry both values, 5 of them once the CBUF
   windows of one model are folded together, and g_cal against bias_k5
   is two files identical in every register but five.

2. "What it does track is which batch of models it came from, which
   makes a toolkit setting more likely" does not survive as written.
   The batches are uniform, but the batch is not what decides:
   g_pw24 carries both values in one compile of one Conv, and the
   toolkit build string is identical across all 94 files. Why a whole
   batch lands on one arm I still cannot say.

3. It only ever mentioned two values. There is a third, 38, on all 58
   depthwise dispatches. The RK3576 encoder declines depthwise so it
   never emits that word, but the corpus has it and the table should
   have said so.

The one thing the earlier mail did not have is the answer to your
question. It is not a toolkit setting sitting on its own; it is a bit
of a three-register per-op choice, and the driver takes one side of
that choice consistently.


Regards,
Jiaxing

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v9 05/13] dt-bindings: npu: rockchip: add rockchip, rk3576-rknn-core
  2026-08-31  4:07           ` Jiaxing Hu
@ 2026-09-02 10:01             ` Igor Paunovic
  2026-09-03 11:22               ` Jiaxing Hu
  0 siblings, 1 reply; 28+ messages in thread
From: Igor Paunovic @ 2026-09-02 10:01 UTC (permalink / raw)
  To: Jiaxing Hu; +Cc: Igor Paunovic, linux-rockchip, dri-devel

Hi Jiaxing,

> Here is the table. It does not say what I told you it said, so the
> retraction comes first.

Thank you for the table, and for leading with the retraction. The
replacement is worth more than the sentence it replaces: 364 of 364
with a single register partitioning the two groups one to one is a
field with a reason, even if what selects the arm is still open.

The third value is the honest kind of catch too - a row that breaks
the lockstep and narrows the claim to the regular datapath instead
of being averaged into it.

> The script that produces every number in it is small enough to
> send if you want to re-run it against your own corpus.

Yes, please send it. One honest note first: there is no vendor
.rknn on this disk - everything on my RK3588 side is upstream Mesa
and generated TFLite, and the script reads .rknn. So the run worth
doing from here is the axis your corpus cannot test: all 94 of your
files carry the same 2.3.2 build string, and I will try to collect
files produced by other toolkit versions and point the script at
those. If 34/66 <-> 0x4044 holds across versions, that is one more
thing RESERVED_0 is not.

On v11: the clocks patch carried exactly as sent, authorship chain
intact. Thank you for doing that carefully.

Igor

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v9 05/13] dt-bindings: npu: rockchip: add rockchip, rk3576-rknn-core
  2026-09-02 10:01             ` Igor Paunovic
@ 2026-09-03 11:22               ` Jiaxing Hu
  0 siblings, 0 replies; 28+ messages in thread
From: Jiaxing Hu @ 2026-09-03 11:22 UTC (permalink / raw)
  To: royalnet026; +Cc: linux-rockchip, dri-devel

Hi Igor,

Here it is, below the line. It is the file that printed every number in
the table, changed only where it had to be before it could run on a
machine that is not mine. The reader for the container is inlined, so
it needs numpy and nothing else. The directory is an argument:

    python3 reserved0-build.py DIR > out.md

And the parts of the page that only hold for my files print only when
those files are present. That covers the named same-geometry pairs,
g_pw24, and the note on my earlier mail.

The meaning of the bits came from trial and error against the files and
the board, so where your corpus disagrees with mine the numbers will
disagree. The "What it does follow" block tests the 0x4044 pairing on
whatever it is given and reports one-to-one or not, with the counts.

Thank you for checking 01/14 in v11.

Regards,
Jiaxing

---8<---
#!/usr/bin/env python3
# Copyright (c) 2026 Jiaxing Hu <gahing@gahingwoo.com>
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Build the RESERVED_0 table for DPU register 0x4050, out of the vendor-compiled
.rknn files in the directory it is given.

This is the table promised to Igor Paunovic on linux-rockchip in the v9 05/13
thread, where the claim was that RESERVED_0 "is 34 in most of the regular
models and 66 in a subset", tracking the model series rather than the geometry.
That claim was written once and never re-derived. This re-derives it from the
files and prints whatever the files say, including where they contradict it.

Method, so it can be checked rather than believed. A .rknn carries the register
command stream the vendor runtime submits, as little-endian u64 words

    [63:48] target   [47:16] value   [15:0] register

with 0x0201 CNA, 0x0801 CORE, 0x1001 DPU, 0x2001 DPU_RDMA and a few others. A
maximal run of such words is one dispatch. Every run of 20 words or more in
this corpus carries exactly one CNA block and exactly one write of DPU 0x4050,
so one run is one convolution and there is no ambiguity about which op a value
belongs to. Nothing here is run on hardware and nothing is taken apart: the
numbers are read out of files the vendor toolkit produced, and the meaning of
the bits was arrived at by trial and error against those files and the board.

The field split is upstream's, from mesa's rocket registers.xml, BS_OW_CFG:

    RGP_CNTER  31:28   TP_ORG_EN  27   RESERVED_0  26:11
    SIZE_E_2   10:8    SIZE_E_1   7:5  SIZE_E_0     4:2
    OD_BYPASS  1       OW_SRC     0

The geometry columns are read from the same run's CNA registers, so the value
and the geometry it is being tested against come from one dispatch:

    ic  = (0x1028 & 0xffff) + 1        oc = (0x1024 & 0xffff) + 1
    k   = sqrt(0x1020 / ic)            stride from 0x1014, which holds (s<<3)|s

On the depthwise path the oc register reads 2 and the channel count is in the
ic register; those rows are marked and their channel count is the ic one.

Usage:  reserved0-build.py [geom-dir] > reserved0-table.md
"""
import glob
import math
import os
import re
import sys
from collections import Counter, defaultdict

import numpy as np

# The container reader, the same two functions charsiu's tools/rkllm_regcmd.py
# carries, copied in so this file runs on its own with numpy and nothing else.
TARGETS = {0x0201: "CNA", 0x0801: "CORE", 0x1001: "DPU",
           0x2001: "RDMA", 0x0401: "U28", 0x0041: "SYNC", 0x0081: "BCAST"}


def streams(path, min_len=20):
    """Every maximal run of register command words, as (byte offset, words)."""
    n = os.path.getsize(path) // 8
    words = np.memmap(path, dtype="<u8", mode="r", shape=(n,))
    ok = np.isin((words >> 48).astype(np.uint32), list(TARGETS))
    idx = np.flatnonzero(ok)
    if idx.size == 0:
        return []
    out = []
    for run in np.split(idx, np.flatnonzero(np.diff(idx) != 1) + 1):
        if len(run) >= min_len:
            out.append((int(run[0]) * 8, words[run[0]:run[-1] + 1]))
    return out


def decode(ws):
    """One stream as {(target, register): value}, first write wins."""
    regs = {}
    for e in ws:
        e = int(e)
        key = ((e >> 48) & 0xffff, e & 0xffff)
        if key not in regs:
            regs[key] = (e >> 16) & 0xffffffff
    return regs

GEOM = sys.argv[1] if len(sys.argv) > 1 else "."
# The sections that name files -- g_cal against bias_k5, g_pw24, and the
# note on the earlier mail -- are about the corpus this was written against.
# On another corpus they are skipped rather than printed with the wrong
# numbers under the same sentences.
MINE = all(os.path.exists(os.path.join(GEOM, f + "_rk3576.rknn"))
           for f in ("g_cal", "bias_k5", "g_pw24", "g_k3s1", "pp_k3",
                     "g_cal_k3", "bias_k3", "p2_k5", "pp_p5"))

CNA, CORE, DPU, RDMA = 0x0201, 0x0801, 0x1001, 0x2001


def fields(v):
    return dict(RGP_CNTER=(v >> 28) & 0xf, TP_ORG_EN=(v >> 27) & 1,
                RESERVED_0=(v >> 11) & 0xffff, SIZE_E_2=(v >> 8) & 7,
                SIZE_E_1=(v >> 5) & 7, SIZE_E_0=(v >> 2) & 7,
                OD_BYPASS=(v >> 1) & 1, OW_SRC=v & 1)


def declared(path):
    """What the file itself says it is: the toolkit build string and the
    input and output shapes it records, plus the ONNX ops it names."""
    d = open(path, "rb").read()
    m = re.search(rb"compiler version: ([0-9][^)]*\))", d)
    ver = m.group(1).decode().strip() if m else "?"
    i = re.search(rb"'shape': \[([0-9, ]+)\], 'layout'", d)
    o = re.search(rb"'output': \{'is_output': True, 'idx': 0, "
                  rb"'shape': \[([0-9, ]+)\]", d)
    def shp(mm):
        if not mm:
            return "?"
        p = [x.strip() for x in mm.group(1).decode().split(",")]
        return "x".join(p[1:])            # drop the batch dimension
    ops = sorted(set(x.decode() for x in re.findall(rb"([A-Za-z]+):/", d)))
    return ver, "%s>%s" % (shp(i), shp(o)), ",".join(ops)


def read_ops(path):
    """Every dispatch in one file, as a dict of value and geometry."""
    out = []
    for off, ws in streams(path, min_len=20):
        r = decode(ws)
        v = r.get((DPU, 0x4050))
        if v is None:                     # never happens in this corpus
            out.append(dict(off=off, undecoded=True))
            continue
        core = r.get((CORE, 0x3018))
        mode = (core & 0xff) if core is not None else None
        dw = mode == 0x0a
        ic = (r[(CNA, 0x1028)] & 0xffff) + 1 if (CNA, 0x1028) in r else None
        oc = (r[(CNA, 0x1024)] & 0xffff) + 1 if (CNA, 0x1024) in r else None
        wbpk = r.get((CNA, 0x1020))
        k = None
        if wbpk and ic:
            root = int(round(math.sqrt(wbpk / ic)))
            k = root if root * root * ic == wbpk else None
        st = r.get((CNA, 0x1014))
        rec = dict(off=off, undecoded=False, v=v, dw=dw, mode=mode,
                   ic=ic, oc=oc, k=k,
                   stride=(st & 7) if st is not None else None,
                   inw=((r[(CNA, 0x102c)] >> 16) & 0xffff) + 1
                       if (CNA, 0x102c) in r else None,
                   rows=(r[(CNA, 0x102c)] & 0xffff) + 1
                       if (CNA, 0x102c) in r else None,
                   ow=(r[(CNA, 0x1030)] & 0xffff) + 1
                       if (CNA, 0x1030) in r else None,
                   pix=r[(CNA, 0x1034)] + 1 if (CNA, 0x1034) in r else None,
                   surf=(r[(CNA, 0x103c)] >> 16) & 0xffff
                       if (CNA, 0x103c) in r else None,
                   r4044=r.get((DPU, 0x4044)), r501c=r.get((RDMA, 0x501c)),
                   r40ac=r.get((DPU, 0x40ac)), r40b0=r.get((DPU, 0x40b0)),
                   r40b4=r.get((DPU, 0x40b4)), regs=r)
        rec.update(fields(v))
        # the channel count the op really works on
        rec["chan"] = rec["ic"] if dw else rec["oc"]
        out.append(rec)
    return out


def geo_key(o):
    return (o["ic"], o["oc"], o["k"], o["stride"], o["inw"], o["rows"],
            o["ow"], o["pix"], o["surf"])


def main():
    files = sorted(glob.glob(os.path.join(GEOM, "*.rknn")))
    if not files:
        sys.stderr.write("no .rknn files under %s\n" % GEOM)
        return 1
    per_file, allops = {}, []
    for p in files:
        name = os.path.basename(p).replace("_rk3576.rknn", "")
        ver, shape, onnxops = declared(p)
        ops = read_ops(p)
        for o in ops:
            o["file"] = name
        per_file[name] = dict(ver=ver, shape=shape, onnxops=onnxops, ops=ops)
        allops += ops

    good = [o for o in allops if not o["undecoded"]]
    bad = [o for o in allops if o["undecoded"]]
    reg = [o for o in good if not o["dw"]]
    dwo = [o for o in good if o["dw"]]

    W = []                                 # the report, line by line
    def p(s=""):
        W.append(s)

    p("DPU 0x4050 RESERVED_0 across %d vendor-compiled .rknn" % len(files))
    p("=" * 68)
    p()
    p("Regenerate with reserved0-build.py, which reads only the files in")
    p("the directory it is given and prints this whole page, numbers included.")
    p()

    # ---------------------------------------------------------------- method
    p("How the value is obtained")
    p("-" * 25)
    p()
    p("A .rknn carries the register command stream the vendor runtime")
    p("submits, as little-endian u64 words")
    p()
    p("    [63:48] target   [47:16] value   [15:0] register")
    p()
    p("with target 0x0201 CNA, 0x0801 CORE, 0x1001 DPU, 0x2001 DPU_RDMA. A")
    p("maximal run of such words is one dispatch. In this corpus every run of")
    p("20 words or more carries exactly one CNA block and exactly one write")
    p("of DPU 0x4050, so a value and the geometry it is tested against always")
    p("come from the same op and there is no matching to guess at.")
    p()
    p("The word is BS_OW_CFG. Split as upstream's registers.xml splits it:")
    p()
    p("    RGP_CNTER  31:28   TP_ORG_EN  27   RESERVED_0  26:11")
    p("    SIZE_E_2   10:8    SIZE_E_1   7:5  SIZE_E_0     4:2")
    p("    OD_BYPASS  1       OW_SRC     0")
    p()
    p("So RESERVED_0 is bits 26:11, and the two values in question are")
    p()
    p("    0x80011111 -> RESERVED_0 = 34 = field bits 5 and 1 = word bits 16, 12")
    p("    0x80021111 -> RESERVED_0 = 66 = field bits 6 and 1 = word bits 17, 12")
    p()
    p("One word bit apart, 16 against 17, over a constant word bit 12. The")
    p("geometry columns come from the CNA registers of the same run:")
    p()
    p("    ic  = (0x1028 & 0xffff) + 1     oc = (0x1024 & 0xffff) + 1")
    p("    k   = sqrt(0x1020 / ic)         stride from 0x1014, which is (s<<3)|s")
    p()
    p("Three notes on reading those columns honestly.")
    p()
    p("  * The oc register holds the count rounded up to 2, so a 41 channel")
    p("    output reads 42. pq_ic and pq_oc are the two rows where that shows.")
    p("  * On the depthwise path, CORE 0x3018 mode 0x0a, the oc register reads")
    p("    2 and the channel count is in the ic register. Those rows carry the")
    p("    ic one and are marked dw in the path column.")
    p("  * On the first convolution path, CORE 0x3018 mode 0x81, three input")
    p("    channels are packed and the ic register reads 12, so the ic and k")
    p("    columns on those seven rows are the packed form and not the source")
    p("    shape. They are marked 1st. All seven read 34 either way.")
    p()
    p("Nothing here was run on hardware. These are numbers read out of files")
    p("the vendor toolkit produced, and what the bits mean was arrived at by")
    p("trial and error against those files and against the board.")
    p()

    # ----------------------------------------------------------------- table
    p()
    p("The table")
    p("-" * 9)
    p()
    p("One row per file, for the first dispatch in the file. The last column")
    p("is what the file records about itself, input>output with the batch")
    p("dropped. A + on the name means the file holds more than one distinct")
    p("RESERVED_0 across its dispatches; those are broken out below.")
    p()
    hdr = "%-15s %-10s %3s %-4s %5s %5s %2s %2s  %s" % (
        "model", "0x4050", "R_0", "path", "ic", "oc", "k", "s", "file says")
    p(hdr)
    p("-" * min(78, len(hdr)))

    def row(name, f, o):
        vals = set(x["RESERVED_0"] for x in f["ops"] if not x["undecoded"])
        path = {0x01: "reg", 0x0a: "dw", 0x81: "1st"}.get(o["mode"], "?")
        return "%-15s 0x%08x %3d %-4s %5s %5s %2s %2s  %s" % (
            name + ("+" if len(vals) > 1 else ""), o["v"], o["RESERVED_0"],
            path, o["ic"], o["oc"], o["k"] if o["k"] else "?", o["stride"],
            f["shape"])

    groups = [("RESERVED_0 = 34   (regular convolutions)",
               lambda o: not o["dw"] and o["RESERVED_0"] == 34),
              ("RESERVED_0 = 66   (regular convolutions)",
               lambda o: not o["dw"] and o["RESERVED_0"] == 66),
              ("RESERVED_0 = 38   (depthwise; a third value, see below)",
               lambda o: o["dw"])]
    counted = 0
    for title, pred in groups:
        sel = [(n, f, f["ops"][0]) for n, f in sorted(per_file.items())
               if not f["ops"][0]["undecoded"] and pred(f["ops"][0])]
        p()
        p("  " + title + "   %d files" % len(sel))
        for n, f, o in sel:
            p(row(n, f, o))
        counted += len(sel)
    left = [n for n, f in sorted(per_file.items())
            if f["ops"][0]["undecoded"]]
    if left:
        p()
        p("  could not be decoded: %s" % ", ".join(left))

    # ---------------------------------------------------------------- counts
    p()
    p()
    p("The counts")
    p("-" * 10)
    p()
    p("  files                                  %d" % len(files))
    p("  dispatches in them                     %d" % len(allops))
    p("  dispatches that could not be decoded   %d" % len(bad))
    p("  regular dispatches                     %d" % len(reg))
    p("  depthwise dispatches                   %d" % len(dwo))
    p()
    c = Counter(o["RESERVED_0"] for o in good)
    p("  by dispatch, RESERVED_0")
    for v, n in sorted(c.items()):
        p("    %-4d %4d" % (v, n))
    p()
    prim = Counter(f["ops"][0]["RESERVED_0"] for f in per_file.values()
                   if not f["ops"][0]["undecoded"])
    p("  by file, first dispatch")
    for v, n in sorted(prim.items()):
        p("    %-4d %4d" % (v, n))
    p()
    cw = Counter(o["v"] for o in good)
    p("  distinct 0x4050 words, %d of them" % len(cw))
    p("    %-12s %5s  %-4s %-4s %-4s %-4s %-4s %-4s %s"
      % ("word", "count", "RGP", "R_0", "S_E2", "S_E1", "S_E0", "OD", "OW"))
    for v, n in sorted(cw.items(), key=lambda t: -t[1]):
        f = fields(v)
        p("    0x%08x %5d  %-4d %-4d %-4d %-4d %-4d %-4d %d"
          % (v, n, f["RGP_CNTER"], f["RESERVED_0"], f["SIZE_E_2"],
             f["SIZE_E_1"], f["SIZE_E_0"], f["OD_BYPASS"], f["OW_SRC"]))
    p()
    p("  the other fields, for completeness")
    for fld in ("RGP_CNTER", "TP_ORG_EN", "SIZE_E_0", "SIZE_E_1", "SIZE_E_2",
                "OD_BYPASS", "OW_SRC"):
        a = Counter(o[fld] for o in reg)
        b = Counter(o[fld] for o in dwo)
        p("    %-10s regular %s" % (fld, dict(sorted(a.items()))))
        p("    %-10s dw      %s" % ("", dict(sorted(b.items()))))

    # ------------------------------------------------- series or geometry
    p()
    p()
    p("Does it follow the geometry?")
    p("-" * 28)
    p()
    cls = defaultdict(list)
    for o in reg:
        cls[geo_key(o)].append(o)
    confl = [(k, v) for k, v in cls.items()
             if len(set(x["RESERVED_0"] for x in v)) > 1]
    p("No. Grouping the %d regular dispatches by their full CNA geometry --" % len(reg))
    p("ic, oc, kernel, stride, input width, rows, output width, pixels and")
    p("surface -- gives %d distinct classes, and %d of them contain both" % (len(cls), len(confl)))
    ngroups = len(set(
        (tuple(sorted(set(x["file"] for x in v if x["RESERVED_0"] == 34))),
         tuple(sorted(set(x["file"] for x in v if x["RESERVED_0"] == 66))))
        for _, v in confl))
    p("values. Read that as %d facts and not %d: a model appears in several"
      % (ngroups, len(confl)))
    p("classes because the compiler splits it into CBUF windows of different")
    p("heights, and each window is its own class here. Same geometry,")
    p("different RESERVED_0, in the same corpus:")
    p()
    for k, v in sorted(confl, key=lambda t: -len(t[1])):
        a = sorted(set(x["file"] for x in v if x["RESERVED_0"] == 34))
        b = sorted(set(x["file"] for x in v if x["RESERVED_0"] == 66))
        p("  ic %s  oc %s  k %s  stride %s  %sx%s in, %s pixels out"
          % (k[0], k[1], k[2], k[3], k[4], k[5], k[7]))
        for tag, lst in (("34", a), ("66", b)):
            line = "    %s: " % tag
            for nm in lst:
                if len(line) + len(nm) + 2 > 78:
                    p(line.rstrip(", ") + ",")
                    line = "        "
                line += nm + ", "
            p(line.rstrip(", "))
    p()
    if MINE:
        p("The cleanest single pair is g_cal against bias_k5. Both are one ONNX")
        p("Conv, ic 16, oc 128, 80x80 input, k = 5, stride 2, compiled by the")
        p("same toolkit build, and the two .onnx sources are the same 205601")
        p("bytes -- they differ in the weight and bias VALUES and in nothing")
        p("else. Their first dispatches are 138 registers each and differ in")
        p("five of them:")
        a = read_ops(os.path.join(GEOM, "g_cal_rk3576.rknn"))[0]["regs"]
        b = read_ops(os.path.join(GEOM, "bias_k5_rk3576.rknn"))[0]["regs"]
        for kk in sorted(set(a) | set(b)):
            if a.get(kk) != b.get(kk):
                p("    t=%04x r=%04x   g_cal %08x   bias_k5 %08x"
                  % (kk[0], kk[1], a.get(kk, 0), b.get(kk, 0)))
        p()
        p("Three more same-geometry pairs, one from each of the other groups:")
        p()
        for x, y in (("g_k3s1", "pp_k3"), ("g_cal_k3", "bias_k3"),
                     ("p2_k5", "pp_p5")):
            ra = read_ops(os.path.join(GEOM, x + "_rk3576.rknn"))[0]["regs"]
            rb = read_ops(os.path.join(GEOM, y + "_rk3576.rknn"))[0]["regs"]
            d = [kk for kk in sorted(set(ra) | set(rb)) if ra.get(kk) != rb.get(kk)]
            p("  %s against %s: %d registers differ, %s"
              % (x, y, len(d), " ".join("%04x" % kk[1] for kk in d)))
        p()
        p("0x40ac, 0x40b0 and 0x40b4 are the output zero point, the requant")
        p("multiplier and the requant shift. Those differ between any two models")
        p("with different weights and say nothing here. What is left in every")
        p("pair is 0x4044, 0x4050 and 0x501c, the same three every time.")
    else:
        p("(the named same-geometry pairs of the corpus this was written against")
        p(" are not in this one, so that comparison is skipped here)")

    p()
    p()
    p("Does it follow the model series?")
    p("-" * 32)
    p()
    vers = Counter(f["ver"] for f in per_file.values())
    p("Not that either, and one form of the guess dies immediately: all %d" % len(files))
    p("files carry the same toolkit build string,")
    p()
    for v, n in vers.most_common():
        p("    %s   %d files" % (v, n))
    p()
    p("so it is not a toolkit version difference.")
    p()
    mixed = {n: f for n, f in per_file.items()
             if len(set(o["RESERVED_0"] for o in f["ops"]
                        if not o["undecoded"])) > 1}
    p("And a per-model constant cannot be right, because %d files hold more" % len(mixed)) if mixed else p("No file in this corpus holds more than one value inside a single compile.")
    if mixed: p("than one value inside a single compile:")
    p()
    def dump(n):
        f = per_file[n]
        p("  %s, %s, one %s in the source graph." %
          (n, f["shape"], f["onnxops"] or "?"))
        p("  Its dispatches, by byte offset in the file:")
        for o in f["ops"]:
            p("    0x%-6x 0x%08x  R_0 %-3d %s ic %-5s oc %-5s k %-2s "
              "%s px"
              % (o["off"], o["v"], o["RESERVED_0"],
                 "dw " if o["dw"] else "   ", o["ic"], o["oc"],
                 o["k"] if o["k"] else "?", o["pix"]))
    if MINE:
        dump("g_pw24")
        p()
        others = sorted(k for k in mixed if k != "g_pw24")
        p("  and the same shape in the other %d, all of them dwbig_*:" % len(others))
        dump(others[0])
        p("    ... and the same pattern in %s"
          % ", ".join(others[1:4] + ["..."]) if len(others) > 4
          else "    ... and the same in " + ", ".join(others[1:]))
        p()
        p("g_pw24 is the one that settles it. One Conv in the source graph, one")
        p("compile, one file, and its two REGULAR dispatches disagree: the 512 to")
        p("1024 pointwise reads 34 and the one-pixel 16 by 16 op the compiler")
        p("adds for itself reads 66. Whatever selects the value is decided per")
        p("dispatch, not per model and not per batch of models.")
    else:
        for n in sorted(mixed)[:2]:
            dump(n)
        if not mixed:
            p("  (no file in this corpus does)")

    # --------------------------------------------------------- what it does
    p()
    p()
    p("What it does follow")
    p("-" * 19)
    p()
    lock = Counter((o["RESERVED_0"], o["r4044"]) for o in reg)
    p("DPU 0x4044, exactly, on every regular dispatch in the corpus:")
    p()
    for (r0, v4), n in sorted(lock.items()):
        p("    RESERVED_0 %-3d  <->  0x4044 = 0x%08x    %d dispatches" % (r0, v4, n))
    p()
    r0_to, v4_to = defaultdict(set), defaultdict(set)
    for r0, v4 in lock:
        r0_to[r0].add(v4)
        v4_to[v4].add(r0)
    onetoone = bool(reg) and all(len(v) == 1 for v in r0_to.values()) \
               and all(len(v) == 1 for v in v4_to.values())
    if not reg:
        p("(no regular dispatches in this corpus, so nothing to pair)")
    elif onetoone:
        p("%d of %d, no exceptions. 0x2001/0x501c moves with them:" % (sum(lock.values()), len(reg)))
    else:
        p("NOT one-to-one in this corpus: a RESERVED_0 value meets more than one")
        p("0x4044 value or the other way round, %d regular dispatches. 0x501c:" % len(reg))
    p()
    s = defaultdict(Counter)
    for o in reg:
        s[o["RESERVED_0"]][o["r501c"]] += 1
    for r0 in sorted(s):
        p("    RESERVED_0 %-3d  ->  0x501c %s"
          % (r0, ", ".join("0x%x (%d)" % (k, v)
                           for k, v in sorted(s[r0].items()))))
    p()
    if MINE:
        p("Sweeping every register in the stream, 0x4044 is the ONLY one whose")
        p("value partitions the two RESERVED_0 groups one-to-one, and 0x501c the")
        p("only other whose value sets are disjoint between them. So the bit is")
        p("not a loose constant with no company: it is one third of a single")
        p("per-op decision in the bias-and-scale path of the output stage.")
        p()
    dws = Counter((o["RESERVED_0"], o["r4044"], o["r501c"]) for o in dwo)
    if dws:
        p("The depthwise dispatches read")
        for (r0, v4, v5), n in sorted(dws.items()):
            p("    RESERVED_0 %d, 0x4044 = 0x%x, 0x501c = 0x%x, %d dispatches"
              % (r0, v4, v5, n))
    else:
        p("(no depthwise dispatches in this corpus)")
    if MINE:
        p("38 is 34 with field bit 2 -- word bit 13 -- added, so on the word-bit")
        p("16-against-17 axis depthwise sits with the 34 group while its 0x4044")
        p("sits with the 66 group. The lockstep is a statement about the regular")
        p("datapath only, and I am not claiming more than that.")
        p()
        p("What selects the arm in the first place is still open. It is not the")
        p("shape, not the file, not the toolkit build, and not the day the file")
        p("was compiled: 2026-08-08 and 2026-08-09 each produced both arms.")

    # ------------------------------------------------------ for the driver
    p()
    p()
    p("What this means for the driver")
    p("-" * 30)
    p()
    p("rkt_regcmd.c emits, for a regular convolution,")
    p()
    p("    0x4044 = 0x00000001")
    p("    0x4050 = 0x80011011 or 0x80011111   (RESERVED_0 = 34)")
    p("    0x501c = 0x00000710")
    p()
    p("which is the 34 arm in all three registers at once. It is the arm the")
    p("vendor takes on %d of its %d regular dispatches, and the driver never"
      % (lock[(34, 1)], len(reg)))
    p("mixes an 0x4044 from one arm with a 0x4050 from the other. That is")
    p("the part I could not say in the earlier mail and can say now.")
    p()
    p("The RK3576 encoder has no depthwise path at all -- fill_regcmd_rk3576")
    p("covers a regular convolution and rkt_ml_operation_supported declines")
    p("depthwise before it is reached -- so the driver never emits the 38")
    p("word. That is an observation about the vendor's depthwise datapath")
    p("and about upstream's RK3588 encoder, not about this one.")
    p()
    p("The field is also not free: moved on its own to upstream's 0, on a 5x5")
    p("at 128 output channels and a pointwise at 88, the output stayed")
    p("identical to the baseline and the job timed out. So it is a completion")
    p("field, and 34 is the value that completes.")

    if MINE:
        # ----------------------------------------------------------- retraction
        p()
        p()
        p("What of the claim I sent")
        p("-" * 24)
        p()
        p("The counting half stands. 34 and 66 are the only two RESERVED_0 values")
        p("on regular convolutions, 34 is the common one, %d dispatches against" % c[34])
        p("%d, and %d files against %d by first dispatch." % (c[66], prim[34], prim[66]))
        p()
        p("Three things in it were wrong or too small, and I would rather correct")
        p("them here than let them stand.")
        p()
        p("1. \"It does not correlate with oc, ic, spatial size, kernel size or")
        p("   stride\" is true but it was the weak version of the test. The strong")
        p("   version is the one you asked for: hold the WHOLE geometry fixed.")
        p("   %d geometry classes then carry both values, %d of them once the CBUF"
          % (len(confl), ngroups))
        p("   windows of one model are folded together, and g_cal against bias_k5")
        p("   is two files identical in every register but five.")
        p()
        p("2. \"What it does track is which batch of models it came from, which")
        p("   makes a toolkit setting more likely\" does not survive as written.")
        p("   The batches are uniform, but the batch is not what decides:")
        p("   g_pw24 carries both values in one compile of one Conv, and the")
        p("   toolkit build string is identical across all %d files. Why a whole" % len(files))
        p("   batch lands on one arm I still cannot say.")
        p()
        p("3. It only ever mentioned two values. There is a third, 38, on all %d" % len(dwo))
        p("   depthwise dispatches. The RK3576 encoder declines depthwise so it")
        p("   never emits that word, but the corpus has it and the table should")
        p("   have said so.")
        p()
        p("The one thing the earlier mail did not have is the answer to your")
        p("question. It is not a toolkit setting sitting on its own; it is a bit")
        p("of a three-register per-op choice, and the driver takes one side of")
        p("that choice consistently.")
        p()

    out = "\n".join(W)
    over = [(i + 1, l) for i, l in enumerate(W) if len(l) > 78]
    sys.stdout.write(out + "\n")
    if over:
        sys.stderr.write("WARNING: %d lines over 78 columns\n" % len(over))
        for i, l in over[:10]:
            sys.stderr.write("  %d: %d cols: %s\n" % (i, len(l), l))
    return 0


if __name__ == "__main__":
    sys.exit(main())

^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2026-09-03 11:22 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-24 11:08 [PATCH v9 00/13] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
2026-08-24 11:08 ` [PATCH v9 01/13] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
2026-08-24 11:08   ` [PATCH v9 02/13] accel/rocket: wait for a running IRQ handler before resetting a core Jiaxing Hu
2026-08-25 12:31     ` Igor Paunovic
2026-08-25 12:45       ` Igor Paunovic
2026-08-24 11:08   ` [PATCH v9 03/13] accel/rocket: let the core suspend after a reset Jiaxing Hu
2026-08-25 12:32     ` Igor Paunovic
2026-08-25 12:45       ` Igor Paunovic
2026-08-24 11:08   ` [PATCH v9 04/13] accel/rocket: factor the completion tail out of the IRQ handler Jiaxing Hu
2026-08-24 11:08   ` [PATCH v9 05/13] dt-bindings: npu: rockchip: add rockchip, rk3576-rknn-core Jiaxing Hu
2026-08-25 12:32     ` Igor Paunovic
2026-08-27  1:49       ` Jiaxing Hu
2026-08-27 17:48         ` Igor Paunovic
2026-08-31  4:07           ` Jiaxing Hu
2026-09-02 10:01             ` Igor Paunovic
2026-09-03 11:22               ` Jiaxing Hu
2026-08-24 11:08   ` [PATCH v9 06/13] dt-bindings: power: rockchip: allow resets in a power domain node Jiaxing Hu
2026-08-24 16:24     ` Conor Dooley
2026-08-24 11:08   ` [PATCH v9 07/13] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU Jiaxing Hu
2026-08-24 16:25     ` Conor Dooley
2026-08-24 11:08   ` [PATCH v9 08/13] pmdomain/rockchip: add optional per-domain power-on settle delay Jiaxing Hu
2026-08-24 11:31     ` Abel Vesa
2026-08-24 11:08   ` [PATCH v9 09/13] pmdomain/rockchip: cycle optional power-domain resets on power-on Jiaxing Hu
2026-08-24 11:30     ` Abel Vesa
2026-08-24 11:08   ` [PATCH v9 10/13] accel/rocket: select the per-core clock and reset counts from match data Jiaxing Hu
2026-08-24 11:09   ` [PATCH v9 11/13] accel/rocket: add RK3576 NPU (RKNN) support Jiaxing Hu
2026-08-24 11:09   ` [PATCH v9 12/13] arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes Jiaxing Hu
2026-08-24 11:09   ` [PATCH v9 13/13] arm64: dts: rockchip: rk3576-rock-4d: enable NPU Jiaxing Hu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox