Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 5/8] KVM: arm64: Add host and hypervisor vCPU lookup primitives
From: Fuad Tabba @ 2026-06-19  7:05 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel,
	linux-kernel
  Cc: Catalin Marinas, Will Deacon, Joey Gouly, Steffen Eiden,
	Suzuki K Poulose, Zenghui Yu, Vincent Donnefort, Quentin Perret,
	Sebastian Ene, Hyunwoo Kim, Fuad Tabba
In-Reply-To: <20260619070508.802802-1-tabba@google.com>

From: Marc Zyngier <maz@kernel.org>

The nVHE hypervisor repeatedly resolves a host vCPU into the EL2
address space and validates that the loaded hyp vCPU matches it, with
that logic open-coded in each handler.

Add __get_host_hyp_vcpus() and the get_host_hyp_vcpus() macro, which
translate the host vCPU into the hypervisor's address space and, when
pKVM is enabled, also return the loaded hyp vCPU if it matches. If pKVM
is enabled but the loaded hyp vCPU does not correspond to the requested
host vCPU, both the host and hyp vCPU are returned as NULL. Convert
handle___kvm_vcpu_run() to use it.

No functional change intended.

Signed-off-by: Marc Zyngier <maz@kernel.org>
Co-developed-by: Fuad Tabba <tabba@google.com>
Signed-off-by: Fuad Tabba <tabba@google.com>
---
 arch/arm64/kvm/hyp/nvhe/hyp-main.c | 52 ++++++++++++++++++++++--------
 1 file changed, 38 insertions(+), 14 deletions(-)

diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 1d01c6e547f5..8923f594c264 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -212,14 +212,45 @@ static void handle___pkvm_vcpu_put(struct kvm_cpu_context *host_ctxt)
 		pkvm_put_hyp_vcpu(hyp_vcpu);
 }
 
-static void handle___kvm_vcpu_run(struct kvm_cpu_context *host_ctxt)
+static struct kvm_vcpu *__get_host_hyp_vcpus(struct kvm_vcpu *arg,
+					     struct pkvm_hyp_vcpu **hyp_vcpup)
 {
-	DECLARE_REG(struct kvm_vcpu *, host_vcpu, host_ctxt, 1);
-	int ret;
+	struct kvm_vcpu *host_vcpu = kern_hyp_va(arg);
+	struct pkvm_hyp_vcpu *hyp_vcpu = NULL;
 
 	if (unlikely(is_protected_kvm_enabled())) {
-		struct pkvm_hyp_vcpu *hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
+		hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
 
+		if (!hyp_vcpu || hyp_vcpu->host_vcpu != host_vcpu) {
+			hyp_vcpu = NULL;
+			host_vcpu = NULL;
+		}
+	}
+
+	*hyp_vcpup = hyp_vcpu;
+	return host_vcpu;
+}
+
+#define get_host_hyp_vcpus(ctxt, regnr, hyp_vcpup)			\
+	({								\
+		DECLARE_REG(struct kvm_vcpu *, __vcpu, ctxt, regnr);	\
+		__get_host_hyp_vcpus(__vcpu, hyp_vcpup);		\
+	})
+
+static void handle___kvm_vcpu_run(struct kvm_cpu_context *host_ctxt)
+{
+	struct pkvm_hyp_vcpu *hyp_vcpu;
+	struct kvm_vcpu *host_vcpu;
+	int ret;
+
+	host_vcpu = get_host_hyp_vcpus(host_ctxt, 1, &hyp_vcpu);
+
+	if (!host_vcpu) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (unlikely(hyp_vcpu)) {
 		/*
 		 * KVM (and pKVM) doesn't support SME guests for now, and
 		 * ensures that SME features aren't enabled in pstate when
@@ -231,23 +262,16 @@ static void handle___kvm_vcpu_run(struct kvm_cpu_context *host_ctxt)
 			goto out;
 		}
 
-		if (!hyp_vcpu) {
-			ret = -EINVAL;
-			goto out;
-		}
-
 		flush_hyp_vcpu(hyp_vcpu);
 
 		ret = __kvm_vcpu_run(&hyp_vcpu->vcpu);
 
 		sync_hyp_vcpu(hyp_vcpu);
 	} else {
-		struct kvm_vcpu *vcpu = kern_hyp_va(host_vcpu);
-
 		/* The host is fully trusted, run its vCPU directly. */
-		fpsimd_lazy_switch_to_guest(vcpu);
-		ret = __kvm_vcpu_run(vcpu);
-		fpsimd_lazy_switch_to_host(vcpu);
+		fpsimd_lazy_switch_to_guest(host_vcpu);
+		ret = __kvm_vcpu_run(host_vcpu);
+		fpsimd_lazy_switch_to_host(host_vcpu);
 	}
 out:
 	cpu_reg(host_ctxt, 1) =  ret;
-- 
2.55.0.rc0.738.g0c8ab3ebcc-goog



^ permalink raw reply related

* [PATCH 1/8] KVM: arm64: Extract MPIDR computation into a shared header
From: Fuad Tabba @ 2026-06-19  7:05 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel,
	linux-kernel
  Cc: Catalin Marinas, Will Deacon, Joey Gouly, Steffen Eiden,
	Suzuki K Poulose, Zenghui Yu, Vincent Donnefort, Quentin Perret,
	Sebastian Ene, Hyunwoo Kim, Fuad Tabba
In-Reply-To: <20260619070508.802802-1-tabba@google.com>

Extract the vCPU MPIDR computation embedded in reset_mpidr() into a
kvm_calculate_mpidr() inline in sys_regs.h, so it can be computed
without duplicating the logic. A follow-up series reuses it to reset
protected vCPUs at EL2.

No functional change intended.

Signed-off-by: Fuad Tabba <tabba@google.com>
---
 arch/arm64/kvm/sys_regs.c | 14 +-------------
 arch/arm64/kvm/sys_regs.h | 19 +++++++++++++++++++
 2 files changed, 20 insertions(+), 13 deletions(-)

diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 33c921df19b5..674fabe1d40d 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -976,21 +976,9 @@ static u64 reset_actlr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
 
 static u64 reset_mpidr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
 {
-	u64 mpidr;
+	u64 mpidr = kvm_calculate_mpidr(vcpu);
 
-	/*
-	 * Map the vcpu_id into the first three affinity level fields of
-	 * the MPIDR. We limit the number of VCPUs in level 0 due to a
-	 * limitation to 16 CPUs in that level in the ICC_SGIxR registers
-	 * of the GICv3 to be able to address each CPU directly when
-	 * sending IPIs.
-	 */
-	mpidr = (vcpu->vcpu_id & 0x0f) << MPIDR_LEVEL_SHIFT(0);
-	mpidr |= ((vcpu->vcpu_id >> 4) & 0xff) << MPIDR_LEVEL_SHIFT(1);
-	mpidr |= ((vcpu->vcpu_id >> 12) & 0xff) << MPIDR_LEVEL_SHIFT(2);
-	mpidr |= (1ULL << 31);
 	vcpu_write_sys_reg(vcpu, mpidr, MPIDR_EL1);
-
 	return mpidr;
 }
 
diff --git a/arch/arm64/kvm/sys_regs.h b/arch/arm64/kvm/sys_regs.h
index 2a983664220c..bd56a45abbf9 100644
--- a/arch/arm64/kvm/sys_regs.h
+++ b/arch/arm64/kvm/sys_regs.h
@@ -222,6 +222,25 @@ find_reg(const struct sys_reg_params *params, const struct sys_reg_desc table[],
 	return __inline_bsearch((void *)pval, table, num, sizeof(table[0]), match_sys_reg);
 }
 
+static inline u64 kvm_calculate_mpidr(const struct kvm_vcpu *vcpu)
+{
+	u64 mpidr;
+
+	/*
+	 * Map the vcpu_id into the first three affinity level fields of
+	 * the MPIDR. We limit the number of VCPUs in level 0 due to a
+	 * limitation to 16 CPUs in that level in the ICC_SGIxR registers
+	 * of the GICv3 to be able to address each CPU directly when
+	 * sending IPIs.
+	 */
+	mpidr = (vcpu->vcpu_id & 0x0f) << MPIDR_LEVEL_SHIFT(0);
+	mpidr |= ((vcpu->vcpu_id >> 4) & 0xff) << MPIDR_LEVEL_SHIFT(1);
+	mpidr |= ((vcpu->vcpu_id >> 12) & 0xff) << MPIDR_LEVEL_SHIFT(2);
+	mpidr |= (1ULL << 31);
+
+	return mpidr;
+}
+
 const struct sys_reg_desc *get_reg_by_id(u64 id,
 					 const struct sys_reg_desc table[],
 					 unsigned int num);
-- 
2.55.0.rc0.738.g0c8ab3ebcc-goog



^ permalink raw reply related

* [PATCH 3/8] KVM: arm64: Factor out reusable vCPU reset helpers
From: Fuad Tabba @ 2026-06-19  7:05 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel,
	linux-kernel
  Cc: Catalin Marinas, Will Deacon, Joey Gouly, Steffen Eiden,
	Suzuki K Poulose, Zenghui Yu, Vincent Donnefort, Quentin Perret,
	Sebastian Ene, Hyunwoo Kim, Fuad Tabba
In-Reply-To: <20260619070508.802802-1-tabba@google.com>

Pull the reusable pieces out of kvm_reset_vcpu(): expose the reset
PSTATE values in kvm_arm.h, and split the core register reset and the
PSCI-driven reset into kvm_reset_vcpu_core() and kvm_reset_vcpu_psci().
A follow-up series reuses these to reset protected vCPUs at EL2.

No functional change intended.

Signed-off-by: Fuad Tabba <tabba@google.com>
---
 arch/arm64/include/asm/kvm_arm.h     | 12 ++++++
 arch/arm64/include/asm/kvm_emulate.h | 57 ++++++++++++++++++++++++++
 arch/arm64/kvm/reset.c               | 60 ++--------------------------
 3 files changed, 72 insertions(+), 57 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_arm.h b/arch/arm64/include/asm/kvm_arm.h
index 3f9233b5a130..aba4ec09acd2 100644
--- a/arch/arm64/include/asm/kvm_arm.h
+++ b/arch/arm64/include/asm/kvm_arm.h
@@ -348,4 +348,16 @@
 	{ PSR_AA32_MODE_UND,	"32-bit UND" },	\
 	{ PSR_AA32_MODE_SYS,	"32-bit SYS" }
 
+/*
+ * ARMv8 Reset Values
+ */
+#define VCPU_RESET_PSTATE_EL1	(PSR_MODE_EL1h | PSR_A_BIT | PSR_I_BIT | \
+				 PSR_F_BIT | PSR_D_BIT)
+
+#define VCPU_RESET_PSTATE_EL2	(PSR_MODE_EL2h | PSR_A_BIT | PSR_I_BIT | \
+				 PSR_F_BIT | PSR_D_BIT)
+
+#define VCPU_RESET_PSTATE_SVC	(PSR_AA32_MODE_SVC | PSR_AA32_A_BIT | \
+				 PSR_AA32_I_BIT | PSR_AA32_F_BIT)
+
 #endif /* __ARM64_KVM_ARM_H__ */
diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index 80b30fead3d1..2385d8855fcf 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -704,4 +704,61 @@ static inline void vcpu_set_hcrx(struct kvm_vcpu *vcpu)
 			vcpu->arch.hcrx_el2 |= HCRX_EL2_EnASR;
 	}
 }
+
+/* Reset a vcpu's core registers. */
+static inline void kvm_reset_vcpu_core(struct kvm_vcpu *vcpu)
+{
+	u32 pstate;
+
+	if (vcpu_el1_is_32bit(vcpu))
+		pstate = VCPU_RESET_PSTATE_SVC;
+	else if (vcpu_has_nv(vcpu))
+		pstate = VCPU_RESET_PSTATE_EL2;
+	else
+		pstate = VCPU_RESET_PSTATE_EL1;
+
+	/* Reset core registers */
+	memset(vcpu_gp_regs(vcpu), 0, sizeof(*vcpu_gp_regs(vcpu)));
+	memset(&vcpu->arch.ctxt.fp_regs, 0, sizeof(vcpu->arch.ctxt.fp_regs));
+	vcpu->arch.ctxt.spsr_abt = 0;
+	vcpu->arch.ctxt.spsr_und = 0;
+	vcpu->arch.ctxt.spsr_irq = 0;
+	vcpu->arch.ctxt.spsr_fiq = 0;
+	vcpu_gp_regs(vcpu)->pstate = pstate;
+}
+
+/* PSCI reset handling for a vcpu. */
+static inline void kvm_reset_vcpu_psci(struct kvm_vcpu *vcpu,
+				       struct vcpu_reset_state *reset_state)
+{
+	unsigned long target_pc = reset_state->pc;
+
+	/* Gracefully handle Thumb2 entry point */
+	if (vcpu_mode_is_32bit(vcpu) && (target_pc & 1)) {
+		target_pc &= ~1UL;
+		vcpu_set_thumb(vcpu);
+	}
+
+	/* Propagate caller endianness */
+	if (reset_state->be)
+		kvm_vcpu_set_be(vcpu);
+
+	*vcpu_pc(vcpu) = target_pc;
+
+	/*
+	 * We may come from a state where either a PC update was
+	 * pending (SMC call resulting in PC being increpented to
+	 * skip the SMC) or a pending exception. Make sure we get
+	 * rid of all that, as this cannot be valid out of reset.
+	 *
+	 * Note that clearing the exception mask also clears PC
+	 * updates, but that's an implementation detail, and we
+	 * really want to make it explicit.
+	 */
+	vcpu_clear_flag(vcpu, PENDING_EXCEPTION);
+	vcpu_clear_flag(vcpu, EXCEPT_MASK);
+	vcpu_clear_flag(vcpu, INCREMENT_PC);
+	vcpu_set_reg(vcpu, 0, reset_state->r0);
+}
+
 #endif /* __ARM64_KVM_EMULATE_H__ */
diff --git a/arch/arm64/kvm/reset.c b/arch/arm64/kvm/reset.c
index b963fd975aac..10eb7249aa9e 100644
--- a/arch/arm64/kvm/reset.c
+++ b/arch/arm64/kvm/reset.c
@@ -34,18 +34,6 @@
 static u32 __ro_after_init kvm_ipa_limit;
 unsigned int __ro_after_init kvm_host_sve_max_vl;
 
-/*
- * ARMv8 Reset Values
- */
-#define VCPU_RESET_PSTATE_EL1	(PSR_MODE_EL1h | PSR_A_BIT | PSR_I_BIT | \
-				 PSR_F_BIT | PSR_D_BIT)
-
-#define VCPU_RESET_PSTATE_EL2	(PSR_MODE_EL2h | PSR_A_BIT | PSR_I_BIT | \
-				 PSR_F_BIT | PSR_D_BIT)
-
-#define VCPU_RESET_PSTATE_SVC	(PSR_AA32_MODE_SVC | PSR_AA32_A_BIT | \
-				 PSR_AA32_I_BIT | PSR_AA32_F_BIT)
-
 unsigned int __ro_after_init kvm_sve_max_vl;
 
 int __init kvm_arm_init_sve(void)
@@ -191,7 +179,6 @@ void kvm_reset_vcpu(struct kvm_vcpu *vcpu)
 {
 	struct vcpu_reset_state reset_state;
 	bool loaded;
-	u32 pstate;
 
 	spin_lock(&vcpu->arch.mp_state_lock);
 	reset_state = vcpu->arch.reset_state;
@@ -210,21 +197,8 @@ void kvm_reset_vcpu(struct kvm_vcpu *vcpu)
 		kvm_vcpu_reset_sve(vcpu);
 	}
 
-	if (vcpu_el1_is_32bit(vcpu))
-		pstate = VCPU_RESET_PSTATE_SVC;
-	else if (vcpu_has_nv(vcpu))
-		pstate = VCPU_RESET_PSTATE_EL2;
-	else
-		pstate = VCPU_RESET_PSTATE_EL1;
-
 	/* Reset core registers */
-	memset(vcpu_gp_regs(vcpu), 0, sizeof(*vcpu_gp_regs(vcpu)));
-	memset(&vcpu->arch.ctxt.fp_regs, 0, sizeof(vcpu->arch.ctxt.fp_regs));
-	vcpu->arch.ctxt.spsr_abt = 0;
-	vcpu->arch.ctxt.spsr_und = 0;
-	vcpu->arch.ctxt.spsr_irq = 0;
-	vcpu->arch.ctxt.spsr_fiq = 0;
-	vcpu_gp_regs(vcpu)->pstate = pstate;
+	kvm_reset_vcpu_core(vcpu);
 
 	/* Reset system registers */
 	kvm_reset_sys_regs(vcpu);
@@ -233,36 +207,8 @@ void kvm_reset_vcpu(struct kvm_vcpu *vcpu)
 	 * Additional reset state handling that PSCI may have imposed on us.
 	 * Must be done after all the sys_reg reset.
 	 */
-	if (reset_state.reset) {
-		unsigned long target_pc = reset_state.pc;
-
-		/* Gracefully handle Thumb2 entry point */
-		if (vcpu_mode_is_32bit(vcpu) && (target_pc & 1)) {
-			target_pc &= ~1UL;
-			vcpu_set_thumb(vcpu);
-		}
-
-		/* Propagate caller endianness */
-		if (reset_state.be)
-			kvm_vcpu_set_be(vcpu);
-
-		*vcpu_pc(vcpu) = target_pc;
-
-		/*
-		 * We may come from a state where either a PC update was
-		 * pending (SMC call resulting in PC being increpented to
-		 * skip the SMC) or a pending exception. Make sure we get
-		 * rid of all that, as this cannot be valid out of reset.
-		 *
-		 * Note that clearing the exception mask also clears PC
-		 * updates, but that's an implementation detail, and we
-		 * really want to make it explicit.
-		 */
-		vcpu_clear_flag(vcpu, PENDING_EXCEPTION);
-		vcpu_clear_flag(vcpu, EXCEPT_MASK);
-		vcpu_clear_flag(vcpu, INCREMENT_PC);
-		vcpu_set_reg(vcpu, 0, reset_state.r0);
-	}
+	if (reset_state.reset)
+		kvm_reset_vcpu_psci(vcpu, &reset_state);
 
 	/* Reset timer */
 	kvm_timer_vcpu_reset(vcpu);
-- 
2.55.0.rc0.738.g0c8ab3ebcc-goog



^ permalink raw reply related

* [PATCH 2/8] KVM: arm64: Make vcpu_{read,write}_sys_reg available to HYP code
From: Fuad Tabba @ 2026-06-19  7:05 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel,
	linux-kernel
  Cc: Catalin Marinas, Will Deacon, Joey Gouly, Steffen Eiden,
	Suzuki K Poulose, Zenghui Yu, Vincent Donnefort, Quentin Perret,
	Sebastian Ene, Hyunwoo Kim, Fuad Tabba
In-Reply-To: <20260619070508.802802-1-tabba@google.com>

The vcpu_{read,write}_sys_reg() accessors are host-only, so helpers
built on them such as kvm_vcpu_set_be()/kvm_vcpu_is_be() cannot be
shared with hyp code. exception.c already wraps them in
__vcpu_{read,write}_sys_reg(), which pick the host- or hyp-side accessor
via has_vhe() and so are valid in any context.

Move those wrappers to kvm_emulate.h as kvm_vcpu_{read,write}_sys_reg()
and switch the callers over, so a follow-up series can share that
emulation code at EL2.

No functional change intended.

Signed-off-by: Fuad Tabba <tabba@google.com>
---
 arch/arm64/include/asm/kvm_emulate.h | 22 +++++++++++++++---
 arch/arm64/kvm/hyp/exception.c       | 34 ++++++++--------------------
 2 files changed, 28 insertions(+), 28 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index 5bf3d7e1d92c..80b30fead3d1 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -506,6 +506,22 @@ static inline unsigned long kvm_vcpu_get_mpidr_aff(struct kvm_vcpu *vcpu)
 	return __vcpu_sys_reg(vcpu, MPIDR_EL1) & MPIDR_HWID_BITMASK;
 }
 
+static inline u64 kvm_vcpu_read_sys_reg(const struct kvm_vcpu *vcpu, int reg)
+{
+	if (has_vhe())
+		return vcpu_read_sys_reg(vcpu, reg);
+
+	return __vcpu_sys_reg(vcpu, reg);
+}
+
+static inline void kvm_vcpu_write_sys_reg(struct kvm_vcpu *vcpu, u64 val, int reg)
+{
+	if (has_vhe())
+		vcpu_write_sys_reg(vcpu, val, reg);
+	else
+		__vcpu_assign_sys_reg(vcpu, reg, val);
+}
+
 static inline void kvm_vcpu_set_be(struct kvm_vcpu *vcpu)
 {
 	if (vcpu_mode_is_32bit(vcpu)) {
@@ -516,9 +532,9 @@ static inline void kvm_vcpu_set_be(struct kvm_vcpu *vcpu)
 
 		r = vcpu_has_nv(vcpu) ? SCTLR_EL2 : SCTLR_EL1;
 
-		sctlr = vcpu_read_sys_reg(vcpu, r);
+		sctlr = kvm_vcpu_read_sys_reg(vcpu, r);
 		sctlr |= SCTLR_ELx_EE;
-		vcpu_write_sys_reg(vcpu, sctlr, r);
+		kvm_vcpu_write_sys_reg(vcpu, sctlr, r);
 	}
 }
 
@@ -533,7 +549,7 @@ static inline bool kvm_vcpu_is_be(struct kvm_vcpu *vcpu)
 	r = is_hyp_ctxt(vcpu) ? SCTLR_EL2 : SCTLR_EL1;
 	bit = vcpu_mode_priv(vcpu) ? SCTLR_ELx_EE : SCTLR_EL1_E0E;
 
-	return vcpu_read_sys_reg(vcpu, r) & bit;
+	return kvm_vcpu_read_sys_reg(vcpu, r) & bit;
 }
 
 static inline unsigned long vcpu_data_guest_to_host(struct kvm_vcpu *vcpu,
diff --git a/arch/arm64/kvm/hyp/exception.c b/arch/arm64/kvm/hyp/exception.c
index bef40ddb16db..2cb68dc7d441 100644
--- a/arch/arm64/kvm/hyp/exception.c
+++ b/arch/arm64/kvm/hyp/exception.c
@@ -20,22 +20,6 @@
 #error Hypervisor code only!
 #endif
 
-static inline u64 __vcpu_read_sys_reg(const struct kvm_vcpu *vcpu, int reg)
-{
-	if (has_vhe())
-		return vcpu_read_sys_reg(vcpu, reg);
-
-	return __vcpu_sys_reg(vcpu, reg);
-}
-
-static inline void __vcpu_write_sys_reg(struct kvm_vcpu *vcpu, u64 val, int reg)
-{
-	if (has_vhe())
-		vcpu_write_sys_reg(vcpu, val, reg);
-	else
-		__vcpu_assign_sys_reg(vcpu, reg, val);
-}
-
 static void __vcpu_write_spsr(struct kvm_vcpu *vcpu, unsigned long target_mode,
 			      u64 val)
 {
@@ -101,14 +85,14 @@ static void enter_exception64(struct kvm_vcpu *vcpu, unsigned long target_mode,
 
 	switch (target_mode) {
 	case PSR_MODE_EL1h:
-		vbar = __vcpu_read_sys_reg(vcpu, VBAR_EL1);
-		sctlr = __vcpu_read_sys_reg(vcpu, SCTLR_EL1);
-		__vcpu_write_sys_reg(vcpu, *vcpu_pc(vcpu), ELR_EL1);
+		vbar = kvm_vcpu_read_sys_reg(vcpu, VBAR_EL1);
+		sctlr = kvm_vcpu_read_sys_reg(vcpu, SCTLR_EL1);
+		kvm_vcpu_write_sys_reg(vcpu, *vcpu_pc(vcpu), ELR_EL1);
 		break;
 	case PSR_MODE_EL2h:
-		vbar = __vcpu_read_sys_reg(vcpu, VBAR_EL2);
-		sctlr = __vcpu_read_sys_reg(vcpu, SCTLR_EL2);
-		__vcpu_write_sys_reg(vcpu, *vcpu_pc(vcpu), ELR_EL2);
+		vbar = kvm_vcpu_read_sys_reg(vcpu, VBAR_EL2);
+		sctlr = kvm_vcpu_read_sys_reg(vcpu, SCTLR_EL2);
+		kvm_vcpu_write_sys_reg(vcpu, *vcpu_pc(vcpu), ELR_EL2);
 		break;
 	default:
 		/* Don't do that */
@@ -185,7 +169,7 @@ static void enter_exception64(struct kvm_vcpu *vcpu, unsigned long target_mode,
  */
 static unsigned long get_except32_cpsr(struct kvm_vcpu *vcpu, u32 mode)
 {
-	u32 sctlr = __vcpu_read_sys_reg(vcpu, SCTLR_EL1);
+	u32 sctlr = kvm_vcpu_read_sys_reg(vcpu, SCTLR_EL1);
 	unsigned long old, new;
 
 	old = *vcpu_cpsr(vcpu);
@@ -281,7 +265,7 @@ static void enter_exception32(struct kvm_vcpu *vcpu, u32 mode, u32 vect_offset)
 {
 	unsigned long spsr = *vcpu_cpsr(vcpu);
 	bool is_thumb = (spsr & PSR_AA32_T_BIT);
-	u32 sctlr = __vcpu_read_sys_reg(vcpu, SCTLR_EL1);
+	u32 sctlr = kvm_vcpu_read_sys_reg(vcpu, SCTLR_EL1);
 	u32 return_address;
 
 	*vcpu_cpsr(vcpu) = get_except32_cpsr(vcpu, mode);
@@ -305,7 +289,7 @@ static void enter_exception32(struct kvm_vcpu *vcpu, u32 mode, u32 vect_offset)
 	if (sctlr & (1 << 13))
 		vect_offset += 0xffff0000;
 	else /* always have security exceptions */
-		vect_offset += __vcpu_read_sys_reg(vcpu, VBAR_EL1);
+		vect_offset += kvm_vcpu_read_sys_reg(vcpu, VBAR_EL1);
 
 	*vcpu_pc(vcpu) = vect_offset;
 }
-- 
2.55.0.rc0.738.g0c8ab3ebcc-goog



^ permalink raw reply related

* [PATCH 0/8] KVM: arm64: Rework pKVM vCPU state synchronisation
From: Fuad Tabba @ 2026-06-19  7:05 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel,
	linux-kernel
  Cc: Catalin Marinas, Will Deacon, Joey Gouly, Steffen Eiden,
	Suzuki K Poulose, Zenghui Yu, Vincent Donnefort, Quentin Perret,
	Sebastian Ene, Hyunwoo Kim, Fuad Tabba

Hi folks,

Changes since v1 [2]:
  - Dropped the guard()/scoped_guard() conversion patches: standalone churn
    on code this series does not otherwise rework. (Marc)
  - Rebased onto kvmarm/next. The VGIC flush primitive now bounds used_lrs
    using the cached hyp_gicv3_nr_lr instead of reading ICH_VTR_EL2 on every
    entry. (Marc)
  - Grouped the PKVM_HOST_STATE_DIRTY flag with the other iflags and
    clarified its comment. (Marc)
  - Sync PSTATE alongside PC on every non-protected exit, and sync+dirty
    before host-side SError injection so the syndrome is not dropped. (sashiko)
  - Various cleanups and tidying up. (Vincent)

Building on Will's pKVM infrastructure series [1], this series reworks
how pKVM moves vCPU state between the host and EL2, and stops copying a
non-protected guest's state on every world switch.

EL2 gains proper primitives for the state it transfers: vCPU lookup
helpers, and VGIC flush/sync that reduces how much host state EL2
dereferences. The series also moves some preparatory code (such as sys
reg access and PSCI helpers) to shared headers and HYP, and implements
lazy copying of a non-protected guest's register state back to the host
until the host actually needs it, instead of on every exit.

This is the first of two series moving pKVM vCPU state management to
EL2. The follow-up completes the job for protected VMs: state
isolation, PSCI handling at EL2, and the resulting API behaviour.

The series is structured as follows:

  01-04:  Preparatory refactoring (MPIDR, sys reg access, vCPU reset, PSCI
          helpers) to shared headers and HYP.
  05:     Host and hypervisor vCPU lookup primitives.
  06-07:  VGIC: reduce EL2's exposure to host state, add flush/sync primitives.
  08:     Lazy state sync for non-protected guests.

Based on kvmarm/next.

[1] https://lore.kernel.org/all/20260105154939.11041-1-will@kernel.org/
[2] https://lore.kernel.org/all/20260612065925.755562-1-tabba@google.com/

Cheers,
/fuad

Fuad Tabba (5):
  KVM: arm64: Extract MPIDR computation into a shared header
  KVM: arm64: Make vcpu_{read,write}_sys_reg available to HYP code
  KVM: arm64: Factor out reusable vCPU reset helpers
  KVM: arm64: Move PSCI helper functions to a shared header
  KVM: arm64: Implement lazy vCPU state sync for non-protected guests

Marc Zyngier (3):
  KVM: arm64: Add host and hypervisor vCPU lookup primitives
  KVM: arm64: Minimise EL2's exposure of host VGIC state during world
    switch
  KVM: arm64: Add primitives to flush/sync the VGIC state at EL2

 arch/arm64/include/asm/kvm_arm.h     |  12 ++
 arch/arm64/include/asm/kvm_asm.h     |   1 +
 arch/arm64/include/asm/kvm_emulate.h |  79 +++++++-
 arch/arm64/include/asm/kvm_host.h    |   2 +
 arch/arm64/kvm/arm.c                 |   7 +
 arch/arm64/kvm/handle_exit.c         |  30 ++++
 arch/arm64/kvm/hyp/exception.c       |  34 +---
 arch/arm64/kvm/hyp/nvhe/hyp-main.c   | 258 +++++++++++++++++++++++----
 arch/arm64/kvm/psci.c                |  30 +---
 arch/arm64/kvm/reset.c               |  60 +------
 arch/arm64/kvm/sys_regs.c            |  14 +-
 arch/arm64/kvm/sys_regs.h            |  19 ++
 include/kvm/arm_psci.h               |  27 +++
 13 files changed, 410 insertions(+), 163 deletions(-)

-- 
2.55.0.rc0.738.g0c8ab3ebcc-goog



^ permalink raw reply

* [PATCH] drm/mxsfb/lcdif: don't hide lcdif_attach_bridge() deferral messages
From: Luca Ceresoli @ 2026-06-19  7:02 UTC (permalink / raw)
  To: Marek Vasut, Stefan Agner, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: Hui Pu, Ian Ray, Thomas Petazzoni, dri-devel, imx,
	linux-arm-kernel, linux-kernel, Luca Ceresoli

lcdif_attach_bridge() uses dev_err_probe() on all its error returns to
store a specific deferral message.

However its caller lcdif_load() calls dev_err_probe() again on error,
overwriting the specific deferral messages with a unique, unavoidably
generic, message.

Make the specific deferral message visible by using a plain 'return ret' on
the caller.

Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
 drivers/gpu/drm/mxsfb/lcdif_drv.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/mxsfb/lcdif_drv.c b/drivers/gpu/drm/mxsfb/lcdif_drv.c
index f5bb59cd5028..e2173c4d6fc2 100644
--- a/drivers/gpu/drm/mxsfb/lcdif_drv.c
+++ b/drivers/gpu/drm/mxsfb/lcdif_drv.c
@@ -186,7 +186,7 @@ static int lcdif_load(struct drm_device *drm)
 
 	ret = lcdif_attach_bridge(lcdif);
 	if (ret)
-		return dev_err_probe(drm->dev, ret, "Cannot connect bridge\n");
+		return ret;
 
 	drm->mode_config.min_width	= LCDIF_MIN_XRES;
 	drm->mode_config.min_height	= LCDIF_MIN_YRES;

---
base-commit: 98b46e693b912eef0e6d497327489113845cbd15
change-id: 20260619-drm-lcdif-deferral-msg-e736d5fb301b

Best regards,
--  
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com



^ permalink raw reply related

* Re: [PATCH v2 0/3] ASoC: rockchip: Use guard() for spin locks
From: Bui Duc Phuc @ 2026-06-19  6:58 UTC (permalink / raw)
  To: Nicolas Frattaroli
  Cc: Heiko Stuebner, Liam Girdwood, Mark Brown, Nicolas Frattaroli,
	Jaroslav Kysela, Takashi Iwai, linux-sound, linux-rockchip,
	linux-arm-kernel, linux-kernel
In-Reply-To: <D9b19fqJSQK5FO2BflNEKw@collabora.com>

Hi Nicolas,

Thanks for the clarification.

> If you want to try again, do so in a way where you don't have to indent
> large parts of a function another level, it's possible and the diff
> will be something I can actually read. Remember that you can place a
> guard for the spinlock in the middle of a function, and it'll only be
> picked up then, which should reduce the need for the scoped_guards in
> this. To avoid having to manually do pm unwind (and therefore require
> that the spinlock is dropped somehow before function exit), use the
> runtime pm guard macros as well. That should simplify this immensely.
>
> Last but not least, please run things through checkpatch. Even at the
> default options, it'll warn about the indentation level. With --strict,
> it'll also tell you about the braces. Personally I just use whatever
> preset `b4 prep --check` uses, which usually is quite reasonable.
>

I do run checkpatch before submitting patches, but I wasn't aware of the
`--strict` option. I also didn't know that runtime PM could be handled
with guard helpers.

I agree that using runtime PM guards would make the code much cleaner
and avoid much of the extra indentation.

These are useful suggestions, and I'll keep them in mind for future
submissions.

Best regards,
Phuc


^ permalink raw reply

* Re: [PATCH v2 1/1] reset: imx7: Correct polarity of MIPI CSI resets on i.MX8MQ
From: Robby Cai @ 2026-06-19  6:39 UTC (permalink / raw)
  To: Philipp Zabel
  Cc: Robby Cai, Frank.Li, s.hauer, festevam, krzk+dt, andrew.smirnov,
	kernel, imx, linux-arm-kernel, linux-kernel, aisheng.dong,
	guoniu.zhou
In-Reply-To: <922d581718c90c9ca603a35ece7c44da7c425e0a.camel@pengutronix.de>

On Thu, Jun 18, 2026 at 12:24:57PM +0200, Philipp Zabel wrote:
> On Fr, 2026-04-17 at 17:28 +0800, Robby Cai wrote:
> > On Fri, Apr 17, 2026 at 10:47:48AM +0200, Philipp Zabel wrote:
> > > On Fr, 2026-04-17 at 16:08 +0800, Robby Cai wrote:
> > > > On i.MX8MQ, the MIPI CSI reset lines are active-low and not self-clearing.
> > > > Writing '0' asserts reset and it remains asserted until explicitly
> > > > deasserted by software.
> > > > 
> > > > This driver previously treated the MIPI CSI reset signals as active-high,
> > > > which led to incorrect reset assert/deassert sequencing. This issue was
> > > > exposed by commit 6d79bb8fd2aa ("media: imx8mq-mipi-csi2: Explicitly
> > > > release reset").
> > > 
> > > If this patch is backported without 6d79bb8fd2aa, or the other way
> > > around, will that break MIPI CSI-2 on older kernels? That would warrant
> > > a Cc: stable tag.
> > > 
> > 
> > Yes, will break.
> > These two patches should be backported as a pair to ensure correct behavior.
> 
> Reviewed-by: Philipp Zabel <p.zabel@pengutronix.de>
> 
> Can you please resend with
> 
> Cc: <stable@vger.kernel.org> # 6d79bb8fd2aa: media: imx8mq-mipi-csi2: Explicitly release reset

Sure, will do. Thanks.

Regards,
Robby
> 
> regards
> Philipp


^ permalink raw reply

* [PATCH v6 2/2] arm64: dts: ti: Add audio overlay for k3-j721s2-evm
From: Moteen Shah @ 2026-06-19  6:27 UTC (permalink / raw)
  To: krzk+dt, robh, conor+dt, nm, vigneshr, kristo
  Cc: devicetree, linux-arm-kernel, linux-kernel, u-kumar1,
	gehariprasath, y-abhilashchandra, m-shah, sen
In-Reply-To: <20260619062749.1575066-1-m-shah@ti.com>

From: Jayesh Choudhary <j-choudhary@ti.com>

Add device tree overlay to enable analog audio support on J721S2-EVM
using PCM3168A codec connected to McASP4 serializers.

- Add audio_refclk1 clock node to k3-j721s2-main.dtsi
- Add nodes for sound-card, audio codec, I2C3 and McASP4
- Add pinmux for I2C3, McASP4, AUDIO_EXT_REFCLK1 and WKUP_GPIO_0
- Add GPIO expander (TCA6408) for codec control
- Add GPIO hogs to route I2C3 lines and McASP serializers
- Set idle-state to 0 in mux0 and mux1 for McASP signal routing

Reviewed-by: Hari Prasath Gujulan Elango <gehariprasath@ti.com>
Signed-off-by: Jayesh Choudhary <j-choudhary@ti.com>
Co-developed-by: Moteen Shah <m-shah@ti.com>
Signed-off-by: Moteen Shah <m-shah@ti.com>
---
 arch/arm64/boot/dts/ti/Makefile               |   4 +
 .../boot/dts/ti/k3-j721s2-evm-audio.dtso      | 157 ++++++++++++++++++
 arch/arm64/boot/dts/ti/k3-j721s2-main.dtsi    |   9 +
 3 files changed, 170 insertions(+)
 create mode 100644 arch/arm64/boot/dts/ti/k3-j721s2-evm-audio.dtso

diff --git a/arch/arm64/boot/dts/ti/Makefile b/arch/arm64/boot/dts/ti/Makefile
index 371f9a043fe52..d9824e17085f4 100644
--- a/arch/arm64/boot/dts/ti/Makefile
+++ b/arch/arm64/boot/dts/ti/Makefile
@@ -180,6 +180,7 @@ dtb-$(CONFIG_ARCH_K3) += k3-j721s2-common-proc-board.dtb
 dtb-$(CONFIG_ARCH_K3) += k3-j721s2-evm-gesi-exp-board.dtbo
 k3-j721s2-evm-dtbs := k3-j721s2-common-proc-board.dtb k3-j721s2-evm-gesi-exp-board.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-j721s2-evm.dtb
+dtb-$(CONFIG_ARCH_K3) += k3-j721s2-evm-audio.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-j721s2-evm-pcie1-ep.dtbo
 dtb-$(CONFIG_ARCH_K3) += k3-j721s2-evm-usb0-type-a.dtbo
 
@@ -320,6 +321,8 @@ k3-j721e-evm-pcie1-ep-dtbs := k3-j721e-common-proc-board.dtb \
 	k3-j721e-evm-pcie1-ep.dtbo
 k3-j721e-sk-csi2-dual-imx219-dtbs := k3-j721e-sk.dtb \
 	k3-j721e-sk-csi2-dual-imx219.dtbo
+k3-j721s2-evm-audio-dtbs := k3-j721s2-common-proc-board.dtb \
+	k3-j721s2-evm-audio.dtbo
 k3-j721s2-evm-pcie1-ep-dtbs := k3-j721s2-common-proc-board.dtb \
 	k3-j721s2-evm-pcie1-ep.dtbo
 k3-j721s2-evm-usb0-type-a-dtbs := k3-j721s2-common-proc-board.dtb \
@@ -396,6 +399,7 @@ dtb- += k3-am625-beagleplay-csi2-ov5640.dtb \
 	k3-j721e-evm-pcie0-ep.dtb \
 	k3-j721e-evm-pcie1-ep.dtb \
 	k3-j721e-sk-csi2-dual-imx219.dtb \
+	k3-j721s2-evm-audio.dtb \
 	k3-j721s2-evm-pcie1-ep.dtb \
 	k3-j721s2-evm-usb0-type-a.dtb \
 	k3-j722s-evm-csi2-quad-rpi-cam-imx219.dtb \
diff --git a/arch/arm64/boot/dts/ti/k3-j721s2-evm-audio.dtso b/arch/arm64/boot/dts/ti/k3-j721s2-evm-audio.dtso
new file mode 100644
index 0000000000000..ac5a827e1b750
--- /dev/null
+++ b/arch/arm64/boot/dts/ti/k3-j721s2-evm-audio.dtso
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Device Tree Overlay for J721S2 Audio Support
+ *
+ * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+#include "k3-pinctrl.h"
+
+&{/} {
+	codec_audio: sound {
+		compatible = "ti,j7200-cpb-audio";
+		model = "j721s2-cpb";
+
+		ti,cpb-mcasp = <&mcasp4>;
+		ti,cpb-codec = <&pcm3168a_1>;
+
+		clocks = <&k3_clks 213 0>, <&k3_clks 213 1>,
+			 <&k3_clks 157 299>, <&k3_clks 157 328>;
+		clock-names = "cpb-mcasp-auxclk", "cpb-mcasp-auxclk-48000",
+			      "cpb-codec-scki", "cpb-codec-scki-48000";
+	};
+
+	i2c_mux: mux-controller-2 {
+		compatible = "gpio-mux";
+		#mux-state-cells = <1>;
+		mux-gpios = <&wkup_gpio0 54 GPIO_ACTIVE_HIGH>;
+		idle-state = <1>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&main_i2c3_mux_pins_default>;
+	};
+};
+
+&main_pmx0 {
+	mcasp4_pins_default: mcasp4-default-pins {
+		pinctrl-single,pins = <
+			J721S2_IOPAD(0x0c8, PIN_OUTPUT_PULLDOWN, 1) /* (AD28) MCASP4_ACLKX */
+			J721S2_IOPAD(0x06c, PIN_OUTPUT_PULLDOWN, 1) /* (V26) MCASP4_AFSX */
+			J721S2_IOPAD(0x068, PIN_INPUT_PULLDOWN, 1) /* (U28) MCASP4_AXR1 */
+			J721S2_IOPAD(0x0c4, PIN_OUTPUT_PULLDOWN, 1) /* (AB26) MCASP4_AXR2 */
+			J721S2_IOPAD(0x070, PIN_OUTPUT_PULLDOWN, 1) /* (R27) MCASP4_AXR3 */
+		>;
+	};
+
+	audio_ext_refclk1_pins_default: audio-ext-refclk1-default-pins {
+		pinctrl-single,pins = <
+			J721S2_IOPAD(0x078, PIN_OUTPUT, 1) /* (Y25) MCAN2_RX.AUDIO_EXT_REFCLK1 */
+		>;
+	};
+};
+
+&wkup_pmx2 {
+	main_i2c3_mux_pins_default: main-i2c3-mux-default-pins {
+		pinctrl-single,pins = <
+			J721S2_WKUP_IOPAD(0x038, PIN_OUTPUT, 7) /* (B27) WKUP_GPIO0_54 */
+		>;
+	};
+};
+
+&exp2 {
+	p09-hog {
+		/* P09 - MCASP/TRACE_MUX_S0 */
+		gpio-hog;
+		gpios = <9 GPIO_ACTIVE_HIGH>;
+		output-low;
+		line-name = "MCASP/TRACE_MUX_S0";
+	};
+
+	p10-hog {
+		/* P10 - MCASP/TRACE_MUX_S1 */
+		gpio-hog;
+		gpios = <10 GPIO_ACTIVE_HIGH>;
+		output-high;
+		line-name = "MCASP/TRACE_MUX_S1";
+	};
+};
+
+&mux0 {
+	idle-state = <0>;
+};
+
+&mux1 {
+	idle-state = <0>;
+};
+
+&main_mcan3 {
+	/* Conflicts with McASP4 signal routing via mux0 */
+	status = "disabled";
+};
+
+&main_mcan5 {
+	/* Conflicts with McASP4 signal routing via mux1 */
+	status = "disabled";
+};
+
+&k3_clks {
+	/* Configure AUDIO_EXT_REFCLK1 pin as output */
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_ext_refclk1_pins_default>;
+};
+
+&main_i2c3 {
+	status = "okay";
+	pinctrl-names = "default";
+	pinctrl-0 = <&main_i2c3_pins_default>;
+	clock-frequency = <400000>;
+	mux-states = <&i2c_mux 1>;
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	exp3: gpio@20 {
+		compatible = "ti,tca6408";
+		reg = <0x20>;
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-line-names = "CODEC_RSTZ", "CODEC_SPARE1",
+				  "UB926_RESETN", "UB926_LOCK",
+				  "UB926_PWR_SW_CNTRL", "UB926_TUNER_RESET",
+				  "UB926_GPIO_SPARE";
+	};
+
+	pcm3168a_1: audio-codec@44 {
+		compatible = "ti,pcm3168a";
+		reg = <0x44>;
+		#sound-dai-cells = <1>;
+		reset-gpios = <&exp3 0 GPIO_ACTIVE_LOW>;
+		clocks = <&audio_refclk1>;
+		clock-names = "scki";
+		VDD1-supply = <&vsys_3v3>;
+		VDD2-supply = <&vsys_3v3>;
+		VCCAD1-supply = <&vsys_5v0>;
+		VCCAD2-supply = <&vsys_5v0>;
+		VCCDA1-supply = <&vsys_5v0>;
+		VCCDA2-supply = <&vsys_5v0>;
+	};
+};
+
+&mcasp4 {
+	status = "okay";
+	#sound-dai-cells = <0>;
+	pinctrl-names = "default";
+	pinctrl-0 = <&mcasp4_pins_default>;
+	op-mode = <0>;          /* MCASP_IIS_MODE */
+	tdm-slots = <2>;
+	auxclk-fs-ratio = <256>;
+	serial-dir = <	/* 0: INACTIVE, 1: TX, 2: RX */
+		0 2 1 1
+		0 0 0 0
+		0 0 0 0
+		0 0 0 0
+	>;
+};
diff --git a/arch/arm64/boot/dts/ti/k3-j721s2-main.dtsi b/arch/arm64/boot/dts/ti/k3-j721s2-main.dtsi
index 1228ac5711bf0..36a01a06dd254 100644
--- a/arch/arm64/boot/dts/ti/k3-j721s2-main.dtsi
+++ b/arch/arm64/boot/dts/ti/k3-j721s2-main.dtsi
@@ -74,6 +74,15 @@ ehrpwm_tbclk: clock-controller@140 {
 			reg = <0x140 0x18>;
 			#clock-cells = <1>;
 		};
+
+		audio_refclk1: clock-controller@42e4 {
+			compatible = "ti,j721s2-audio-refclk", "ti,am62-audio-refclk";
+			reg = <0x42e4 0x4>;
+			clocks = <&k3_clks 157 299>;
+			assigned-clocks = <&k3_clks 157 299>;
+			assigned-clock-parents = <&k3_clks 157 328>;
+			#clock-cells = <0>;
+		};
 	};
 
 	main_ehrpwm0: pwm@3000000 {
-- 
2.34.1



^ permalink raw reply related

* [PATCH v6 1/2] dt-bindings: ti: Update audio-refclk binding and j721e system controller
From: Moteen Shah @ 2026-06-19  6:27 UTC (permalink / raw)
  To: krzk+dt, robh, conor+dt, nm, vigneshr, kristo
  Cc: devicetree, linux-arm-kernel, linux-kernel, u-kumar1,
	gehariprasath, y-abhilashchandra, m-shah, sen
In-Reply-To: <20260619062749.1575066-1-m-shah@ti.com>

Add ti,j721s2-audio-refclk as a supported compatible string in the
ti,am62-audio-refclk binding. J721S2 uses the same audio reference
clock IP block first introduced on AM62. Per writing-bindings
guidelines, the J721S2-specific compatible is added as the primary
string with ti,am62-audio-refclk as the fallback.

Also extend the ti,j721e-system-controller clock-controller@ child
pattern to accept audio-refclk schemas alongside ehrpwm-tbclk via a
oneOf constraint, fixing the alphanumerical ordering of $refs.

Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Moteen Shah <m-shah@ti.com>
---
 .../devicetree/bindings/clock/ti,am62-audio-refclk.yaml   | 8 ++++++--
 .../bindings/soc/ti/ti,j721e-system-controller.yaml       | 6 ++++--
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/Documentation/devicetree/bindings/clock/ti,am62-audio-refclk.yaml b/Documentation/devicetree/bindings/clock/ti,am62-audio-refclk.yaml
index b2e40bd39a3af..6c8fb0793070d 100644
--- a/Documentation/devicetree/bindings/clock/ti,am62-audio-refclk.yaml
+++ b/Documentation/devicetree/bindings/clock/ti,am62-audio-refclk.yaml
@@ -11,8 +11,12 @@ maintainers:
 
 properties:
   compatible:
-    items:
-      - const: ti,am62-audio-refclk
+    oneOf:
+      - items:
+          - const: ti,am62-audio-refclk
+      - items:
+          - const: ti,j721s2-audio-refclk
+          - const: ti,am62-audio-refclk
 
   reg:
     maxItems: 1
diff --git a/Documentation/devicetree/bindings/soc/ti/ti,j721e-system-controller.yaml b/Documentation/devicetree/bindings/soc/ti/ti,j721e-system-controller.yaml
index f3bd0be3b279f..364be49f8c7c7 100644
--- a/Documentation/devicetree/bindings/soc/ti/ti,j721e-system-controller.yaml
+++ b/Documentation/devicetree/bindings/soc/ti/ti,j721e-system-controller.yaml
@@ -53,9 +53,11 @@ patternProperties:
 
   "^clock-controller@[0-9a-f]+$":
     type: object
-    $ref: /schemas/clock/ti,am654-ehrpwm-tbclk.yaml#
+    oneOf:
+      - $ref: /schemas/clock/ti,am62-audio-refclk.yaml#
+      - $ref: /schemas/clock/ti,am654-ehrpwm-tbclk.yaml#
     description:
-      Clock provider for TI EHRPWM nodes.
+      Clock provider for TI EHRPWM or Audio Reference Clock nodes.
 
   "phy@[0-9a-f]+$":
     type: object
-- 
2.34.1



^ permalink raw reply related

* [PATCH v6 0/2] Enable audio support for J721S2 EVM
From: Moteen Shah @ 2026-06-19  6:27 UTC (permalink / raw)
  To: krzk+dt, robh, conor+dt, nm, vigneshr, kristo
  Cc: devicetree, linux-arm-kernel, linux-kernel, u-kumar1,
	gehariprasath, y-abhilashchandra, m-shah, sen

Earlier version of the patchset sent upstream[0] was rejected as
its dependency[1], which resolves the DTBS check errors introduced
by [0] also got rejected on the grounds of ABI breakage.

Another solution to fix the DTBS check errors introduced by [0] is
to modify the ti,j721e-system-controller.yaml binding to allow
audio-refclk as clock-controller child. This is done in the first
patch of this series.

Changes since v5:
Link to v5:
https://lore.kernel.org/all/20260520115603.2662930-1-m-shah@ti.com/
- Move audio_refclk1 clock node from the overlay (k3-j721s2-evm-audio.dtso)
  to the main DTSI (k3-j721s2-main.dtsi) so it is always available
  independent of the overlay being applied
- Drop the &scm_conf block from the overlay since it is now in main.dtsi

Changes since v4:
Link to v4:
https://lore.kernel.org/all/20260519142341.2531948-1-m-shah@ti.com/
- Explicitly disable main_mcan3 and main_mcan5 as they will silently
  break mcasp audio routing

Link to v3:
https://lore.kernel.org/all/20260330094459.128648-1-m-shah@ti.com/
Link to v2:
https://lore.kernel.org/all/20260205130707.2033197-1-m-shah@ti.com/
Link to v1:
https://lore.kernel.org/all/20260112104536.83309-1-m-shah@ti.com/

Bootlogs:
https://gist.github.com/Jamm02/8ee551c2c8db3a58a9aa7976e049fa28

[0]: https://lore.kernel.org/linux-arm-kernel/20250604104656.38752-1-j-choudhary@ti.com/
[1]: https://lore.kernel.org/all/20250603095609.33569-4-j-choudhary@ti.com/

Jayesh Choudhary (1):
  arm64: dts: ti: Add audio overlay for k3-j721s2-evm

Moteen Shah (1):
  dt-bindings: ti: Update audio-refclk binding and j721e system
    controller

 .../bindings/clock/ti,am62-audio-refclk.yaml  |   8 +-
 .../soc/ti/ti,j721e-system-controller.yaml    |   6 +-
 arch/arm64/boot/dts/ti/Makefile               |   4 +
 .../boot/dts/ti/k3-j721s2-evm-audio.dtso      | 157 ++++++++++++++++++
 arch/arm64/boot/dts/ti/k3-j721s2-main.dtsi    |   9 +
 5 files changed, 180 insertions(+), 4 deletions(-)
 create mode 100644 arch/arm64/boot/dts/ti/k3-j721s2-evm-audio.dtso

-- 
2.34.1



^ permalink raw reply

* [PATCH 3/5] dmaengine: sun6i-dma: Add num_channels_per_reg for flexible interrupt mapping
From: Yuanshen Cao @ 2026-06-19  4:53 UTC (permalink / raw)
  To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
	Samuel Holland, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Maxime Ripard
  Cc: dmaengine, linux-arm-kernel, linux-sunxi, linux-kernel,
	devicetree, Yuanshen Cao
In-Reply-To: <20260619-sun60i-a733-dma-v1-0-da4b649fc72a@gmail.com>

The previous implementation of `sun6i-dma` had some implicit assumptions
about the number of channels per interrupt register. Specifically,
functions like `sun6i_kill_tasklet` were hardcoded to only disable
interrupts for IRQ 0 and 1. `DMA_MAX_CHANNELS` is also not in used in
the past, and the old SoCs never has more than 16 channels.

The A733 has a different interrupt structure where the number of
channels per register may differ. This patch introduces
`num_channels_per_reg` to the `sun6i_dma_config`, similar to BSP, to
make the interrupt handling logic hardware-agnostic. It also sets
`DMA_MAX_CHANNELS` to 16 to align with the new BSP code and ensure loops
over interrupts are correctly bounded.

Changes:
- Change `DMA_MAX_CHANNELS` definition to 16.
- Added `num_channels_per_reg` to `struct sun6i_dma_config`.
- Replaced hardcoded IRQ register calculations with values from
  `sdev->cfg->num_channels_per_reg`.
- Updated `sun6i_kill_tasklet` to loop through all possible interrupt
  registers based on `DMA_MAX_CHANNELS` and the configuration.

Signed-off-by: Yuanshen Cao <alex.caoys@gmail.com>
---
 drivers/dma/sun6i-dma.c | 25 ++++++++++++++++++-------
 1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index 059455425e19..fb1c1a28744b 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -41,7 +41,7 @@
 #define DMA_STAT		0x30
 
 /* Offset between DMA_IRQ_EN and DMA_IRQ_STAT limits number of channels */
-#define DMA_MAX_CHANNELS	(DMA_IRQ_CHAN_NR * 0x10 / 4)
+#define DMA_MAX_CHANNELS	16
 
 /*
  * sun8i specific registers
@@ -151,6 +151,7 @@ struct sun6i_dma_config {
 	u32 src_addr_widths;
 	u32 dst_addr_widths;
 	bool has_mbus_clk;
+	u32 num_channels_per_reg;
 };
 
 /*
@@ -481,8 +482,8 @@ static int sun6i_dma_start_desc(struct sun6i_vchan *vchan)
 
 	sun6i_dma_dump_lli(vchan, pchan->desc->v_lli, pchan->desc->p_lli);
 
-	irq_reg = pchan->idx / DMA_IRQ_CHAN_NR;
-	irq_offset = pchan->idx % DMA_IRQ_CHAN_NR;
+	irq_reg = pchan->idx / sdev->cfg->num_channels_per_reg;
+	irq_offset = pchan->idx % sdev->cfg->num_channels_per_reg;
 
 	vchan->irq_type = vchan->cyclic ? DMA_IRQ_PKG : DMA_IRQ_QUEUE;
 
@@ -574,7 +575,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
 	int i, j, ret = IRQ_NONE;
 	u32 status;
 
-	for (i = 0; i < sdev->num_pchans / DMA_IRQ_CHAN_NR; i++) {
+	for (i = 0; i < sdev->num_pchans / sdev->cfg->num_channels_per_reg; i++) {
 		status = sdev->cfg->read_irq_stat(sdev, i);
 		if (!status)
 			continue;
@@ -584,7 +585,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
 
 		sdev->cfg->write_irq_stat(sdev, i, status);
 
-		for (j = 0; (j < DMA_IRQ_CHAN_NR) && status; j++) {
+		for (j = 0; (j < sdev->cfg->num_channels_per_reg) && status; j++) {
 			pchan = sdev->pchans + j;
 			vchan = pchan->vchan;
 			if (vchan && (status & vchan->irq_type)) {
@@ -1108,9 +1109,11 @@ static struct dma_chan *sun6i_dma_of_xlate(struct of_phandle_args *dma_spec,
 
 static inline void sun6i_kill_tasklet(struct sun6i_dma_dev *sdev)
 {
+	int i;
+
 	/* Disable all interrupts from DMA */
-	writel(0, sdev->base + DMA_IRQ_EN(0));
-	writel(0, sdev->base + DMA_IRQ_EN(1));
+	for (i = 0; i < DMA_MAX_CHANNELS / sdev->cfg->num_channels_per_reg; i++)
+		sdev->cfg->write_irq_en(sdev, i, 0);
 
 	/* Prevent spurious interrupts from scheduling the tasklet */
 	atomic_inc(&sdev->tasklet_shutdown);
@@ -1171,6 +1174,7 @@ static struct sun6i_dma_config sun6i_a31_dma_cfg = {
 	.dst_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
 			     BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_4_BYTES),
+	.num_channels_per_reg = DMA_IRQ_CHAN_NR,
 };
 
 /*
@@ -1200,6 +1204,7 @@ static struct sun6i_dma_config sun8i_a23_dma_cfg = {
 	.dst_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
 			     BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_4_BYTES),
+	.num_channels_per_reg = DMA_IRQ_CHAN_NR,
 };
 
 static struct sun6i_dma_config sun8i_a83t_dma_cfg = {
@@ -1224,6 +1229,7 @@ static struct sun6i_dma_config sun8i_a83t_dma_cfg = {
 	.dst_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
 			     BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_4_BYTES),
+	.num_channels_per_reg = DMA_IRQ_CHAN_NR,
 };
 
 /*
@@ -1257,6 +1263,7 @@ static struct sun6i_dma_config sun8i_h3_dma_cfg = {
 			     BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_8_BYTES),
+	.num_channels_per_reg = DMA_IRQ_CHAN_NR,
 };
 
 /*
@@ -1284,6 +1291,7 @@ static struct sun6i_dma_config sun50i_a64_dma_cfg = {
 			     BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_8_BYTES),
+	.num_channels_per_reg = DMA_IRQ_CHAN_NR,
 };
 
 /*
@@ -1311,6 +1319,7 @@ static struct sun6i_dma_config sun50i_a100_dma_cfg = {
 			     BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_8_BYTES),
+	.num_channels_per_reg = DMA_IRQ_CHAN_NR,
 	.has_mbus_clk = true,
 };
 
@@ -1339,6 +1348,7 @@ static struct sun6i_dma_config sun50i_h6_dma_cfg = {
 			     BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_8_BYTES),
+	.num_channels_per_reg = DMA_IRQ_CHAN_NR,
 	.has_mbus_clk = true,
 };
 
@@ -1369,6 +1379,7 @@ static struct sun6i_dma_config sun8i_v3s_dma_cfg = {
 	.dst_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
 			     BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_4_BYTES),
+	.num_channels_per_reg = DMA_IRQ_CHAN_NR,
 };
 
 static const struct of_device_id sun6i_dma_match[] = {

-- 
2.54.0



^ permalink raw reply related

* [RFC PATCH 6/6] media: rkisp2: Implement inline mode
From: Paul Elder @ 2026-06-19  5:26 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: Paul Elder, michael.riesch, xuhf, stefan.klug, kieran.bingham,
	dan.scally, jacopo.mondi, linux-media, linux-arm-kernel,
	linux-rockchip, linux-kernel, hverkuil+cisco, nicolas.dufresne,
	ribalda, sakari.ailus
In-Reply-To: <20260619052637.1110672-1-paul.elder@ideasonboard.com>

Add support to rkisp2 for inline mode. Switching between offline mode
and inline mode is done by disabling/enabling the link between the
rkisp2 and rkcif.

As the link is on a sink pad on rkisp2-isp, rkisp2-dmarx is bypassed in
inline mode, because the v4l2_subdev_call() on the source will go to rkcif
instead of rkisp2-dmarx. Also DMA read only needs to be configured in
offline mode, while in inline mode there is no configuration necessary,
so the change to implement inline mode for rkisp2 is fairly lean.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>

---
Although this patch is meant to serve as an example of how one might
implement inline mode, I think the actual implementation may very well
resemble this. The API for switching between inline mode and offline
is likely to be heavily debated, though that is not a topic for this
series.

Despite it being "just an example", it has been tested and
captures properly, including loading rkcif and rkisp2 in differing
orders, and swapping between offline mode and inline mode (though
capturing in the wrong mode is still a bit problematic).

The shared media graph that was added in an earlier patch has made
media graph manipulation trivial.
---
 .../platform/rockchip/rkisp2/rkisp2-common.h  |   3 +-
 .../platform/rockchip/rkisp2/rkisp2-dev.c     |   6 +-
 .../platform/rockchip/rkisp2/rkisp2-isp.c     | 155 ++++++++++++++----
 3 files changed, 124 insertions(+), 40 deletions(-)

diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h b/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
index ecf0f5e22064..91ccb84b5a7a 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
@@ -88,10 +88,11 @@ enum rkisp2_fmt_raw_pat_type {
 
 /* enum for the isp pads */
 enum rkisp2_isp_pad {
-	RKISP2_ISP_PAD_SINK_VIDEO,
+	RKISP2_ISP_PAD_SINK_VIDEO_DMA,
 	RKISP2_ISP_PAD_SINK_PARAMS,
 	RKISP2_ISP_PAD_SOURCE_VIDEO,
 	RKISP2_ISP_PAD_SOURCE_STATS,
+	RKISP2_ISP_PAD_SINK_VIDEO_CIF,
 	RKISP2_ISP_PAD_MAX
 };
 
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c b/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
index f74b7aae3159..2b6b7ee31f1d 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
@@ -117,12 +117,12 @@ static int rkisp2_create_links(struct rkisp2_device *rkisp2)
 		if (i == RKISP2_RAWRD0) {
 			ret = media_create_pad_link(
 				source, 0, &rkisp2->isp.sd.entity,
-				RKISP2_ISP_PAD_SINK_VIDEO,
+				RKISP2_ISP_PAD_SINK_VIDEO_DMA,
 				MEDIA_LNK_FL_ENABLED);
 		} else {
 			ret = media_create_pad_link(source, 0,
 						    &rkisp2->isp.sd.entity,
-						    RKISP2_ISP_PAD_SINK_VIDEO,
+						    RKISP2_ISP_PAD_SINK_VIDEO_DMA,
 						    0);
 		}
 
@@ -150,7 +150,7 @@ static int rkisp2_create_links(struct rkisp2_device *rkisp2)
 
 	ret = media_device_shared_join_link_sink(rkisp2->media_dev, rkisp2->dev,
 						 &rkisp2->isp.sd.entity,
-						 RKISP2_ISP_PAD_SINK_VIDEO, 0);
+						 RKISP2_ISP_PAD_SINK_VIDEO_CIF, 0);
 	if (ret)
 		return ret;
 
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c b/drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c
index 36c1aeed272f..9be5fb4cbd14 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c
@@ -24,6 +24,27 @@
 
 #define RKISP2_ISP_DEV_NAME	RKISP2_DRIVER_NAME "_isp"
 
+static u16 rkisp2_isp_get_active_sink_pad(struct rkisp2_isp *isp)
+{
+	struct media_entity *entity = &isp->sd.entity;
+	struct media_link *link;
+
+	list_for_each_entry(link, &entity->links, list) {
+		if (link->sink->entity != entity ||
+		    (link->sink->index != RKISP2_ISP_PAD_SINK_VIDEO_DMA &&
+		     link->sink->index != RKISP2_ISP_PAD_SINK_VIDEO_CIF))
+			continue;
+
+		if (link->flags & MEDIA_LNK_FL_ENABLED) {
+			dev_dbg(isp->rkisp2->dev, "%s: active link is %d\n",
+				__func__, link->sink->index);
+			return link->sink->index;
+		}
+	}
+
+	/* Default to DMA if neither link is active */
+	return RKISP2_ISP_PAD_SINK_VIDEO_DMA;
+}
 
 /* ----------------------------------------------------------------------------
  * Camera Interface registers configurations
@@ -70,9 +91,9 @@ static int rkisp2_config_isp(struct rkisp2_isp *isp,
 	const struct v4l2_rect *sink_crop;
 
 	sink_frm = v4l2_subdev_state_get_format(sd_state,
-						RKISP2_ISP_PAD_SINK_VIDEO);
+						rkisp2_isp_get_active_sink_pad(isp));
 	sink_crop = v4l2_subdev_state_get_crop(sd_state,
-					       RKISP2_ISP_PAD_SINK_VIDEO);
+					       rkisp2_isp_get_active_sink_pad(isp));
 	src_frm = v4l2_subdev_state_get_format(sd_state,
 					       RKISP2_ISP_PAD_SOURCE_VIDEO);
 
@@ -116,6 +137,12 @@ static int rkisp2_config_isp(struct rkisp2_isp *isp,
 	rkisp2_write(rkisp2, RKISP2_CIF_ISP_OUT_H_SIZE, sink_crop->width);
 	rkisp2_write(rkisp2, RKISP2_CIF_ISP_OUT_V_SIZE, sink_crop->height);
 
+	/*
+	 * I think we don't need to configure cif source here because it seems
+	 * like offline mode needs to explicitly configure CSI2RX in offline
+	 * mode, but the default (all zero) is inline mode
+	 */
+
 	irq_mask |= RKISP2_CIF_ISP_FRAME | RKISP2_CIF_ISP_V_START |
 		    RKISP2_CIF_ISP_PIC_SIZE_ERROR;
 	rkisp2_write(rkisp2, RKISP2_CIF_ISP_IMSC, irq_mask);
@@ -273,7 +300,8 @@ static int rkisp2_isp_enum_mbus_code(struct v4l2_subdev *sd,
 	unsigned int i, dir;
 	int pos = 0;
 
-	if (code->pad == RKISP2_ISP_PAD_SINK_VIDEO) {
+	if (code->pad == RKISP2_ISP_PAD_SINK_VIDEO_DMA ||
+	    code->pad == RKISP2_ISP_PAD_SINK_VIDEO_CIF) {
 		dir = RKISP2_ISP_SD_SINK;
 	} else if (code->pad == RKISP2_ISP_PAD_SOURCE_VIDEO) {
 		dir = RKISP2_ISP_SD_SRC;
@@ -322,7 +350,8 @@ static int rkisp2_isp_enum_frame_size(struct v4l2_subdev *sd,
 		return -EINVAL;
 
 	if (!(mbus_info->direction & RKISP2_ISP_SD_SINK) &&
-	    fse->pad == RKISP2_ISP_PAD_SINK_VIDEO)
+	     (fse->pad == RKISP2_ISP_PAD_SINK_VIDEO_DMA ||
+	      fse->pad == RKISP2_ISP_PAD_SINK_VIDEO_CIF))
 		return -EINVAL;
 
 	if (!(mbus_info->direction & RKISP2_ISP_SD_SRC) &&
@@ -337,15 +366,14 @@ static int rkisp2_isp_enum_frame_size(struct v4l2_subdev *sd,
 	return 0;
 }
 
-static int rkisp2_isp_init_state(struct v4l2_subdev *sd,
-				 struct v4l2_subdev_state *sd_state)
+static void rkisp2_isp_init_state_sink(struct v4l2_subdev_state *sd_state,
+				       u16 pad)
 {
-	struct v4l2_mbus_framefmt *sink_fmt, *src_fmt;
-	struct v4l2_rect *sink_crop, *src_crop;
+	struct v4l2_mbus_framefmt *sink_fmt;
+	struct v4l2_rect *sink_crop;
+
+	sink_fmt = v4l2_subdev_state_get_format(sd_state, pad);
 
-	/* Video. */
-	sink_fmt = v4l2_subdev_state_get_format(sd_state,
-						RKISP2_ISP_PAD_SINK_VIDEO);
 	sink_fmt->width = RKISP2_DEFAULT_WIDTH;
 	sink_fmt->height = RKISP2_DEFAULT_HEIGHT;
 	sink_fmt->field = V4L2_FIELD_NONE;
@@ -355,13 +383,27 @@ static int rkisp2_isp_init_state(struct v4l2_subdev *sd,
 	sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
 	sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
 
-	sink_crop = v4l2_subdev_state_get_crop(sd_state,
-					       RKISP2_ISP_PAD_SINK_VIDEO);
+	sink_crop = v4l2_subdev_state_get_crop(sd_state, pad);
 	sink_crop->width = RKISP2_DEFAULT_WIDTH;
 	sink_crop->height = RKISP2_DEFAULT_HEIGHT;
 	sink_crop->left = 0;
 	sink_crop->top = 0;
+}
+
+static int rkisp2_isp_init_state(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state)
+{
+	struct v4l2_mbus_framefmt *sink_fmt, *src_fmt;
+	struct v4l2_rect *sink_crop, *src_crop;
+
+	/* Video. */
+	rkisp2_isp_init_state_sink(sd_state, RKISP2_ISP_PAD_SINK_VIDEO_DMA);
+	rkisp2_isp_init_state_sink(sd_state, RKISP2_ISP_PAD_SINK_VIDEO_CIF);
 
+	sink_fmt = v4l2_subdev_state_get_format(sd_state,
+						RKISP2_ISP_PAD_SINK_VIDEO_DMA);
+	sink_crop = v4l2_subdev_state_get_crop(sd_state,
+					       RKISP2_ISP_PAD_SINK_VIDEO_DMA);
 	src_fmt = v4l2_subdev_state_get_format(sd_state,
 					       RKISP2_ISP_PAD_SOURCE_VIDEO);
 	*src_fmt = *sink_fmt;
@@ -390,7 +432,7 @@ static void rkisp2_isp_set_src_fmt(struct rkisp2_isp *isp,
 	bool set_csc;
 
 	sink_fmt = v4l2_subdev_state_get_format(sd_state,
-						RKISP2_ISP_PAD_SINK_VIDEO);
+						rkisp2_isp_get_active_sink_pad(isp));
 	src_fmt = v4l2_subdev_state_get_format(sd_state,
 					       RKISP2_ISP_PAD_SOURCE_VIDEO);
 	src_crop = v4l2_subdev_state_get_crop(sd_state,
@@ -490,7 +532,7 @@ static void rkisp2_isp_set_src_crop(struct rkisp2_isp *isp,
 	src_crop = v4l2_subdev_state_get_crop(sd_state,
 					      RKISP2_ISP_PAD_SOURCE_VIDEO);
 	sink_crop = v4l2_subdev_state_get_crop(sd_state,
-					       RKISP2_ISP_PAD_SINK_VIDEO);
+					       rkisp2_isp_get_active_sink_pad(isp));
 
 	src_crop->left = ALIGN(r->left, 2);
 	src_crop->width = ALIGN(r->width, 2);
@@ -508,15 +550,13 @@ static void rkisp2_isp_set_src_crop(struct rkisp2_isp *isp,
 
 static void rkisp2_isp_set_sink_crop(struct rkisp2_isp *isp,
 				     struct v4l2_subdev_state *sd_state,
-				     struct v4l2_rect *r)
+				     struct v4l2_rect *r, u16 pad)
 {
 	struct v4l2_rect *sink_crop, *src_crop;
 	const struct v4l2_mbus_framefmt *sink_fmt;
 
-	sink_crop = v4l2_subdev_state_get_crop(sd_state,
-					       RKISP2_ISP_PAD_SINK_VIDEO);
-	sink_fmt = v4l2_subdev_state_get_format(sd_state,
-						RKISP2_ISP_PAD_SINK_VIDEO);
+	sink_crop = v4l2_subdev_state_get_crop(sd_state, pad);
+	sink_fmt = v4l2_subdev_state_get_format(sd_state, pad);
 
 	sink_crop->left = ALIGN(r->left, 2);
 	sink_crop->width = ALIGN(r->width, 2);
@@ -534,15 +574,14 @@ static void rkisp2_isp_set_sink_crop(struct rkisp2_isp *isp,
 
 static void rkisp2_isp_set_sink_fmt(struct rkisp2_isp *isp,
 				    struct v4l2_subdev_state *sd_state,
-				    struct v4l2_mbus_framefmt *format)
+				    struct v4l2_mbus_framefmt *format, u16 pad)
 {
 	const struct rkisp2_mbus_info *mbus_info;
 	struct v4l2_mbus_framefmt *sink_fmt;
 	struct v4l2_rect *sink_crop;
 	bool is_yuv;
 
-	sink_fmt = v4l2_subdev_state_get_format(sd_state,
-						RKISP2_ISP_PAD_SINK_VIDEO);
+	sink_fmt = v4l2_subdev_state_get_format(sd_state, pad);
 	sink_fmt->code = format->code;
 	mbus_info = rkisp2_mbus_info_get_by_code(sink_fmt->code);
 	if (!mbus_info || !(mbus_info->direction & RKISP2_ISP_SD_SINK)) {
@@ -590,9 +629,8 @@ static void rkisp2_isp_set_sink_fmt(struct rkisp2_isp *isp,
 	*format = *sink_fmt;
 
 	/* Propagate to in crop */
-	sink_crop = v4l2_subdev_state_get_crop(sd_state,
-					       RKISP2_ISP_PAD_SINK_VIDEO);
-	rkisp2_isp_set_sink_crop(isp, sd_state, sink_crop);
+	sink_crop = v4l2_subdev_state_get_crop(sd_state, pad);
+	rkisp2_isp_set_sink_crop(isp, sd_state, sink_crop, pad);
 }
 
 static int rkisp2_isp_set_fmt(struct v4l2_subdev *sd,
@@ -601,8 +639,9 @@ static int rkisp2_isp_set_fmt(struct v4l2_subdev *sd,
 {
 	struct rkisp2_isp *isp = to_rkisp2_isp(sd);
 
-	if (fmt->pad == RKISP2_ISP_PAD_SINK_VIDEO)
-		rkisp2_isp_set_sink_fmt(isp, sd_state, &fmt->format);
+	if (fmt->pad == RKISP2_ISP_PAD_SINK_VIDEO_DMA ||
+	    fmt->pad == RKISP2_ISP_PAD_SINK_VIDEO_CIF)
+		rkisp2_isp_set_sink_fmt(isp, sd_state, &fmt->format, fmt->pad);
 	else if (fmt->pad == RKISP2_ISP_PAD_SOURCE_VIDEO)
 		rkisp2_isp_set_src_fmt(isp, sd_state, &fmt->format);
 	else
@@ -619,12 +658,14 @@ static int rkisp2_isp_get_selection(struct v4l2_subdev *sd,
 	int ret = 0;
 
 	if (sel->pad != RKISP2_ISP_PAD_SOURCE_VIDEO &&
-	    sel->pad != RKISP2_ISP_PAD_SINK_VIDEO)
+	    sel->pad != RKISP2_ISP_PAD_SINK_VIDEO_DMA &&
+	    sel->pad != RKISP2_ISP_PAD_SINK_VIDEO_CIF)
 		return -EINVAL;
 
 	switch (sel->target) {
 	case V4L2_SEL_TGT_CROP_BOUNDS:
-		if (sel->pad == RKISP2_ISP_PAD_SINK_VIDEO) {
+		if (sel->pad == RKISP2_ISP_PAD_SINK_VIDEO_DMA ||
+		    sel->pad == RKISP2_ISP_PAD_SINK_VIDEO_CIF) {
 			struct v4l2_mbus_framefmt *fmt;
 
 			fmt = v4l2_subdev_state_get_format(sd_state, sel->pad);
@@ -634,7 +675,7 @@ static int rkisp2_isp_get_selection(struct v4l2_subdev *sd,
 			sel->r.top = 0;
 		} else {
 			sel->r = *v4l2_subdev_state_get_crop(sd_state,
-							     RKISP2_ISP_PAD_SINK_VIDEO);
+							     RKISP2_ISP_PAD_SINK_VIDEO_DMA);
 		}
 		break;
 
@@ -663,8 +704,9 @@ static int rkisp2_isp_set_selection(struct v4l2_subdev *sd,
 	dev_dbg(isp->rkisp2->dev, "%s: pad: %d sel(%d,%d)/%ux%u\n", __func__,
 		sel->pad, sel->r.left, sel->r.top, sel->r.width, sel->r.height);
 
-	if (sel->pad == RKISP2_ISP_PAD_SINK_VIDEO)
-		rkisp2_isp_set_sink_crop(isp, sd_state, &sel->r);
+	if (sel->pad == RKISP2_ISP_PAD_SINK_VIDEO_DMA ||
+	    sel->pad == RKISP2_ISP_PAD_SINK_VIDEO_CIF)
+		rkisp2_isp_set_sink_crop(isp, sd_state, &sel->r, sel->pad);
 	else if (sel->pad == RKISP2_ISP_PAD_SOURCE_VIDEO)
 		rkisp2_isp_set_src_crop(isp, sd_state, &sel->r);
 	else
@@ -678,6 +720,46 @@ static int rkisp2_subdev_link_validate(struct media_link *link)
 	return v4l2_subdev_link_validate(link);
 }
 
+static int rkisp2_subdev_link_setup(struct media_entity *entity,
+				    const struct media_pad *local_pad,
+				    const struct media_pad *remote_pad, u32 flags)
+{
+	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+	struct rkisp2_isp *isp = to_rkisp2_isp(sd);
+	struct media_link *link;
+	u16 other_sink_pad_index; 
+
+	dev_dbg(isp->rkisp2->dev, "link setup %s -> %s\n", remote_pad->entity->name,
+		local_pad->entity->name);
+
+	/* We only care about links being created on a sink pad */
+	if (!(flags & MEDIA_LNK_FL_ENABLED) ||
+	    !(local_pad->flags & MEDIA_PAD_FL_SINK) ||
+	    (local_pad->index != RKISP2_ISP_PAD_SINK_VIDEO_DMA &&
+	     local_pad->index != RKISP2_ISP_PAD_SINK_VIDEO_CIF))
+		return 0;
+
+	other_sink_pad_index =
+		local_pad->index == RKISP2_ISP_PAD_SINK_VIDEO_DMA ?
+				    RKISP2_ISP_PAD_SINK_VIDEO_CIF :
+				    RKISP2_ISP_PAD_SINK_VIDEO_DMA;
+
+	list_for_each_entry(link, &entity->links, list) {
+		if (link->sink->entity != local_pad->entity ||
+		    link->sink->index != other_sink_pad_index)
+			continue;
+
+		/*
+		 * If we are trying to enable DMA sink pad but the CIF
+		 * sink pad (and vice versa) has an enabled link then return
+		 * error
+		 */
+		return link->flags & MEDIA_LNK_FL_ENABLED ? -EBUSY : 0;
+	}
+
+	return 0;
+}
+
 static const struct v4l2_subdev_pad_ops rkisp2_isp_pad_ops = {
 	.enum_mbus_code = rkisp2_isp_enum_mbus_code,
 	.enum_frame_size = rkisp2_isp_enum_frame_size,
@@ -709,7 +791,7 @@ static int rkisp2_isp_s_stream(struct v4l2_subdev *sd, int enable)
 		return 0;
 	}
 
-	sink_pad = &isp->pads[RKISP2_ISP_PAD_SINK_VIDEO];
+	sink_pad = &isp->pads[rkisp2_isp_get_active_sink_pad(isp)];
 	source_pad = media_pad_remote_pad_unique(sink_pad);
 	if (IS_ERR(source_pad)) {
 		dev_dbg(rkisp2->dev, "Failed to get source for ISP: %ld\n",
@@ -769,6 +851,7 @@ static int rkisp2_isp_subs_evt(struct v4l2_subdev *sd, struct v4l2_fh *fh,
 
 static const struct media_entity_operations rkisp2_isp_media_ops = {
 	.link_validate = rkisp2_subdev_link_validate,
+	.link_setup = rkisp2_subdev_link_setup,
 };
 
 static const struct v4l2_subdev_video_ops rkisp2_isp_video_ops = {
@@ -808,8 +891,8 @@ int rkisp2_isp_register(struct rkisp2_device *rkisp2)
 	sd->owner = THIS_MODULE;
 	strscpy(sd->name, RKISP2_ISP_DEV_NAME, sizeof(sd->name));
 
-	pads[RKISP2_ISP_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK |
-						MEDIA_PAD_FL_MUST_CONNECT;
+	pads[RKISP2_ISP_PAD_SINK_VIDEO_DMA].flags = MEDIA_PAD_FL_SINK;
+	pads[RKISP2_ISP_PAD_SINK_VIDEO_CIF].flags = MEDIA_PAD_FL_SINK;
 	pads[RKISP2_ISP_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
 	pads[RKISP2_ISP_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
 	pads[RKISP2_ISP_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
-- 
2.47.2



^ permalink raw reply related

* [RFC PATCH 5/6] media: rkcif: Implement inline mode
From: Paul Elder @ 2026-06-19  5:26 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: Paul Elder, michael.riesch, xuhf, stefan.klug, kieran.bingham,
	dan.scally, jacopo.mondi, linux-media, linux-arm-kernel,
	linux-rockchip, linux-kernel, hverkuil+cisco, nicolas.dufresne,
	ribalda, sakari.ailus
In-Reply-To: <20260619052637.1110672-1-paul.elder@ideasonboard.com>

Add support to the rkcif for inline mode. Switching between offline mode
and inline mode is done by disabling/enabling the link between the rkcif
and rkisp2.

As the link is on a source pad on rkcif-interface, rkcif-mipi is
bypassed. This is why s_stream is implemented for rkcif_interface and it
calls enable_streams on the remote on the sink pad. Also since
rkcif_mipi_isr is called unconditionally it is skipped when in inline
mode.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>

---
This patch is meant to serve as an example of how one might implement
inline mode. Despite it being "just an example", it has been tested and
captures properly, including loading rkcif and rkisp2 in differing
orders, and swapping between offline mode and inline mode (though
capturing in the wrong mode is still a bit problematic).

The shared media graph that was added in an earlier patch has made media
graph manipulation a non-issue. Although the API for switching between
inline mode and offline can be debated (here it is done with link
manipulation via link_setup), that is not a relevant topic for this
series.
---
 .../rockchip/rkcif/rkcif-capture-dvp.c        |   2 +-
 .../rockchip/rkcif/rkcif-capture-mipi.c       |   3 +
 .../platform/rockchip/rkcif/rkcif-common.h    |  16 +-
 .../platform/rockchip/rkcif/rkcif-interface.c | 240 +++++++++++++++++-
 .../platform/rockchip/rkcif/rkcif-regs.h      |  49 ++++
 .../platform/rockchip/rkcif/rkcif-stream.c    |   6 +-
 6 files changed, 301 insertions(+), 15 deletions(-)

diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c
index dbaf7636aeeb..6aa1031ac330 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c
@@ -666,7 +666,7 @@ static int rkcif_dvp_start_streaming(struct rkcif_stream *stream)
 	int ret = -EINVAL;
 
 	state = v4l2_subdev_lock_and_get_active_state(&interface->sd);
-	source_fmt = v4l2_subdev_state_get_format(state, RKCIF_IF_PAD_SRC,
+	source_fmt = v4l2_subdev_state_get_format(state, RKCIF_IF_PAD_SRC_DMA,
 						  stream->id);
 	if (!source_fmt)
 		goto out;
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
index bc9518f8db50..50050cfa83b0 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
@@ -821,6 +821,9 @@ irqreturn_t rkcif_mipi_isr(int irq, void *ctx)
 		enum rkcif_interface_index index = RKCIF_MIPI_BASE + i;
 		struct rkcif_interface *interface = &rkcif->interfaces[index];
 
+		if (interface->inline_mode)
+			continue;
+
 		intstat = rkcif_mipi_read(interface, RKCIF_MIPI_INTSTAT);
 		rkcif_mipi_write(interface, RKCIF_MIPI_INTSTAT, intstat);
 
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-common.h b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
index f2989d152ba2..cf9322cae7a9 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-common.h
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
@@ -58,7 +58,8 @@ enum rkcif_interface_index {
 
 enum rkcif_interface_pad_index {
 	RKCIF_IF_PAD_SINK,
-	RKCIF_IF_PAD_SRC,
+	RKCIF_IF_PAD_SRC_DMA,
+	RKCIF_IF_PAD_SRC_TOISP,
 	RKCIF_IF_PAD_MAX
 };
 
@@ -194,6 +195,8 @@ struct rkcif_interface {
 	struct v4l2_fwnode_endpoint vep;
 	struct v4l2_subdev sd;
 
+	bool inline_mode;
+
 	union {
 		struct rkcif_dvp dvp;
 	};
@@ -247,4 +250,15 @@ struct rkcif_device {
 	struct v4l2_async_notifier notifier;
 };
 
+static inline void
+rkcif_write(struct rkcif_device *rkcif, unsigned int addr, u32 val)
+{
+	writel(val, rkcif->base_addr + addr);
+}
+
+static inline u32 rkcif_read(struct rkcif_device *rkcif, unsigned int addr)
+{
+	return readl(rkcif->base_addr + addr);
+}
+
 #endif
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
index cd791186f224..568835e47f92 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
@@ -6,6 +6,8 @@
  * Copyright (C) 2025 Collabora, Ltd.
  */
 
+#include <linux/pm_runtime.h>
+
 #include <media/mc-shared-graph.h>
 #include <media/v4l2-common.h>
 #include <media/v4l2-fwnode.h>
@@ -20,8 +22,118 @@ static inline struct rkcif_interface *to_rkcif_interface(struct v4l2_subdev *sd)
 	return container_of(sd, struct rkcif_interface, sd);
 }
 
+static u16 rkcif_interface_get_active_source_pad(struct rkcif_interface *interface)
+{
+	struct media_entity *entity = &interface->sd.entity;
+	struct media_link *link;
+
+	list_for_each_entry(link, &entity->links, list) {
+		if (link->source->entity != entity ||
+		    (link->source->index != RKCIF_IF_PAD_SRC_DMA &&
+		     link->source->index != RKCIF_IF_PAD_SRC_TOISP))
+			continue;
+
+		if (link->flags & MEDIA_LNK_FL_ENABLED) {
+			dev_dbg(interface->rkcif->dev, "%s: active link is %d\n",
+				__func__, link->source->index);
+			return link->source->index;
+		}
+	}
+
+	/* Default to DMA if neither link is active */
+	return RKCIF_IF_PAD_SRC_DMA;
+}
+
+static u32 rkcif_interface_mipi_dt(u32 fourcc)
+{
+	switch (fourcc) {
+	case MEDIA_BUS_FMT_SRGGB8_1X8:
+	case MEDIA_BUS_FMT_SBGGR8_1X8:
+	case MEDIA_BUS_FMT_SGBRG8_1X8:
+	case MEDIA_BUS_FMT_SGRBG8_1X8:
+		return RKCIF_CSI2_DT_RAW8;
+	case MEDIA_BUS_FMT_SRGGB10_1X10:
+	case MEDIA_BUS_FMT_SBGGR10_1X10:
+	case MEDIA_BUS_FMT_SGBRG10_1X10:
+	case MEDIA_BUS_FMT_SGRBG10_1X10:
+		return RKCIF_CSI2_DT_RAW10;
+	case MEDIA_BUS_FMT_SRGGB12_1X12:
+	case MEDIA_BUS_FMT_SBGGR12_1X12:
+	case MEDIA_BUS_FMT_SGBRG12_1X12:
+	case MEDIA_BUS_FMT_SGRBG12_1X12:
+		return RKCIF_CSI2_DT_RAW12;
+	default:
+		return RKCIF_CSI2_DT_RAW10;
+	}
+}
+
+static u32 rkcif_interface_mipi_parse_type(u32 fourcc)
+{
+	switch (fourcc) {
+	case MEDIA_BUS_FMT_SRGGB8_1X8:
+	case MEDIA_BUS_FMT_SBGGR8_1X8:
+	case MEDIA_BUS_FMT_SGBRG8_1X8:
+	case MEDIA_BUS_FMT_SGRBG8_1X8:
+		return RKCIF_MIPI_PARSE_TYPE_RAW8_RGB888;
+	case MEDIA_BUS_FMT_SRGGB10_1X10:
+	case MEDIA_BUS_FMT_SBGGR10_1X10:
+	case MEDIA_BUS_FMT_SGBRG10_1X10:
+	case MEDIA_BUS_FMT_SGRBG10_1X10:
+		return RKCIF_MIPI_PARSE_TYPE_RAW10;
+	case MEDIA_BUS_FMT_SRGGB12_1X12:
+	case MEDIA_BUS_FMT_SBGGR12_1X12:
+	case MEDIA_BUS_FMT_SGBRG12_1X12:
+	case MEDIA_BUS_FMT_SGRBG12_1X12:
+		return RKCIF_MIPI_PARSE_TYPE_RAW12;
+	default:
+		return RKCIF_MIPI_PARSE_TYPE_RAW10;
+	}
+}
+
+static int rkcif_interface_subdev_link_setup(struct media_entity *entity,
+					     const struct media_pad *local_pad,
+					     const struct media_pad *remote_pad,
+					     u32 flags)
+{
+	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+	struct rkcif_interface *interface = to_rkcif_interface(sd);
+	struct media_link *link;
+	u16 other_source_pad_index; 
+
+	dev_dbg(interface->rkcif->dev, "link setup %s -> %s\n",
+		local_pad->entity->name, remote_pad->entity->name);
+
+	/* We only care about links being created on a source pad */
+	if (!(flags & MEDIA_LNK_FL_ENABLED) ||
+	    !(local_pad->flags & MEDIA_PAD_FL_SOURCE) ||
+	    (local_pad->index != RKCIF_IF_PAD_SRC_DMA &&
+	     local_pad->index != RKCIF_IF_PAD_SRC_TOISP))
+		return 0;
+
+	other_source_pad_index =
+		local_pad->index == RKCIF_IF_PAD_SRC_DMA ?
+				    RKCIF_IF_PAD_SRC_TOISP :
+				    RKCIF_IF_PAD_SRC_DMA;
+
+	list_for_each_entry(link, &entity->links, list) {
+		if (link->source->entity != local_pad->entity ||
+		    link->source->index != other_source_pad_index)
+			continue;
+
+		/*
+		 * If we are trying to enable DMA source pad but the TOISP
+		 * source pad (and vice versa) has an enabled link then return
+		 * error
+		 */
+		return link->flags & MEDIA_LNK_FL_ENABLED ? -EBUSY : 0;
+	}
+
+	return 0;
+}
+
 static const struct media_entity_operations rkcif_interface_media_ops = {
 	.link_validate = v4l2_subdev_link_validate,
+	.link_setup = rkcif_interface_subdev_link_setup,
 	.has_pad_interdep = v4l2_subdev_has_pad_interdep,
 };
 
@@ -37,7 +149,8 @@ static int rkcif_interface_set_fmt(struct v4l2_subdev *sd,
 	int ret;
 
 	/* the format on the source pad always matches the sink pad */
-	if (format->pad == RKCIF_IF_PAD_SRC)
+	if (format->pad == RKCIF_IF_PAD_SRC_DMA ||
+	    format->pad == RKCIF_IF_PAD_SRC_TOISP)
 		return v4l2_subdev_get_fmt(sd, state, format);
 
 	input = rkcif_interface_find_input_fmt(interface, true,
@@ -85,7 +198,8 @@ static int rkcif_interface_get_sel(struct v4l2_subdev *sd,
 	struct v4l2_rect *crop;
 	int ret = 0;
 
-	if (sel->pad != RKCIF_IF_PAD_SRC)
+	if (sel->pad != RKCIF_IF_PAD_SRC_DMA &&
+	    sel->pad != RKCIF_IF_PAD_SRC_TOISP)
 		return -EINVAL;
 
 	sink = v4l2_subdev_state_get_opposite_stream_format(state, sel->pad,
@@ -122,7 +236,8 @@ static int rkcif_interface_set_sel(struct v4l2_subdev *sd,
 	struct v4l2_mbus_framefmt *sink, *src;
 	struct v4l2_rect *crop;
 
-	if (sel->pad != RKCIF_IF_PAD_SRC || sel->target != V4L2_SEL_TGT_CROP)
+	if ((sel->pad != RKCIF_IF_PAD_SRC_DMA &&
+	     sel->pad != RKCIF_IF_PAD_SRC_TOISP) || sel->target != V4L2_SEL_TGT_CROP)
 		return -EINVAL;
 
 	sink = v4l2_subdev_state_get_opposite_stream_format(state, sel->pad,
@@ -176,7 +291,10 @@ static int rkcif_interface_apply_crop(struct rkcif_stream *stream,
 	struct rkcif_interface *interface = stream->interface;
 	struct v4l2_rect *crop;
 
-	crop = v4l2_subdev_state_get_crop(state, RKCIF_IF_PAD_SRC, stream->id);
+	crop = v4l2_subdev_state_get_crop(
+		state,
+		rkcif_interface_get_active_source_pad(interface),
+		stream->id);
 	if (!crop)
 		return -EINVAL;
 
@@ -195,8 +313,11 @@ static int rkcif_interface_enable_streams(struct v4l2_subdev *sd,
 	struct v4l2_subdev_route *route;
 	struct v4l2_subdev *remote_sd;
 	struct media_pad *remote_pad;
+	u32 active_pad = rkcif_interface_get_active_source_pad(interface);
 	u64 mask;
 
+	interface->inline_mode = (active_pad == RKCIF_IF_PAD_SRC_TOISP);
+
 	remote_pad =
 		media_pad_remote_pad_first(&sd->entity.pads[RKCIF_IF_PAD_SINK]);
 	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
@@ -213,7 +334,7 @@ static int rkcif_interface_enable_streams(struct v4l2_subdev *sd,
 	}
 
 	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_IF_PAD_SINK,
-					       RKCIF_IF_PAD_SRC, &streams_mask);
+					       active_pad, &streams_mask);
 
 	return v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask);
 }
@@ -222,6 +343,7 @@ static int rkcif_interface_disable_streams(struct v4l2_subdev *sd,
 					   struct v4l2_subdev_state *state,
 					   u32 pad, u64 streams_mask)
 {
+	struct rkcif_interface *interface = to_rkcif_interface(sd);
 	struct v4l2_subdev *remote_sd;
 	struct media_pad *remote_pad;
 	u64 mask;
@@ -230,8 +352,9 @@ static int rkcif_interface_disable_streams(struct v4l2_subdev *sd,
 		media_pad_remote_pad_first(&sd->entity.pads[RKCIF_IF_PAD_SINK]);
 	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
 
-	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_IF_PAD_SINK,
-					       RKCIF_IF_PAD_SRC, &streams_mask);
+	mask = v4l2_subdev_state_xlate_streams(
+		state, RKCIF_IF_PAD_SINK,
+		rkcif_interface_get_active_source_pad(interface), &streams_mask);
 
 	return v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask);
 }
@@ -246,7 +369,94 @@ static const struct v4l2_subdev_pad_ops rkcif_interface_pad_ops = {
 	.disable_streams = rkcif_interface_disable_streams,
 };
 
+static int rkcif_interface_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct rkcif_interface *interface = to_rkcif_interface(sd);
+	struct rkcif_device *rkcif = interface->rkcif;
+	struct v4l2_subdev_state *state;
+	struct v4l2_subdev *remote_sd;
+	struct media_pad *remote_pad;
+	struct v4l2_rect *crop;
+	struct v4l2_mbus_framefmt *mbus;
+	int ret;
+	u32 active_pad = rkcif_interface_get_active_source_pad(interface);
+	u32 val, crop_val, offset_val;
+
+	interface->inline_mode = (active_pad == RKCIF_IF_PAD_SRC_TOISP);
+
+	remote_pad =
+		media_pad_remote_pad_first(&sd->entity.pads[RKCIF_IF_PAD_SINK]);
+	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
+
+	if (!enable) {
+		rkcif_write(rkcif, RKCIF_MIPI2_ID0_CTRL0, 0);
+		rkcif_write(rkcif, RKCIF_MIPI2_CTRL, 0);
+
+		ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, 1);
+
+		pm_runtime_put(rkcif->dev);
+		return ret;
+	}
+
+	ret = pm_runtime_resume_and_get(rkcif->dev);
+	if (ret < 0) {
+		dev_err(rkcif->dev, "failed to get runtime pm, %d\n", ret);
+		return ret;
+	}
+
+	state = v4l2_subdev_lock_and_get_active_state(sd);
+	crop = v4l2_subdev_state_get_crop(state, active_pad);
+	mbus = v4l2_subdev_state_get_format(state, active_pad);
+	v4l2_subdev_unlock_state(state);
+
+	/*
+	 * Enable interrupts:
+	 * - toisp0 ch{0,1,2} frame start
+	 * - toisp1 ch{0,1,2} frame start
+	 * - toisp0 ch{0,1,2} frame end
+	 * - toisp1 ch{0,1,2} frame end
+	 * - toisp0 fifo overflow
+	 * - toisp1 fifo overflow
+	 * - axi bus errors
+	 */
+	rkcif_write(rkcif, RKCIF_GLB_INTEN, 0xFFFC003);
+
+	/*
+	 * - Enable toisp ch0
+	 * - Select MIPI2 ID0
+	 */
+	val = 0x01;
+	val |= 8 << 3;
+	rkcif_write(rkcif, RKCIF_TOISP0_CH_CTRL, val);
+
+	crop_val = RKCIF_XY_COORD(3840, 2160);
+	offset_val = RKCIF_XY_COORD(0, 0);
+	if (!!crop) {
+		crop_val = RKCIF_XY_COORD(crop->width, crop->height);
+		offset_val = RKCIF_XY_COORD(crop->left, crop->top);
+	}
+
+	rkcif_write(rkcif, RKCIF_TOISP0_CROP_SIZE, crop_val);
+	rkcif_write(rkcif, RKCIF_TOISP0_CROP_START, offset_val);
+
+	val = rkcif_interface_mipi_parse_type(mbus ? mbus->code : 0);
+	val |= RKCIF_MIPI_DATA_SEL_DT(
+			rkcif_interface_mipi_dt(mbus ? mbus->code : 0));
+	val |= RKCIF_MIPI_CAP_EN;
+
+	rkcif_write(rkcif, RKCIF_MIPI2_ID0_CTRL0, val);
+	rkcif_write(rkcif, RKCIF_MIPI2_ID0_CTRL1, crop_val);
+	rkcif_write(rkcif, RKCIF_MIPI2_CTRL, RKCIF_MIPI_CAP_EN);
+
+	return v4l2_subdev_enable_streams(remote_sd, remote_pad->index, 1);
+}
+
+static const struct v4l2_subdev_video_ops rkcif_interface_video_ops = {
+	.s_stream = rkcif_interface_s_stream,
+};
+
 static const struct v4l2_subdev_ops rkcif_interface_ops = {
+	.video = &rkcif_interface_video_ops,
 	.pad = &rkcif_interface_pad_ops,
 };
 
@@ -258,7 +468,14 @@ static int rkcif_interface_init_state(struct v4l2_subdev *sd,
 		{
 			.sink_pad = RKCIF_IF_PAD_SINK,
 			.sink_stream = 0,
-			.source_pad = RKCIF_IF_PAD_SRC,
+			.source_pad = RKCIF_IF_PAD_SRC_DMA,
+			.source_stream = 0,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		},
+		{
+			.sink_pad = RKCIF_IF_PAD_SINK,
+			.sink_stream = 0,
+			.source_pad = RKCIF_IF_PAD_SRC_TOISP,
 			.source_stream = 0,
 			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
 		},
@@ -373,6 +590,8 @@ int rkcif_interface_register(struct rkcif_device *rkcif,
 	sd->internal_ops = &rkcif_interface_internal_ops;
 	sd->owner = THIS_MODULE;
 
+	interface->inline_mode = false;
+
 	if (interface->type == RKCIF_IF_DVP)
 		snprintf(sd->name, sizeof(sd->name), "rkcif-dvp0");
 	else if (interface->type == RKCIF_IF_MIPI)
@@ -381,7 +600,8 @@ int rkcif_interface_register(struct rkcif_device *rkcif,
 
 	pads[RKCIF_IF_PAD_SINK].flags = MEDIA_PAD_FL_SINK |
 					MEDIA_PAD_FL_MUST_CONNECT;
-	pads[RKCIF_IF_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
+	pads[RKCIF_IF_PAD_SRC_DMA].flags = MEDIA_PAD_FL_SOURCE;
+	pads[RKCIF_IF_PAD_SRC_TOISP].flags = MEDIA_PAD_FL_SOURCE;
 	ret = media_entity_pads_init(&sd->entity, RKCIF_IF_PAD_MAX, pads);
 	if (ret)
 		goto err;
@@ -403,7 +623,7 @@ int rkcif_interface_register(struct rkcif_device *rkcif,
 	ret = media_device_shared_join_link_source(interface->rkcif->media_dev,
 						   interface->rkcif->dev,
 						   &interface->sd.entity,
-						   RKCIF_IF_PAD_SRC,
+						   RKCIF_IF_PAD_SRC_TOISP,
 						   0);
 	if (ret)
 		goto err_subdev_unregister;
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-regs.h b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
index 3cf7ee19de30..3addd2aac691 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
@@ -150,4 +150,53 @@ enum rkcif_mipi_id_register_index {
 	RKCIF_MIPI_ID_REGISTER_MAX
 };
 
+#define RKCIF_BASE		0x00000000
+#define RKCIF_GLB_CTRL		(RKCIF_BASE + 0x00000000)
+#define RKCIF_GLB_INTEN		(RKCIF_BASE + 0x00000004)
+#define RKCIF_GLB_INTST		(RKCIF_BASE + 0x00000008)
+#define RKCIF_TOISP0_CH_CTRL	(RKCIF_BASE + 0x00000780)
+#define RKCIF_TOISP0_CROP_SIZE	(RKCIF_BASE + 0x00000784)
+#define RKCIF_TOISP0_CROP_START	(RKCIF_BASE + 0x00000788)
+#define RKCIF_TOISP1_CH_CTRL	(RKCIF_BASE + 0x0000078c)
+#define RKCIF_TOISP1_CROP_SIZE	(RKCIF_BASE + 0x00000790)
+#define RKCIF_TOISP1_CROP_START	(RKCIF_BASE + 0x00000794)
+
+#define RKCIF_MIPI2_BASE	(RKCIF_BASE + 0x00000300)
+#define RKCIF_MIPI2_ID0_CTRL0	(RKCIF_MIPI2_BASE + 0x00000000)
+#define RKCIF_MIPI2_ID0_CTRL1	(RKCIF_MIPI2_BASE + 0x00000004)
+#define RKCIF_MIPI2_CTRL	(RKCIF_MIPI2_BASE + 0x00000020)
+
+#define RKCIF_MIPI_CAP_EN			BIT(0)
+#define RKCIF_MIPI_PARSE_TYPE_RAW8_RGB888	(0 << 1)
+#define RKCIF_MIPI_PARSE_TYPE_RAW10		(1 << 1)
+#define RKCIF_MIPI_PARSE_TYPE_RAW12		(2 << 1)
+#define RKCIF_MIPI_PARSE_TYPE_RAW14		(3 << 1)
+#define RKCIF_MIPI_PARSE_TYPE_YUV422_8BIT	(4 << 1)
+#define RKCIF_MIPI_CROP_EN			BIT(4)
+#define RKCIF_MIPI_WDDR_RAW_COMPACT		(0 << 5)
+#define RKCIF_MIPI_WDDR_RAW_UNCOMPACT		(1 << 5)
+#define RKCIF_MIPI_WDDR_YUV_PACKET		(2 << 5)
+#define RKCIF_MIPI_WDDR_YUV400			(3 << 5)
+#define RKCIF_MIPI_WDDR_YUV422SP		(4 << 5)
+#define RKCIF_MIPI_WDDR_YUV420SP		(5 << 5)
+
+/* MIPI_DATA_SEL */
+#define RKCIF_MIPI_DATA_SEL_VC(a)			(((a) & 0x3) << 8)
+#define RKCIF_MIPI_DATA_SEL_DT(a)			(((a) & 0x3F) << 10)
+/* MIPI DATA_TYPE */
+/* These are copied from rkisp2 as they are not in the cif trm */
+#define RKCIF_CSI2_DT_EBD			0x12
+#define RKCIF_CSI2_DT_YUV420_8b			0x18
+#define RKCIF_CSI2_DT_YUV420_10b		0x19
+#define RKCIF_CSI2_DT_YUV422_8b			0x1E
+#define RKCIF_CSI2_DT_YUV422_10b		0x1F
+#define RKCIF_CSI2_DT_RGB565			0x22
+#define RKCIF_CSI2_DT_RGB666			0x23
+#define RKCIF_CSI2_DT_RGB888			0x24
+#define RKCIF_CSI2_DT_RAW8			0x2A
+#define RKCIF_CSI2_DT_RAW10			0x2B
+#define RKCIF_CSI2_DT_RAW12			0x2C
+#define RKCIF_CSI2_DT_RAW16			0x2e
+#define RKCIF_CSI2_DT_SPD			0x2F
+
 #endif
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-stream.c b/drivers/media/platform/rockchip/rkcif/rkcif-stream.c
index 3130d420ad55..f173657fd2b4 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-stream.c
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-stream.c
@@ -283,7 +283,7 @@ static int rkcif_stream_start_streaming(struct vb2_queue *queue,
 
 	mask = BIT_ULL(stream->id);
 	ret = v4l2_subdev_enable_streams(&stream->interface->sd,
-					 RKCIF_IF_PAD_SRC, mask);
+					 RKCIF_IF_PAD_SRC_DMA, mask);
 	if (ret < 0)
 		goto err_stop_stream;
 
@@ -309,7 +309,7 @@ static void rkcif_stream_stop_streaming(struct vb2_queue *queue)
 	int ret;
 
 	mask = BIT_ULL(stream->id);
-	v4l2_subdev_disable_streams(&stream->interface->sd, RKCIF_IF_PAD_SRC,
+	v4l2_subdev_disable_streams(&stream->interface->sd, RKCIF_IF_PAD_SRC_DMA,
 				    mask);
 
 	stream->stopping = true;
@@ -589,7 +589,7 @@ int rkcif_stream_register(struct rkcif_device *rkcif,
 	if (stream->id == RKCIF_ID0)
 		link_flags |= MEDIA_LNK_FL_ENABLED;
 
-	ret = media_create_pad_link(&interface->sd.entity, RKCIF_IF_PAD_SRC,
+	ret = media_create_pad_link(&interface->sd.entity, RKCIF_IF_PAD_SRC_DMA,
 				    &stream->vdev.entity, 0, link_flags);
 	if (ret) {
 		dev_err(rkcif->dev, "failed to link stream media pad: %d\n",
-- 
2.47.2



^ permalink raw reply related

* [RFC PATCH 4/6] media: rkisp2: Use shared media graph
From: Paul Elder @ 2026-06-19  5:26 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: Paul Elder, michael.riesch, xuhf, stefan.klug, kieran.bingham,
	dan.scally, jacopo.mondi, linux-media, linux-arm-kernel,
	linux-rockchip, linux-kernel, hverkuil+cisco, nicolas.dufresne,
	ribalda, sakari.ailus
In-Reply-To: <20260619052637.1110672-1-paul.elder@ideasonboard.com>

Make rkisp2 use shared media graph. This allows it to be in the same
media graph as rkcif on the rk3588, opening to door to allowing the
entire capture pipeline to run in inline mode.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
---
 .../platform/rockchip/rkisp2/rkisp2-common.h  |  2 +-
 .../platform/rockchip/rkisp2/rkisp2-dev.c     | 42 ++++++++++---------
 2 files changed, 24 insertions(+), 20 deletions(-)

diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h b/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
index 1eafdb5db5d8..ecf0f5e22064 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
@@ -432,7 +432,7 @@ struct rkisp2_device {
 	struct regmap *gasket;
 	unsigned int gasket_id;
 	struct v4l2_device v4l2_dev;
-	struct media_device media_dev;
+	struct media_device *media_dev;
 	struct v4l2_async_notifier notifier;
 	struct v4l2_subdev *source;
 	struct rkisp2_isp isp;
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c b/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
index 4042bf43d287..f74b7aae3159 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
@@ -20,6 +20,7 @@
 #include <linux/platform_device.h>
 #include <linux/pinctrl/consumer.h>
 #include <linux/pm_runtime.h>
+#include <media/mc-shared-graph.h>
 #include <media/v4l2-fwnode.h>
 #include <media/v4l2-mc.h>
 
@@ -117,7 +118,7 @@ static int rkisp2_create_links(struct rkisp2_device *rkisp2)
 			ret = media_create_pad_link(
 				source, 0, &rkisp2->isp.sd.entity,
 				RKISP2_ISP_PAD_SINK_VIDEO,
-				MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
+				MEDIA_LNK_FL_ENABLED);
 		} else {
 			ret = media_create_pad_link(source, 0,
 						    &rkisp2->isp.sd.entity,
@@ -147,6 +148,12 @@ static int rkisp2_create_links(struct rkisp2_device *rkisp2)
 	if (ret)
 		return ret;
 
+	ret = media_device_shared_join_link_sink(rkisp2->media_dev, rkisp2->dev,
+						 &rkisp2->isp.sd.entity,
+						 RKISP2_ISP_PAD_SINK_VIDEO, 0);
+	if (ret)
+		return ret;
+
 	return 0;
 }
 
@@ -233,6 +240,7 @@ static int rkisp2_probe(struct platform_device *pdev)
 	struct device *dev = &pdev->dev;
 	struct rkisp2_device *rkisp2;
 	struct v4l2_device *v4l2_dev;
+	struct media_device *mdev;
 	unsigned int i;
 	int ret, irq;
 	u32 cif_id;
@@ -298,29 +306,28 @@ static int rkisp2_probe(struct platform_device *pdev)
 
 	pm_runtime_put(&pdev->dev);
 
-	rkisp2->media_dev.hw_revision = info->isp_ver;
-	strscpy(rkisp2->media_dev.model, RKISP2_DRIVER_NAME,
-		sizeof(rkisp2->media_dev.model));
-	rkisp2->media_dev.dev = &pdev->dev;
-	strscpy(rkisp2->media_dev.bus_info, RKISP2_BUS_INFO,
-		sizeof(rkisp2->media_dev.bus_info));
-	media_device_init(&rkisp2->media_dev);
+	mdev = media_device_shared_join(rkisp2->dev);
+	if (IS_ERR(mdev))
+		goto err_pm_runtime_disable;
+
+	rkisp2->media_dev = mdev;
+	rkisp2->media_dev->hw_revision = info->isp_ver;
+	strscpy(rkisp2->media_dev->model, RKISP2_DRIVER_NAME,
+		sizeof(rkisp2->media_dev->model));
+	strscpy(rkisp2->media_dev->bus_info, RKISP2_BUS_INFO,
+		sizeof(rkisp2->media_dev->bus_info));
 
 	v4l2_dev = &rkisp2->v4l2_dev;
-	v4l2_dev->mdev = &rkisp2->media_dev;
+	v4l2_dev->mdev = rkisp2->media_dev;
 	strscpy(v4l2_dev->name, RKISP2_DRIVER_NAME, sizeof(v4l2_dev->name));
 
 	ret = v4l2_device_register(rkisp2->dev, &rkisp2->v4l2_dev);
 	if (ret)
 		goto err_media_dev_cleanup;
 
-	ret = media_device_register(&rkisp2->media_dev);
-	if (ret)
-		goto err_unreg_v4l2_dev;
-
 	ret = rkisp2_entities_register(rkisp2);
 	if (ret)
-		goto err_unreg_media_dev;
+		goto err_unreg_v4l2_dev;
 
 	ret = v4l2_device_register_subdev_nodes(&rkisp2->v4l2_dev);
 	if (ret)
@@ -332,12 +339,10 @@ static int rkisp2_probe(struct platform_device *pdev)
 
 err_unreg_entities:
 	rkisp2_entities_unregister(rkisp2);
-err_unreg_media_dev:
-	media_device_unregister(&rkisp2->media_dev);
 err_unreg_v4l2_dev:
 	v4l2_device_unregister(&rkisp2->v4l2_dev);
 err_media_dev_cleanup:
-	media_device_cleanup(&rkisp2->media_dev);
+	media_device_shared_leave(rkisp2->media_dev, rkisp2->dev);
 err_pm_runtime_disable:
 	pm_runtime_disable(&pdev->dev);
 	return ret;
@@ -353,10 +358,9 @@ static void rkisp2_remove(struct platform_device *pdev)
 	rkisp2_entities_unregister(rkisp2);
 	rkisp2_debug_cleanup(rkisp2);
 
-	media_device_unregister(&rkisp2->media_dev);
 	v4l2_device_unregister(&rkisp2->v4l2_dev);
 
-	media_device_cleanup(&rkisp2->media_dev);
+	media_device_shared_leave(rkisp2->media_dev, rkisp2->dev);
 
 	pm_runtime_disable(&pdev->dev);
 }
-- 
2.47.2



^ permalink raw reply related

* [RFC PATCH 3/6] media: rkcif: Use shared media graph
From: Paul Elder @ 2026-06-19  5:26 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: Paul Elder, michael.riesch, xuhf, stefan.klug, kieran.bingham,
	dan.scally, jacopo.mondi, linux-media, linux-arm-kernel,
	linux-rockchip, linux-kernel, hverkuil+cisco, nicolas.dufresne,
	ribalda, sakari.ailus
In-Reply-To: <20260619052637.1110672-1-paul.elder@ideasonboard.com>

Make rkcif use shared media graph. This allows it to be in the same
media graph as rkisp2 on the rk3588, opening to door to allowing the
entire capture pipeline to run in inline mode.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
---
 .../platform/rockchip/rkcif/rkcif-common.h    |  2 +-
 .../media/platform/rockchip/rkcif/rkcif-dev.c | 32 +++++++++----------
 .../platform/rockchip/rkcif/rkcif-interface.c | 10 ++++++
 3 files changed, 27 insertions(+), 17 deletions(-)

diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-common.h b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
index 4d9211ba9bda..f2989d152ba2 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-common.h
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
@@ -242,7 +242,7 @@ struct rkcif_device {
 
 	struct rkcif_interface interfaces[RKCIF_IF_MAX];
 
-	struct media_device media_dev;
+	struct media_device *media_dev;
 	struct v4l2_device v4l2_dev;
 	struct v4l2_async_notifier notifier;
 };
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
index be3a174b9aab..4c86e3e2f3cd 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
@@ -20,6 +20,7 @@
 #include <linux/pm_runtime.h>
 #include <linux/reset.h>
 
+#include <media/mc-shared-graph.h>
 #include <media/v4l2-fwnode.h>
 #include <media/v4l2-mc.h>
 
@@ -165,6 +166,7 @@ static int rkcif_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
 	struct rkcif_device *rkcif;
+	struct media_device *mdev;
 	int ret, irq;
 
 	rkcif = devm_kzalloc(dev, sizeof(*rkcif), GFP_KERNEL);
@@ -212,22 +214,22 @@ static int rkcif_probe(struct platform_device *pdev)
 
 	pm_runtime_enable(&pdev->dev);
 
-	rkcif->media_dev.dev = dev;
-	strscpy(rkcif->media_dev.model, RKCIF_DRIVER_NAME,
-		sizeof(rkcif->media_dev.model));
-	media_device_init(&rkcif->media_dev);
+	mdev = media_device_shared_join(rkcif->dev);
+	if (IS_ERR(mdev)) {
+		dev_err(dev, "failed to register media device: %d\n", ret);
+		goto err_pm_runtime_disable;
+	}
+
+	rkcif->media_dev = mdev;
+	rkcif->media_dev->dev = dev;
+	strscpy(rkcif->media_dev->model, RKCIF_DRIVER_NAME,
+		sizeof(rkcif->media_dev->model));
 
-	rkcif->v4l2_dev.mdev = &rkcif->media_dev;
+	rkcif->v4l2_dev.mdev = rkcif->media_dev;
 	ret = v4l2_device_register(dev, &rkcif->v4l2_dev);
 	if (ret)
 		goto err_media_dev_cleanup;
 
-	ret = media_device_register(&rkcif->media_dev);
-	if (ret < 0) {
-		dev_err(dev, "failed to register media device: %d\n", ret);
-		goto err_v4l2_dev_unregister;
-	}
-
 	v4l2_async_nf_init(&rkcif->notifier, &rkcif->v4l2_dev);
 	rkcif->notifier.ops = &rkcif_notifier_ops;
 
@@ -247,11 +249,10 @@ static int rkcif_probe(struct platform_device *pdev)
 	rkcif_unregister(rkcif);
 err_notifier_cleanup:
 	v4l2_async_nf_cleanup(&rkcif->notifier);
-	media_device_unregister(&rkcif->media_dev);
-err_v4l2_dev_unregister:
 	v4l2_device_unregister(&rkcif->v4l2_dev);
 err_media_dev_cleanup:
-	media_device_cleanup(&rkcif->media_dev);
+	media_device_shared_leave(rkcif->media_dev, rkcif->dev);
+err_pm_runtime_disable:
 	pm_runtime_disable(&pdev->dev);
 	return ret;
 }
@@ -263,9 +264,8 @@ static void rkcif_remove(struct platform_device *pdev)
 	v4l2_async_nf_unregister(&rkcif->notifier);
 	rkcif_unregister(rkcif);
 	v4l2_async_nf_cleanup(&rkcif->notifier);
-	media_device_unregister(&rkcif->media_dev);
 	v4l2_device_unregister(&rkcif->v4l2_dev);
-	media_device_cleanup(&rkcif->media_dev);
+	media_device_shared_leave(rkcif->media_dev, rkcif->dev);
 	pm_runtime_disable(&pdev->dev);
 }
 
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
index 414a9980cf2e..cd791186f224 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
@@ -6,6 +6,7 @@
  * Copyright (C) 2025 Collabora, Ltd.
  */
 
+#include <media/mc-shared-graph.h>
 #include <media/v4l2-common.h>
 #include <media/v4l2-fwnode.h>
 #include <media/v4l2-mc.h>
@@ -399,6 +400,15 @@ int rkcif_interface_register(struct rkcif_device *rkcif,
 	if (ret)
 		goto err_subdev_unregister;
 
+	ret = media_device_shared_join_link_source(interface->rkcif->media_dev,
+						   interface->rkcif->dev,
+						   &interface->sd.entity,
+						   RKCIF_IF_PAD_SRC,
+						   0);
+	if (ret)
+		goto err_subdev_unregister;
+
+
 	return 0;
 
 err_subdev_unregister:
-- 
2.47.2



^ permalink raw reply related

* [RFC PATCH 2/6] arm64: dts: rockchip: rk3588s-base: Connect vicap and isps
From: Paul Elder @ 2026-06-19  5:26 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: Paul Elder, michael.riesch, xuhf, stefan.klug, kieran.bingham,
	dan.scally, jacopo.mondi, linux-media, linux-arm-kernel,
	linux-rockchip, linux-kernel, hverkuil+cisco, nicolas.dufresne,
	ribalda, sakari.ailus, Heiko Stuebner
In-Reply-To: <20260619052637.1110672-1-paul.elder@ideasonboard.com>

Add a connection between VICAP and the two ISPs.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
---
 arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 30 +++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
index 55c5c603c1e3..f36267720910 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
@@ -1493,10 +1493,16 @@ vicap_mipi5: port@6 {
 
 			vicap_toisp0: port@10 {
 				reg = <16>;
+				vicap_toisp0_ep: endpoint {
+					remote-endpoint = <&isp0_tovicap>;
+				};
 			};
 
 			vicap_toisp1: port@11 {
 				reg = <17>;
+				vicap_toisp1_ep: endpoint {
+					remote-endpoint = <&isp1_tovicap>;
+				};
 			};
 		};
 	};
@@ -3551,6 +3557,18 @@ isp0: isp@fdcb0000 {
 		power-domains = <&power RK3588_PD_VI>;
 		iommus = <&isp0_mmu>;
 		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			port@0 {
+				reg = <0>;
+				isp0_tovicap: endpoint {
+					remote-endpoint = <&vicap_toisp0_ep>;
+				};
+			};
+		};
 	};
 
 	isp0_mmu: iommu@fdcb7f00 {
@@ -3580,6 +3598,18 @@ isp1: isp@fdcc0000 {
 		power-domains = <&power RK3588_PD_ISP1>;
 		iommus = <&isp1_mmu>;
 		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			port@0 {
+				reg = <0>;
+				isp1_tovicap: endpoint {
+					remote-endpoint = <&vicap_toisp1_ep>;
+				};
+			};
+		};
 	};
 
 	isp1_mmu: iommu@fdcc7f00 {
-- 
2.47.2



^ permalink raw reply related

* [RFC PATCH 1/6] media: mc: Implement shared media graph
From: Paul Elder @ 2026-06-19  5:26 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: Paul Elder, michael.riesch, xuhf, stefan.klug, kieran.bingham,
	dan.scally, jacopo.mondi, linux-media, linux-arm-kernel,
	linux-rockchip, linux-kernel, hverkuil+cisco, nicolas.dufresne,
	ribalda, sakari.ailus
In-Reply-To: <20260619052637.1110672-1-paul.elder@ideasonboard.com>

Currently, a media graph contains a main device whose driver is
responsible for creating the media device. We have however recently run
into devices that have multiple devices that can quality as a main
device. Examples are the RK3588 which has a VICAP and two ISP
instances, and another example is the i.MX8MP which has an ISI and two
ISP instances. As there is currently no way to reconcile who the main
device is in the media device, these setups simple cannot be used
simultaneously.

This patch extends the media controller API with a "shared media graph"
framework. This allows drivers to share a media device, thus enabling
the setups mentioned above. Instead of owning and creating a media
device, drivers can join-or-create a shared media device via the shared
media graph API. The matching is done automatically based on the
detected endpoints in the device tree.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
---
 drivers/media/mc/Makefile          |   2 +-
 drivers/media/mc/mc-shared-graph.c | 335 +++++++++++++++++++++++++++++
 include/media/mc-shared-graph.h    |  92 ++++++++
 3 files changed, 428 insertions(+), 1 deletion(-)
 create mode 100644 drivers/media/mc/mc-shared-graph.c
 create mode 100644 include/media/mc-shared-graph.h

diff --git a/drivers/media/mc/Makefile b/drivers/media/mc/Makefile
index 2b7af42ba59c..1d502fdc52ad 100644
--- a/drivers/media/mc/Makefile
+++ b/drivers/media/mc/Makefile
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 mc-objs	:= mc-device.o mc-devnode.o mc-entity.o \
-	   mc-request.o
+	   mc-request.o mc-shared-graph.o
 
 ifneq ($(CONFIG_USB),)
 	mc-objs += mc-dev-allocator.o
diff --git a/drivers/media/mc/mc-shared-graph.c b/drivers/media/mc/mc-shared-graph.c
new file mode 100644
index 000000000000..c4067e5b861d
--- /dev/null
+++ b/drivers/media/mc/mc-shared-graph.c
@@ -0,0 +1,335 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mc-shared-graph.c - Media Controller Shared Graph API
+ *
+ * Copyright (c) 2026 Paul Elder <paul.elder@ideasonboard.com>
+ */
+
+/*
+ * This file adds the Media Controller Shared Graph API. This allows drivers
+ * to create shared media graphs or join existing media graphs from other
+ * drivers, so that they can all be in the same media graph. This allows us to
+ * have more complex media graphs chaining more complex hardware together,
+ * instead of simple async subdevs.
+ */
+
+#include <linux/device.h>
+#include <linux/fwnode.h>
+#include <linux/kref.h>
+#include <linux/property.h>
+
+#include <media/media-device.h>
+
+#include <media/mc-shared-graph.h>
+
+static LIST_HEAD(media_device_shared_list);
+static DEFINE_MUTEX(media_device_shared_lock);
+
+struct media_device_shared_member {
+	struct device *dev;
+	struct fwnode_handle *fwnode;
+	struct list_head list;
+};
+
+struct media_device_shared_link {
+	struct media_entity *source;
+	u16 source_pad;
+	struct media_entity *sink;
+	u16 sink_pad;
+	u32 flags;
+	struct list_head list;
+};
+
+// TODO figure out locking for when multiple drivers touch the media graph;
+// maybe macros for shared versions?
+struct media_device_shared {
+	struct media_device mdev;
+	struct list_head members;
+	struct list_head links;
+
+	struct list_head list;
+	struct kref refcount;
+
+	struct device *removed_device;
+};
+
+static inline struct media_device_shared *
+to_media_device_shared(struct media_device *mdev)
+{
+	return container_of(mdev, struct media_device_shared, mdev);
+}
+
+static void media_device_shared_release(struct kref *kref)
+{
+	struct media_device_shared *mds =
+		container_of(kref, struct media_device_shared, refcount);
+
+	dev_dbg(mds->removed_device, "%s: releasing Media Device\n", __func__);
+
+	mutex_lock(&media_device_shared_lock);
+
+	media_device_unregister(&mds->mdev);
+	media_device_cleanup(&mds->mdev);
+
+	list_del(&mds->list);
+	mutex_unlock(&media_device_shared_lock);
+
+	kfree(mds);
+}
+
+/* Callers should hold media_device_shared_lock when calling this function */
+static bool __media_device_shared_find_match(struct media_device_shared *mds,
+					     struct fwnode_handle *fwnode)
+{
+	struct media_device_shared_member *member;
+	struct fwnode_handle *ep;
+	struct fwnode_handle *remote_ep;
+	bool match = false;
+
+	// TODO: parse the device tree endpoints graph instead of finding just the
+	// first-level neighbours
+	fwnode_graph_for_each_endpoint(fwnode, ep) {
+		list_for_each_entry(member, &mds->members, list) {
+			remote_ep = fwnode_graph_get_remote_port_parent(ep);
+			match = (member->fwnode == remote_ep);
+			fwnode_handle_put(remote_ep);
+
+			if (!match)
+				continue;
+
+			goto match_complete;
+		}
+	}
+
+match_complete:
+	fwnode_handle_put(ep);
+	return match;
+}
+
+/* Callers should hold media_device_shared_lock when calling this function */
+static struct media_device *__media_device_shared_get(struct device *dev)
+{
+	struct media_device_shared *mds;
+	struct media_device_shared_member *member;
+	struct fwnode_handle *fwnode = dev_fwnode(dev);
+	bool ret;
+
+	dev_dbg(dev, "%s: searching for media device for %pfwf", __func__, fwnode);
+
+	list_for_each_entry(mds, &media_device_shared_list, list) {
+		ret = __media_device_shared_find_match(mds, fwnode);
+		if (ret)
+			break;
+	}
+
+	if (!ret)
+		return NULL;
+
+	member = kzalloc_obj(*member);
+	if (!member)
+		return NULL;
+
+	member->dev = dev;
+	member->fwnode = fwnode;
+	list_add_tail(&member->list, &mds->members);
+	kref_get(&mds->refcount);
+
+	dev_dbg(dev, "%s: %pfwf joined media device of %pfwf",
+		__func__, fwnode,
+		list_first_entry(&mds->members, struct media_device_shared_member, list)->fwnode);
+
+	return &mds->mdev;
+}
+
+/* Callers should hold media_device_shared_lock when calling this function */
+static struct media_device *__media_device_shared_create(struct device *dev)
+{
+	struct media_device_shared *mds;
+	struct media_device_shared_member *member;
+	struct fwnode_handle *fwnode = dev_fwnode(dev);
+	int ret;
+
+	mds = kzalloc_obj(*mds);
+	if (!mds)
+		return NULL;
+
+	member = kzalloc_obj(*member);
+	if (!member)
+		goto err_free_mds;
+
+	media_device_init(&mds->mdev);
+
+	ret = media_device_register(&mds->mdev);
+	if (ret)
+		goto err_free_member;
+
+	INIT_LIST_HEAD(&mds->members);
+	member->dev = dev;
+	member->fwnode = fwnode;
+	list_add_tail(&member->list, &mds->members);
+
+	INIT_LIST_HEAD(&mds->links);
+
+	kref_init(&mds->refcount);
+	list_add_tail(&mds->list, &media_device_shared_list);
+
+	// TODO figure out how to reconcile this with multiple members
+	mds->mdev.dev = dev;
+
+	devv_dbg(dev, "%s: Allocated media device with %pfwf at %p\n",
+		 __func__, fwnode, &mds->mdev);
+	return &mds->mdev;
+
+err_free_member:
+	kfree(member);
+err_free_mds:
+	kfree(mds);
+	return NULL;
+}
+
+// TODO figure out how to resolve the identifiers (model, driver name, etc);
+// atm it's racy and whoever gets it last wins
+struct media_device *media_device_shared_join(struct device *dev)
+{
+	struct media_device *mdev;
+
+	mutex_lock(&media_device_shared_lock);
+
+	mdev = __media_device_shared_get(dev);
+	if (!!mdev) {
+		dev_dbg(dev, "%s: found media device for %pfwf", __func__, dev_fwnode(dev));
+		mutex_unlock(&media_device_shared_lock);
+		return mdev;
+	}
+
+	mdev = __media_device_shared_create(dev);
+	if (!mdev) {
+		dev_warn(dev, "%s: failed to create media device for %pfwf", __func__, dev_fwnode(dev));
+		mutex_unlock(&media_device_shared_lock);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	dev_dbg(dev, "%s: created media device for %pfwf", __func__, dev_fwnode(dev));
+	mutex_unlock(&media_device_shared_lock);
+	return mdev;
+}
+EXPORT_SYMBOL_GPL(media_device_shared_join);
+
+void media_device_shared_leave(struct media_device *mdev, struct device *dev)
+{
+	struct media_device_shared *mds = to_media_device_shared(mdev);
+	struct media_device_shared_member *member;
+	struct media_device_shared_member *member_tmp;
+	bool removed = false;
+
+	mutex_lock(&media_device_shared_lock);
+
+	list_for_each_entry_safe(member, member_tmp, &mds->members, list) {
+		if (member->dev == dev) {
+			list_del(&member->list);
+			kfree(member);
+			removed = true;
+		}
+	}
+
+	if (!removed)
+		dev_err(dev, "%s: %pfwf trying to leave from graph in which not a member",
+			__func__, dev_fwnode(dev));
+
+	mds->removed_device = dev;
+	mutex_unlock(&media_device_shared_lock);
+	kref_put(&mds->refcount, media_device_shared_release);
+}
+EXPORT_SYMBOL_GPL(media_device_shared_leave);
+
+int media_device_shared_join_link_source(struct media_device *mdev,
+					 struct device *dev,
+					 struct media_entity *source,
+					 u16 source_pad, u32 flags)
+{
+	struct media_device_shared *mds = to_media_device_shared(mdev);
+	struct media_device_shared_link *link;
+	struct media_device_shared_link *link_tmp;
+	int ret = 0;
+
+	mutex_lock(&media_device_shared_lock);
+
+	/*
+	 * TODO Figure out flags. Should we use greatest common denominator? Or
+	 * prioritize sink? Or whoever wins the race? For now we just take the flags
+	 * from the sink.
+	 *
+	 * TODO Figure out how to actually do the matching. For now we just match
+	 * whoever comes in first. This works with the simple example we're running
+	 * with now (rkcif + one rkisp2) but with setups with multiple copies of
+	 * hardware this will cause problems, like with rkcif + two rkisp2 and
+	 * imx8-isi + two rkisp1.
+	 */
+	list_for_each_entry_safe(link, link_tmp, &mds->links, list) {
+		if (link->sink) {
+			ret = media_create_pad_link(source, source_pad,
+						    link->sink, link->sink_pad,
+						    link->flags);
+			list_del(&link->list);
+			kfree(link);
+			goto exit_join_link_source;
+		}
+	}
+
+	link = kzalloc_obj(*link);
+	if (!link) {
+		ret = -ENOMEM;
+		goto exit_join_link_source;
+	}
+
+	link->source = source;
+	link->source_pad = source_pad;
+	link->flags = flags;
+	list_add_tail(&link->list, &mds->links);
+
+exit_join_link_source:
+	mutex_unlock(&media_device_shared_lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(media_device_shared_join_link_source);
+
+// TODO deduplicate from above
+int media_device_shared_join_link_sink(struct media_device *mdev,
+				       struct device *dev,
+				       struct media_entity *sink,
+				       u16 sink_pad, u32 flags)
+{
+	struct media_device_shared *mds = to_media_device_shared(mdev);
+	struct media_device_shared_link *link;
+	struct media_device_shared_link *link_tmp;
+	int ret = 0;
+
+	mutex_lock(&media_device_shared_lock);
+
+	list_for_each_entry_safe(link, link_tmp, &mds->links, list) {
+		if (link->source) {
+			ret = media_create_pad_link(link->source, link->source_pad,
+						    sink, sink_pad,
+						    flags);
+			list_del(&link->list);
+			kfree(link);
+			goto exit_join_link_sink;
+		}
+	}
+
+	link = kzalloc_obj(*link);
+	if (!link) {
+		ret = -ENOMEM;
+		goto exit_join_link_sink;
+	}
+
+	link->sink = sink;
+	link->sink_pad = sink_pad;
+	link->flags = flags;
+	list_add_tail(&link->list, &mds->links);
+
+exit_join_link_sink:
+	mutex_unlock(&media_device_shared_lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(media_device_shared_join_link_sink);
diff --git a/include/media/mc-shared-graph.h b/include/media/mc-shared-graph.h
new file mode 100644
index 000000000000..487325163f84
--- /dev/null
+++ b/include/media/mc-shared-graph.h
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mc-shared-graph.h - Media Controller Shared Graph API
+ *
+ * Copyright (c) 2026 Paul Elder <paul.elder@ideasonboard.com>
+ */
+
+/*
+ * This file adds the Media Controller Shared Graph API. This allows drivers
+ * to create shared media graphs or join existing media graphs from other
+ * drivers, so that they can all be in the same media graph. This allows us to
+ * have more complex media graphs chaining more complex hardware together,
+ * instead of simple async subdevs.
+ */
+
+#include <linux/types.h>
+
+#ifndef _MEDIA_SHARED_GRAPH_H
+#define _MEDIA_SHARED_GRAPH_H
+
+struct device;
+struct media_device;
+struct media_entity;
+
+#if defined(CONFIG_MEDIA_CONTROLLER)
+/**
+ * media_device_shared_join() - Join or create a new shared media device
+ *
+ * @dev:		struct &device pointer
+ *
+ * This is the entrance function for a device to join or create a new shared
+ * media device. It searches for an existing shared media device based on the
+ * neighbours in the device's device tree ports node. If found, then this
+ * functions returns the existing shared media device and joins it. If one is
+ * not found then one is created and initialized and returned.
+ */
+struct media_device *media_device_shared_join(struct device *dev);
+
+/**
+ * media_device_shared_leave() - Leave the shared media device.
+ *
+ * @mdev:		struct &media_device pointer
+ * @dev:		struct &device pointer
+ *
+ * This function makes the device leave the shared media device. When all
+ * members have left the media device it will be freed.
+ */
+void media_device_shared_leave(struct media_device *mdev, struct device *dev);
+
+/**
+ * media_device_shared_join_link_source() - Register a link source in the shared media device
+ *
+ * @mdev: The struct &media_device pointer that is part of a shared media device
+ * @dev: struct &device pointer
+ * @source: The link source
+ * @source_pad: The pad
+ * @flags: The flags
+ *
+ * This function registers with the shared media device the source part of a
+ * link. When the shared media device receives the matching sink part of a link
+ * via media_device_shared_join_link_sink() then the link will be fully created.
+ */
+int media_device_shared_join_link_source(struct media_device *mdev,
+					 struct device *dev,
+					 struct media_entity *source,
+					 u16 source_pad, u32 flags);
+
+/**
+ * media_device_shared_join_link_sink() - Register a link sink in the shared media device
+ *
+ * Same as media_device_shared_join_link_source() but for sink instead of
+ * source.
+ */
+int media_device_shared_join_link_sink(struct media_device *mdev,
+				       struct device *dev,
+				       struct media_entity *sink,
+				       u16 sink_pad, u32 flags);
+#else
+static inline struct media_device *media_device_shared_join(struct device *dev)
+{ return NULL; }
+static inline void media_device_shared_leave(struct media_device *mdev,
+					     struct device *dev) { }
+static inline int media_device_shared_join_link_source(struct media_device *mdev,
+						       struct device *dev,
+						       struct media_entity *source,
+						       u16 source_pad, u32 flags) { }
+static inline int media_device_shared_join_link_sink(struct media_device *mdev,
+						     struct device *dev,
+						     struct media_entity *sink,
+						     u16 sink_pad, u32 flags) { }
+#endif /* CONFIG_MEDIA_CONTROLLER */
+#endif /* _MEDIA_DEV_SHARED_GRAPH_H */
-- 
2.47.2



^ permalink raw reply related

* [RFC PATCH 0/6] Add Shared Media Graph API
From: Paul Elder @ 2026-06-19  5:26 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: Paul Elder, michael.riesch, xuhf, stefan.klug, kieran.bingham,
	dan.scally, jacopo.mondi, linux-media, linux-arm-kernel,
	linux-rockchip, linux-kernel, hverkuil+cisco, nicolas.dufresne,
	ribalda, sakari.ailus

Hello everyone,

This patch series extends the media controller API with a Shared Media
Graph framework.

We have started to run into platforms where there are multiple media
drivers that ought to be participating in the same media graph, but it's
undecidable who should own the media device.

One example of this is the RK3588 which has a capture interface (VICAP,
handled by rkcif) and two ISP instances (handled by rkisp2). Another
example is the i.MX8MP which has an image sensor interface (ISI, handled
by imx8-isi) and two ISP instances (handled by rkisp1). Since they
cannot all be in the same media graph at the moment, on the RK3588 we
can only support memory-to-memory mode between the VICAP and ISP, while
in the i.MX8MP we cannot use the ISI and ISP simultaneously.

The drivers for these ISPs and capture interfaces also support hardware
where only the ISP or only the capture interface is present (eg. imx8mn
only has ISI; rk3399 only has rkisp1). This means that we cannot simply
make one of them always the main device of the media graph.

This topic was discussed over a lunch at Embedded Recipes, and the
conclusion was that the best solution is to add a mechanism where
drivers could join an existing media device, or create one if none were
available. This series implements such a mechanism, which I have
tentatively named Shared Media Graph.


Patch 1 implements the Shared Media Graph API. The rest of the series
are a functioning example implementation on the aforementioned RK3588
setup.

This framework allows the aforementioned setups to work. For this
series, I specfically targeted the RK3588 setup, and I am able to have
both the VICAP and the ISP in the same media graph [0] [5]. I can switch
between inline mode and memory-to-memory mode by enabling/disabling the
link between the rkcif and rkisp2, and can successfully capture in both
modes.

There are still a lot of rough edges in the framework that need to be
discussed and sorted out (aka RFC); see all of the TODOs in patch 1 for
details. Actually everything that I want comments on are in the TODOs
for patch 1. The rest of the series are less RFC and more "here's an
example of how it would work".


This patch series is based on linux-media/next (06cb687a5132) [1] and
depends on:
- v5 of "media: rockchip: rkcif: add support for rk3588 vicap" [2]
- rfc of "media: rockchip: rkisp2: Add driver for ISP on Rk3588" [3]

I have a branch here [4].

[0] https://www.pasteboard.co/i-p5Z10LMHJ5.png
[1] https://gitlab.freedesktop.org/linux-media/media-committers/-/tree/next
[2] https://lore.kernel.org/all/20260522-rk3588-vicap-v5-3-d1d1f5265c56@collabora.com/
[3] https://lore.kernel.org/all/20260424175853.638202-1-paul.elder@ideasonboard.com/
[4] https://gitlab.freedesktop.org/linux-media/users/epaul/-/tree/epaul/v7.1-rc1/rk3588/shared-mc/upstream

[5] media-ctl -p and dot output for when the pasteboard [0] expires:

Media controller API version 7.1.0

Media device information
------------------------
driver          rockchip-cif
model           rkisp2
serial          
bus info        platform:rkisp2
hw revision     0x1e
driver version  7.1.0

Device topology
- entity 9: rkcif-mipi2 (3 pads, 6 links, 2 routes)
            type V4L2 subdev subtype Unknown flags 0
            device node name /dev/v4l-subdev0
	routes:
		0/0 -> 1/0 [ACTIVE]
		0/0 -> 2/0 [ACTIVE]
	pad0: SINK,MUST_CONNECT
		[stream:0 fmt:SRGGB10_1X10/1920x1080 field:none]
		<- "dw-mipi-csi2rx fdd30000.csi":1 [ENABLED]
	pad1: SOURCE
		[stream:0 fmt:SRGGB10_1X10/1920x1080 field:none
		 crop.bounds:(0,0)/1920x1080
		 crop:(0,0)/1920x1080]
		-> "rkcif-mipi2-id0":0 []
		-> "rkcif-mipi2-id1":0 []
		-> "rkcif-mipi2-id2":0 []
		-> "rkcif-mipi2-id3":0 []
	pad2: SOURCE
		[stream:0 fmt:SRGGB10_1X10/1920x1080 field:none colorspace:raw xfer:none ycbcr:601 quantization:full-range
		 crop.bounds:(0,0)/1920x1080
		 crop:(0,0)/1920x1080]
		-> "rkisp2_isp":4 [ENABLED]

- entity 13: rkcif-mipi2-id0 (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video0
	pad0: SINK,MUST_CONNECT
		<- "rkcif-mipi2":1 []

- entity 19: rkcif-mipi2-id1 (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video1
	pad0: SINK,MUST_CONNECT
		<- "rkcif-mipi2":1 []

- entity 25: rkcif-mipi2-id2 (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video2
	pad0: SINK,MUST_CONNECT
		<- "rkcif-mipi2":1 []

- entity 31: rkcif-mipi2-id3 (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video3
	pad0: SINK,MUST_CONNECT
		<- "rkcif-mipi2":1 []

- entity 49: dw-mipi-csi2rx fdd30000.csi (2 pads, 2 links, 1 route)
             type V4L2 subdev subtype Unknown flags 0
             device node name /dev/v4l-subdev1
	routes:
		0/0 -> 1/0 [ACTIVE]
	pad0: SINK,MUST_CONNECT
		[stream:0 fmt:SRGGB10_1X10/1920x1080 field:none]
		<- "imx219 4-0010":0 [ENABLED]
	pad1: SOURCE
		[stream:0 fmt:SRGGB10_1X10/1920x1080 field:none]
		-> "rkcif-mipi2":0 [ENABLED]

- entity 54: imx219 4-0010 (1 pad, 1 link, 0 routes)
             type V4L2 subdev subtype Sensor flags 0
             device node name /dev/v4l-subdev2
	pad0: SOURCE
		[stream:0 fmt:SRGGB10_1X10/1920x1080 field:none colorspace:raw xfer:none ycbcr:601 quantization:full-range
		 crop.bounds:(8,8)/3280x2464
		 crop:(688,700)/1920x1080]
		-> "dw-mipi-csi2rx fdd30000.csi":0 [ENABLED]

- entity 64: rkisp2_isp (5 pads, 8 links, 0 routes)
             type V4L2 subdev subtype Unknown flags 0
             device node name /dev/v4l-subdev3
	pad0: SINK
		[stream:0 fmt:SRGGB10_1X10/1920x1080 field:none colorspace:raw xfer:none ycbcr:601 quantization:full-range
		 crop.bounds:(0,0)/1920x1080
		 crop:(0,0)/1920x1080]
		<- "rkisp2_rawrd0":0 []
		<- "rkisp2_rawrd1":0 []
		<- "rkisp2_rawrd2":0 []
	pad1: SINK
		[stream:0 fmt:unknown/0x0]
		<- "rkisp2_params":0 [ENABLED,IMMUTABLE]
	pad2: SOURCE
		[stream:0 fmt:YUYV8_2X8/1920x1080 field:none colorspace:raw xfer:none ycbcr:601 quantization:lim-range
		 crop.bounds:(0,0)/1920x1080
		 crop:(0,0)/1920x1080]
		-> "rkisp2_mainpath":0 [ENABLED,IMMUTABLE]
		-> "rkisp2_selfpath":0 [ENABLED,IMMUTABLE]
	pad3: SOURCE
		[stream:0 fmt:unknown/0x0]
		-> "rkisp2_stats":0 [ENABLED,IMMUTABLE]
	pad4: SINK
		[stream:0 fmt:SRGGB10_1X10/1920x1080 field:none colorspace:raw xfer:none ycbcr:601 quantization:full-range
		 crop.bounds:(0,0)/1920x1080
		 crop:(0,0)/1920x1080]
		<- "rkcif-mipi2":2 [ENABLED]

- entity 70: rkisp2_mainpath (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video5
	pad0: SINK
		<- "rkisp2_isp":2 [ENABLED,IMMUTABLE]

- entity 74: rkisp2_selfpath (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video6
	pad0: SINK
		<- "rkisp2_isp":2 [ENABLED,IMMUTABLE]

- entity 78: rkisp2_rawrd0 (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video7
	pad0: SOURCE
		-> "rkisp2_isp":0 []

- entity 82: rkisp2_rawrd1 (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video8
	pad0: SOURCE
		-> "rkisp2_isp":0 []

- entity 86: rkisp2_rawrd2 (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video9
	pad0: SOURCE
		-> "rkisp2_isp":0 []

- entity 90: rkisp2_params (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video10
	pad0: SOURCE
		-> "rkisp2_isp":1 [ENABLED,IMMUTABLE]

- entity 94: rkisp2_stats (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video11
	pad0: SINK
		<- "rkisp2_isp":3 [ENABLED,IMMUTABLE]


digraph board {
	rankdir=TB
	n00000009 [label="{{<port0> 0} | rkcif-mipi2\n/dev/v4l-subdev0 | {<port1> 1 | <port2> 2}}", shape=Mrecord, style=filled, fillcolor=green]
	n00000009:port1 -> n0000000d [style=dashed]
	n00000009:port1 -> n00000013 [style=dashed]
	n00000009:port1 -> n00000019 [style=dashed]
	n00000009:port1 -> n0000001f [style=dashed]
	n00000009:port2 -> n00000040:port4
	n0000000d [label="rkcif-mipi2-id0\n/dev/video0", shape=box, style=filled, fillcolor=yellow]
	n00000013 [label="rkcif-mipi2-id1\n/dev/video1", shape=box, style=filled, fillcolor=yellow]
	n00000019 [label="rkcif-mipi2-id2\n/dev/video2", shape=box, style=filled, fillcolor=yellow]
	n0000001f [label="rkcif-mipi2-id3\n/dev/video3", shape=box, style=filled, fillcolor=yellow]
	n00000031 [label="{{<port0> 0} | dw-mipi-csi2rx fdd30000.csi\n/dev/v4l-subdev1 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
	n00000031:port1 -> n00000009:port0
	n00000036 [label="{{} | imx219 4-0010\n/dev/v4l-subdev2 | {<port0> 0}}", shape=Mrecord, style=filled, fillcolor=green]
	n00000036:port0 -> n00000031:port0
	n00000040 [label="{{<port0> 0 | <port1> 1 | <port4> 4} | rkisp2_isp\n/dev/v4l-subdev3 | {<port2> 2 | <port3> 3}}", shape=Mrecord, style=filled, fillcolor=green]
	n00000040:port2 -> n00000046 [style=bold]
	n00000040:port2 -> n0000004a [style=bold]
	n00000040:port3 -> n0000005e [style=bold]
	n00000046 [label="rkisp2_mainpath\n/dev/video5", shape=box, style=filled, fillcolor=yellow]
	n0000004a [label="rkisp2_selfpath\n/dev/video6", shape=box, style=filled, fillcolor=yellow]
	n0000004e [label="rkisp2_rawrd0\n/dev/video7", shape=box, style=filled, fillcolor=yellow]
	n0000004e -> n00000040:port0 [style=dashed]
	n00000052 [label="rkisp2_rawrd1\n/dev/video8", shape=box, style=filled, fillcolor=yellow]
	n00000052 -> n00000040:port0 [style=dashed]
	n00000056 [label="rkisp2_rawrd2\n/dev/video9", shape=box, style=filled, fillcolor=yellow]
	n00000056 -> n00000040:port0 [style=dashed]
	n0000005a [label="rkisp2_params\n/dev/video10", shape=box, style=filled, fillcolor=yellow]
	n0000005a -> n00000040:port1 [style=bold]
	n0000005e [label="rkisp2_stats\n/dev/video11", shape=box, style=filled, fillcolor=yellow]
}

Paul Elder (6):
  media: mc: Implement shared media graph
  arm64: dts: rockchip: rk3588s-base: Connect vicap and isps
  media: rkcif: Use shared media graph
  media: rkisp2: Use shared media graph
  media: rkcif: Implement inline mode
  media: rkisp2: Implement inline mode

 arch/arm64/boot/dts/rockchip/rk3588-base.dtsi |  30 ++
 drivers/media/mc/Makefile                     |   2 +-
 drivers/media/mc/mc-shared-graph.c            | 335 ++++++++++++++++++
 .../rockchip/rkcif/rkcif-capture-dvp.c        |   2 +-
 .../rockchip/rkcif/rkcif-capture-mipi.c       |   3 +
 .../platform/rockchip/rkcif/rkcif-common.h    |  18 +-
 .../media/platform/rockchip/rkcif/rkcif-dev.c |  32 +-
 .../platform/rockchip/rkcif/rkcif-interface.c | 248 ++++++++++++-
 .../platform/rockchip/rkcif/rkcif-regs.h      |  49 +++
 .../platform/rockchip/rkcif/rkcif-stream.c    |   6 +-
 .../platform/rockchip/rkisp2/rkisp2-common.h  |   5 +-
 .../platform/rockchip/rkisp2/rkisp2-dev.c     |  46 +--
 .../platform/rockchip/rkisp2/rkisp2-isp.c     | 155 ++++++--
 include/media/mc-shared-graph.h               |  92 +++++
 14 files changed, 932 insertions(+), 91 deletions(-)
 create mode 100644 drivers/media/mc/mc-shared-graph.c
 create mode 100644 include/media/mc-shared-graph.h

-- 
2.47.2



^ permalink raw reply

* Re: [RFC PATCH 0/2] kasan: hw_tags: Add option to tag only at allocation time
From: Dev Jain @ 2026-06-19  5:17 UTC (permalink / raw)
  To: Ryan Roberts, ryabinin.a.a, akpm, corbet
  Cc: glider, andreyknvl, dvyukov, vincenzo.frascino, kasan-dev,
	linux-mm, linux-kernel, skhan, workflows, linux-doc,
	linux-arm-kernel, anshuman.khandual, kaleshsingh, 21cnbao, david,
	will, catalin.marinas
In-Reply-To: <dbc2800f-7880-486f-831c-ec9b6cedc005@arm.com>



On 18/06/26 7:18 pm, Ryan Roberts wrote:
> On 12/06/2026 05:44, Dev Jain wrote:
>> Introduce a boot option to tag only at allocation time of the objects. This
>> reduces KASAN MTE overhead, the tradeoff being reduced ability of
>> catching bugs.
>>
>> Now, when a memory object will be freed, it will retain the random tag it
>> had at allocation time. This compromises on catching UAF bugs, till the
>> time the object is not reallocated, at which point it will have a new
>> random tag.
>>
>> Hence, not catching "use-after-free-before-reallocation" and not catching
>> "double-free" will be the compromise for reduced KASAN overhead.
> 
> Does standard KASAN with HW_TAGS really detect double-free? How does it do that?
> I could imagine it testing the tags of memory being freed to see if they are set
> to the poison tag, but that would lead to false positives for the GFP_SKIP_KASAN
> case, surely?

Should have mentioned, the double-free check is only for slab objects, see
__kasan_slab_pre_free. So we won't be able to catch double-free here.

> 
> If I'm right, then the only downgrade this new mode causes is that if
> freed-but-not-yet-reallocated memory is accessed via it's dangling pointer, then
> that bad access is not detected. I think that would be benign in all the cases I
> can think of, so while it would be a problem for a debugging use case, it would
> unlikely be a problem for security enforcement?

Okay so you are saying that we won't catch the bug, but there is no security problem
because the dangling pointer is accessing memory which isn't in use by anyone else.


> 
> Thanks,
> Ryan
> 
> 
>>
>> This is an RFC because we are not clear about the performance benefit.
>>
>> Android folks, please help with testing!
>>
>> ---
>> Applies on Linus master (9716c086c8e8).
>>
>> Dev Jain (2):
>>   kasan: hw_tags: Use KASAN_PAGE_REDZONE for vmalloc redzoning
>>   kasan: hw_tags: Add boot option to elide free time poisoning
>>
>>  Documentation/dev-tools/kasan.rst |  4 +++
>>  mm/kasan/hw_tags.c                | 45 +++++++++++++++++++++++++++++--
>>  mm/kasan/kasan.h                  | 23 +++++++++++++++-
>>  3 files changed, 69 insertions(+), 3 deletions(-)
>>
> 



^ permalink raw reply

* [PATCH 5/5] dt-bindings: dma: sun50i-a64-dma: Update device tree bindings documentation for A733
From: Yuanshen Cao @ 2026-06-19  4:53 UTC (permalink / raw)
  To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
	Samuel Holland, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Maxime Ripard
  Cc: dmaengine, linux-arm-kernel, linux-sunxi, linux-kernel,
	devicetree, Yuanshen Cao
In-Reply-To: <20260619-sun60i-a733-dma-v1-0-da4b649fc72a@gmail.com>

To complete the support for the A733 DMA controller, added
`allwinner,sun60i-a733-dma` to the list of compatible strings for
`allwinner,sun50i-a64-dma` dt-binding documentations..

Signed-off-by: Yuanshen Cao <alex.caoys@gmail.com>
---
 Documentation/devicetree/bindings/dma/allwinner,sun50i-a64-dma.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/dma/allwinner,sun50i-a64-dma.yaml b/Documentation/devicetree/bindings/dma/allwinner,sun50i-a64-dma.yaml
index c3e14eb6cfff..1cc3304b7414 100644
--- a/Documentation/devicetree/bindings/dma/allwinner,sun50i-a64-dma.yaml
+++ b/Documentation/devicetree/bindings/dma/allwinner,sun50i-a64-dma.yaml
@@ -25,6 +25,7 @@ properties:
           - allwinner,sun50i-a64-dma
           - allwinner,sun50i-a100-dma
           - allwinner,sun50i-h6-dma
+          - allwinner,sun60i-a733-dma
       - items:
           - const: allwinner,sun8i-r40-dma
           - const: allwinner,sun50i-a64-dma
@@ -70,6 +71,7 @@ if:
           - allwinner,sun20i-d1-dma
           - allwinner,sun50i-a100-dma
           - allwinner,sun50i-h6-dma
+          - allwinner,sun60i-a733-dma
 
 then:
   properties:

-- 
2.54.0



^ permalink raw reply related

* [PATCH 4/5] dmaengine: sun6i-dma: Implement support for Allwinner A733 DMA controller
From: Yuanshen Cao @ 2026-06-19  4:53 UTC (permalink / raw)
  To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
	Samuel Holland, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Maxime Ripard
  Cc: dmaengine, linux-arm-kernel, linux-sunxi, linux-kernel,
	devicetree, Yuanshen Cao
In-Reply-To: <20260619-sun60i-a733-dma-v1-0-da4b649fc72a@gmail.com>

This patch implements the actual support for the Allwinner A733 DMA
controller. It defines the new register offsets and bitfield mappings
required for the A733, which slightly differs from the older `sun6i`
series.

Changes:
- New register macros for A733 interrupt enable `DMA_IRQ_EN_A733` and
  status `DMA_IRQ_STAT_A733`.
- New `SRC_HIGH_ADDR_32G` and `DST_HIGH_ADDR_32G` macro to handle the
  32G high-address field in the LLI.
- Implemented `sun6i_dma_set_addr_a733` and A733-specific interrupt
  register accessors.
- Added `sun60i_a733_dma_config`, which ties all the refactored
  functionality together for this specific hardware.

Signed-off-by: Yuanshen Cao <alex.caoys@gmail.com>
---
 drivers/dma/sun6i-dma.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)

diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index fb1c1a28744b..9585b4a9e00d 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -52,6 +52,15 @@
 #define SUNXI_H3_SECURE_REG		0x20
 #define SUNXI_H3_DMA_GATE		0x28
 #define SUNXI_H3_DMA_GATE_ENABLE	0x4
+
+/*
+ * sun60i specific registers
+ */
+#define DMA_IRQ_EN_A733(x)		((x) * 0x40 + 0x134)
+#define DMA_IRQ_STAT_A733(x)		((x) * 0x40 + 0x138)
+
+#define DMA_IRQ_CHAN_NR_A733		1
+
 /*
  * Channels specific registers
  */
@@ -100,6 +109,8 @@
  */
 #define SRC_HIGH_ADDR(x)		(((x) & 0x3U) << 16)
 #define DST_HIGH_ADDR(x)		(((x) & 0x3U) << 18)
+#define SRC_HIGH_ADDR_32G(x)	(((x) & 0x7U) << 11)
+#define DST_HIGH_ADDR_32G(x)	(((x) & 0x7U) << 15)
 
 /*
  * Various hardware related defines
@@ -257,6 +268,23 @@ static inline void sun6i_dma_dump_com_regs(struct sun6i_dma_dev *sdev)
 		DMA_STAT, readl(sdev->base + DMA_STAT));
 }
 
+static inline void sun6i_dma_dump_com_regs_a733(struct sun6i_dma_dev *sdev)
+{
+	int i;
+
+	for (i = 0; i < sdev->num_pchans / sdev->cfg->num_channels_per_reg; i++) {
+		dev_dbg(sdev->slave.dev, "Common register:\n"
+			"chan num %d\n"
+			"\tmask(%04x): 0x%08x\n"
+			"\tpend(%04x): 0x%08x\n"
+			"\tstats(%04x): 0x%08x\n",
+			i,
+			DMA_IRQ_EN_A733(i), readl(sdev->base + DMA_IRQ_EN_A733(i)),
+			DMA_IRQ_STAT_A733(i), readl(sdev->base + DMA_IRQ_STAT_A733(i)),
+			DMA_STAT, readl(sdev->base + DMA_STAT));
+	}
+}
+
 static inline void sun6i_dma_dump_chan_regs(struct sun6i_dma_dev *sdev,
 					    struct sun6i_pchan *pchan)
 {
@@ -360,20 +388,41 @@ static u32 sun6i_read_irq_en(struct sun6i_dma_dev *sdev, u32 chan_num)
 	return readl(sdev->base + DMA_IRQ_EN(chan_num));
 }
 
+static u32 sun6i_read_irq_en_a733(struct sun6i_dma_dev *sdev, u32 chan_num)
+{
+	return readl(sdev->base + DMA_IRQ_EN_A733(chan_num));
+}
+
 static void sun6i_write_irq_en(struct sun6i_dma_dev *sdev, u32 chan_num, u32 irq_val)
 {
 	writel(irq_val, sdev->base + DMA_IRQ_EN(chan_num));
 }
+
+static void sun6i_write_irq_en_a733(struct sun6i_dma_dev *sdev, u32 chan_num, u32 irq_val)
+{
+	writel(irq_val, sdev->base + DMA_IRQ_EN_A733(chan_num));
+}
+
 static u32 sun6i_read_irq_stat(struct sun6i_dma_dev *sdev, u32 chan_num)
 {
 	return readl(sdev->base + DMA_IRQ_STAT(chan_num));
 }
 
+static u32 sun6i_read_irq_stat_a733(struct sun6i_dma_dev *sdev, u32 chan_num)
+{
+	return readl(sdev->base + DMA_IRQ_STAT_A733(chan_num));
+}
+
 static void sun6i_write_irq_stat(struct sun6i_dma_dev *sdev, u32 chan_num, u32 status)
 {
 	writel(status, sdev->base + DMA_IRQ_STAT(chan_num));
 }
 
+static void sun6i_write_irq_stat_a733(struct sun6i_dma_dev *sdev, u32 chan_num, u32 status)
+{
+	writel(status, sdev->base + DMA_IRQ_STAT_A733(chan_num));
+}
+
 static size_t sun6i_get_chan_size(struct sun6i_pchan *pchan)
 {
 	struct sun6i_desc *txd = pchan->desc;
@@ -694,6 +743,17 @@ static inline void sun6i_dma_set_addr_a100(struct sun6i_dma_dev *sdev,
 				DST_HIGH_ADDR(upper_32_bits(dst));
 }
 
+static inline void sun6i_dma_set_addr_a733(struct sun6i_dma_dev *sdev,
+				      struct sun6i_dma_lli *v_lli,
+				      dma_addr_t src, dma_addr_t dst)
+{
+	v_lli->src = lower_32_bits(src);
+	v_lli->dst = lower_32_bits(dst);
+
+	v_lli->para |= SRC_HIGH_ADDR_32G(upper_32_bits(src)) |
+				DST_HIGH_ADDR_32G(upper_32_bits(dst));
+}
+
 static struct dma_async_tx_descriptor *sun6i_dma_prep_dma_memcpy(
 		struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
 		size_t len, unsigned long flags)
@@ -1352,6 +1412,33 @@ static struct sun6i_dma_config sun50i_h6_dma_cfg = {
 	.has_mbus_clk = true,
 };
 
+/*
+ * The A733 binding uses the number of dma channels from the
+ * device tree node.
+ */
+static struct sun6i_dma_config sun60i_a733_dma_cfg = {
+	.clock_autogate_enable = sun6i_enable_clock_autogate_h3,
+	.set_burst_length = sun6i_set_burst_length_h3,
+	.set_drq          = sun6i_set_drq_h6,
+	.set_mode         = sun6i_set_mode_h6,
+	.set_addr         = sun6i_dma_set_addr_a733,
+	.dump_com_regs    = sun6i_dma_dump_com_regs_a733,
+	.read_irq_en      = sun6i_read_irq_en_a733,
+	.write_irq_en     = sun6i_write_irq_en_a733,
+	.read_irq_stat    = sun6i_read_irq_stat_a733,
+	.write_irq_stat   = sun6i_write_irq_stat_a733,
+	.src_burst_lengths = BIT(1) | BIT(4) | BIT(8) | BIT(16),
+	.dst_burst_lengths = BIT(1) | BIT(4) | BIT(8) | BIT(16),
+	.src_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
+			     BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
+			     BIT(DMA_SLAVE_BUSWIDTH_4_BYTES),
+	.dst_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
+			     BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
+			     BIT(DMA_SLAVE_BUSWIDTH_4_BYTES),
+	.num_channels_per_reg = DMA_IRQ_CHAN_NR_A733,
+	.has_mbus_clk = true,
+};
+
 /*
  * The V3s have only 8 physical channels, a maximum DRQ port id of 23,
  * and a total of 24 usable source and destination endpoints.
@@ -1392,6 +1479,7 @@ static const struct of_device_id sun6i_dma_match[] = {
 	{ .compatible = "allwinner,sun50i-a64-dma", .data = &sun50i_a64_dma_cfg },
 	{ .compatible = "allwinner,sun50i-a100-dma", .data = &sun50i_a100_dma_cfg },
 	{ .compatible = "allwinner,sun50i-h6-dma", .data = &sun50i_h6_dma_cfg },
+	{ .compatible = "allwinner,sun60i-a733-dma", .data = &sun60i_a733_dma_cfg },
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, sun6i_dma_match);

-- 
2.54.0



^ permalink raw reply related

* [PATCH 2/5] dmaengine: sun6i-dma: Add set_addr function pointer for variable address widths
From: Yuanshen Cao @ 2026-06-19  4:53 UTC (permalink / raw)
  To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
	Samuel Holland, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Maxime Ripard
  Cc: dmaengine, linux-arm-kernel, linux-sunxi, linux-kernel,
	devicetree, Yuanshen Cao
In-Reply-To: <20260619-sun60i-a733-dma-v1-0-da4b649fc72a@gmail.com>

The A733 DMA controller supports higher address (up to 32G) compared to
previous generations. The existing `sun6i_dma_set_addr` function uses a
hardcoded logic for setting the high-address bits in the LLI parameters.

By moving `set_addr` into the `sun6i_dma_config` structure, we can
provide specialized implementations for different hardware. This allows
the A733 to use a version of `set_addr` that correctly handles its
specific `SRC_HIGH_ADDR_32G` and `DST_HIGH_ADDR_32G` in the `set_addr`
register later in the series.

Changes:
- Added `set_addr` function pointer to `struct sun6i_dma_config`.
- Refactored `sun6i_dma_set_addr` and introduced
  `sun6i_dma_set_addr_a100` (keeping the logic for high address
  support).
- Updated all existing configuration structs to include the new
  `set_addr` pointer.
- Removed `has_high_addr` since the logic is replaced by
  `sun6i_dma_set_addr_a100`.

Signed-off-by: Yuanshen Cao <alex.caoys@gmail.com>
---
 drivers/dma/sun6i-dma.c | 36 ++++++++++++++++++++++++++----------
 1 file changed, 26 insertions(+), 10 deletions(-)

diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index d92e702320d9..059455425e19 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -112,6 +112,7 @@
 
 /* forward declaration */
 struct sun6i_dma_dev;
+struct sun6i_dma_lli;
 
 /*
  * Hardware channels / ports representation
@@ -138,6 +139,8 @@ struct sun6i_dma_config {
 	void (*set_burst_length)(u32 *p_cfg, s8 src_burst, s8 dst_burst);
 	void (*set_drq)(u32 *p_cfg, s8 src_drq, s8 dst_drq);
 	void (*set_mode)(u32 *p_cfg, s8 src_mode, s8 dst_mode);
+	void (*set_addr)(struct sun6i_dma_dev *sdev, struct sun6i_dma_lli *v_lli,
+		dma_addr_t src, dma_addr_t dst);
 	void (*dump_com_regs)(struct sun6i_dma_dev *sdev);
 	u32 (*read_irq_en)(struct sun6i_dma_dev *sdev, u32 chan_num);
 	void (*write_irq_en)(struct sun6i_dma_dev *sdev, u32 chan_num, u32 irq_val);
@@ -147,7 +150,6 @@ struct sun6i_dma_config {
 	u32 dst_burst_lengths;
 	u32 src_addr_widths;
 	u32 dst_addr_widths;
-	bool has_high_addr;
 	bool has_mbus_clk;
 };
 
@@ -675,13 +677,20 @@ static int set_config(struct sun6i_dma_dev *sdev,
 static inline void sun6i_dma_set_addr(struct sun6i_dma_dev *sdev,
 				      struct sun6i_dma_lli *v_lli,
 				      dma_addr_t src, dma_addr_t dst)
+{
+	v_lli->src = lower_32_bits(src);
+	v_lli->dst = lower_32_bits(dst);
+}
+
+static inline void sun6i_dma_set_addr_a100(struct sun6i_dma_dev *sdev,
+				      struct sun6i_dma_lli *v_lli,
+				      dma_addr_t src, dma_addr_t dst)
 {
 	v_lli->src = lower_32_bits(src);
 	v_lli->dst = lower_32_bits(dst);
 
-	if (sdev->cfg->has_high_addr)
-		v_lli->para |= SRC_HIGH_ADDR(upper_32_bits(src)) |
-			       DST_HIGH_ADDR(upper_32_bits(dst));
+	v_lli->para |= SRC_HIGH_ADDR(upper_32_bits(src)) |
+				DST_HIGH_ADDR(upper_32_bits(dst));
 }
 
 static struct dma_async_tx_descriptor *sun6i_dma_prep_dma_memcpy(
@@ -714,7 +723,7 @@ static struct dma_async_tx_descriptor *sun6i_dma_prep_dma_memcpy(
 
 	v_lli->len = len;
 	v_lli->para = NORMAL_WAIT;
-	sun6i_dma_set_addr(sdev, v_lli, src, dest);
+	sdev->cfg->set_addr(sdev, v_lli, src, dest);
 
 	burst = convert_burst(8);
 	width = convert_buswidth(DMA_SLAVE_BUSWIDTH_4_BYTES);
@@ -773,7 +782,7 @@ static struct dma_async_tx_descriptor *sun6i_dma_prep_slave_sg(
 		v_lli->para = NORMAL_WAIT;
 
 		if (dir == DMA_MEM_TO_DEV) {
-			sun6i_dma_set_addr(sdev, v_lli,
+			sdev->cfg->set_addr(sdev, v_lli,
 					   sg_dma_address(sg),
 					   sconfig->dst_addr);
 			v_lli->cfg = lli_cfg;
@@ -787,7 +796,7 @@ static struct dma_async_tx_descriptor *sun6i_dma_prep_slave_sg(
 				sg_dma_len(sg), flags);
 
 		} else {
-			sun6i_dma_set_addr(sdev, v_lli,
+			sdev->cfg->set_addr(sdev, v_lli,
 					   sconfig->src_addr,
 					   sg_dma_address(sg));
 			v_lli->cfg = lli_cfg;
@@ -858,7 +867,7 @@ static struct dma_async_tx_descriptor *sun6i_dma_prep_dma_cyclic(
 		v_lli->para = NORMAL_WAIT;
 
 		if (dir == DMA_MEM_TO_DEV) {
-			sun6i_dma_set_addr(sdev, v_lli,
+			sdev->cfg->set_addr(sdev, v_lli,
 					   buf_addr + period_len * i,
 					   sconfig->dst_addr);
 			v_lli->cfg = lli_cfg;
@@ -870,7 +879,7 @@ static struct dma_async_tx_descriptor *sun6i_dma_prep_dma_cyclic(
 				&sconfig->dst_addr, &buf_addr,
 				buf_len, flags);
 		} else {
-			sun6i_dma_set_addr(sdev, v_lli,
+			sdev->cfg->set_addr(sdev, v_lli,
 					   sconfig->src_addr,
 					   buf_addr + period_len * i);
 			v_lli->cfg = lli_cfg;
@@ -1148,6 +1157,7 @@ static struct sun6i_dma_config sun6i_a31_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_a31,
 	.set_drq          = sun6i_set_drq_a31,
 	.set_mode         = sun6i_set_mode_a31,
+	.set_addr         = sun6i_dma_set_addr,
 	.dump_com_regs    = sun6i_dma_dump_com_regs,
 	.read_irq_en      = sun6i_read_irq_en,
 	.write_irq_en     = sun6i_write_irq_en,
@@ -1176,6 +1186,7 @@ static struct sun6i_dma_config sun8i_a23_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_a31,
 	.set_drq          = sun6i_set_drq_a31,
 	.set_mode         = sun6i_set_mode_a31,
+	.set_addr         = sun6i_dma_set_addr,
 	.dump_com_regs    = sun6i_dma_dump_com_regs,
 	.read_irq_en      = sun6i_read_irq_en,
 	.write_irq_en     = sun6i_write_irq_en,
@@ -1199,6 +1210,7 @@ static struct sun6i_dma_config sun8i_a83t_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_a31,
 	.set_drq          = sun6i_set_drq_a31,
 	.set_mode         = sun6i_set_mode_a31,
+	.set_addr         = sun6i_dma_set_addr,
 	.dump_com_regs    = sun6i_dma_dump_com_regs,
 	.read_irq_en      = sun6i_read_irq_en,
 	.write_irq_en     = sun6i_write_irq_en,
@@ -1229,6 +1241,7 @@ static struct sun6i_dma_config sun8i_h3_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_h3,
 	.set_drq          = sun6i_set_drq_a31,
 	.set_mode         = sun6i_set_mode_a31,
+	.set_addr         = sun6i_dma_set_addr,
 	.dump_com_regs    = sun6i_dma_dump_com_regs,
 	.read_irq_en      = sun6i_read_irq_en,
 	.write_irq_en     = sun6i_write_irq_en,
@@ -1255,6 +1268,7 @@ static struct sun6i_dma_config sun50i_a64_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_h3,
 	.set_drq          = sun6i_set_drq_a31,
 	.set_mode         = sun6i_set_mode_a31,
+	.set_addr         = sun6i_dma_set_addr,
 	.dump_com_regs    = sun6i_dma_dump_com_regs,
 	.read_irq_en      = sun6i_read_irq_en,
 	.write_irq_en     = sun6i_write_irq_en,
@@ -1281,6 +1295,7 @@ static struct sun6i_dma_config sun50i_a100_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_h3,
 	.set_drq          = sun6i_set_drq_h6,
 	.set_mode         = sun6i_set_mode_h6,
+	.set_addr         = sun6i_dma_set_addr_a100,
 	.dump_com_regs    = sun6i_dma_dump_com_regs,
 	.read_irq_en      = sun6i_read_irq_en,
 	.write_irq_en     = sun6i_write_irq_en,
@@ -1296,7 +1311,6 @@ static struct sun6i_dma_config sun50i_a100_dma_cfg = {
 			     BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) |
 			     BIT(DMA_SLAVE_BUSWIDTH_8_BYTES),
-	.has_high_addr = true,
 	.has_mbus_clk = true,
 };
 
@@ -1309,6 +1323,7 @@ static struct sun6i_dma_config sun50i_h6_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_h3,
 	.set_drq          = sun6i_set_drq_h6,
 	.set_mode         = sun6i_set_mode_h6,
+	.set_addr         = sun6i_dma_set_addr,
 	.dump_com_regs    = sun6i_dma_dump_com_regs,
 	.read_irq_en      = sun6i_read_irq_en,
 	.write_irq_en     = sun6i_write_irq_en,
@@ -1340,6 +1355,7 @@ static struct sun6i_dma_config sun8i_v3s_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_a31,
 	.set_drq          = sun6i_set_drq_a31,
 	.set_mode         = sun6i_set_mode_a31,
+	.set_addr         = sun6i_dma_set_addr,
 	.dump_com_regs    = sun6i_dma_dump_com_regs,
 	.read_irq_en      = sun6i_read_irq_en,
 	.write_irq_en     = sun6i_write_irq_en,

-- 
2.54.0



^ permalink raw reply related

* [PATCH 1/5] dmaengine: sun6i-dma: Refactor to support A733 interrupt and register handling
From: Yuanshen Cao @ 2026-06-19  4:53 UTC (permalink / raw)
  To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
	Samuel Holland, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Maxime Ripard
  Cc: dmaengine, linux-arm-kernel, linux-sunxi, linux-kernel,
	devicetree, Yuanshen Cao
In-Reply-To: <20260619-sun60i-a733-dma-v1-0-da4b649fc72a@gmail.com>

This patch is the first step in a refactoring effort to support the
Allwinner A733 DMA controller. Currently, the `sun6i-dma` driver has
several functions related to interrupt handling (reading/writing
interrupt enable and status registers) and register dumping that are
hardcoded.

To support the A733, which has different register layouts and interrupt
handling logic, these functions are being moved into the
`sun6i_dma_config` structure as function pointers. This allows the
driver to use a polymorphic approach where the specific implementation
is determined by the hardware configuration assigned during device
probing.

Changes:
- Added function pointers to `struct sun6i_dma_config` for:
    - `dump_com_regs`
    - `read_irq_en`
    - `write_irq_en`
    - `read_irq_stat`
    - `write_irq_stat`
- Implemented generic `sun6i_read/write_irq_*` functions for existing
  hardware.
- Updated existing `sun6i_dma_config` instances (A31, A23, H3, A64,
  A100, H6, V3S) to use these new function pointers.

Signed-off-by: Yuanshen Cao <alex.caoys@gmail.com>
---
 drivers/dma/sun6i-dma.c | 74 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 69 insertions(+), 5 deletions(-)

diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index a9a254dbf8cb..d92e702320d9 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -138,6 +138,11 @@ struct sun6i_dma_config {
 	void (*set_burst_length)(u32 *p_cfg, s8 src_burst, s8 dst_burst);
 	void (*set_drq)(u32 *p_cfg, s8 src_drq, s8 dst_drq);
 	void (*set_mode)(u32 *p_cfg, s8 src_mode, s8 dst_mode);
+	void (*dump_com_regs)(struct sun6i_dma_dev *sdev);
+	u32 (*read_irq_en)(struct sun6i_dma_dev *sdev, u32 chan_num);
+	void (*write_irq_en)(struct sun6i_dma_dev *sdev, u32 chan_num, u32 irq_val);
+	u32 (*read_irq_stat)(struct sun6i_dma_dev *sdev, u32 chan_num);
+	void (*write_irq_stat)(struct sun6i_dma_dev *sdev, u32 chan_num, u32 status);
 	u32 src_burst_lengths;
 	u32 dst_burst_lengths;
 	u32 src_addr_widths;
@@ -347,6 +352,25 @@ static void sun6i_set_mode_h6(u32 *p_cfg, s8 src_mode, s8 dst_mode)
 		  DMA_CHAN_CFG_DST_MODE_H6(dst_mode);
 }
 
+static u32 sun6i_read_irq_en(struct sun6i_dma_dev *sdev, u32 chan_num)
+{
+	return readl(sdev->base + DMA_IRQ_EN(chan_num));
+}
+
+static void sun6i_write_irq_en(struct sun6i_dma_dev *sdev, u32 chan_num, u32 irq_val)
+{
+	writel(irq_val, sdev->base + DMA_IRQ_EN(chan_num));
+}
+static u32 sun6i_read_irq_stat(struct sun6i_dma_dev *sdev, u32 chan_num)
+{
+	return readl(sdev->base + DMA_IRQ_STAT(chan_num));
+}
+
+static void sun6i_write_irq_stat(struct sun6i_dma_dev *sdev, u32 chan_num, u32 status)
+{
+	writel(status, sdev->base + DMA_IRQ_STAT(chan_num));
+}
+
 static size_t sun6i_get_chan_size(struct sun6i_pchan *pchan)
 {
 	struct sun6i_desc *txd = pchan->desc;
@@ -460,16 +484,16 @@ static int sun6i_dma_start_desc(struct sun6i_vchan *vchan)
 
 	vchan->irq_type = vchan->cyclic ? DMA_IRQ_PKG : DMA_IRQ_QUEUE;
 
-	irq_val = readl(sdev->base + DMA_IRQ_EN(irq_reg));
+	irq_val = sdev->cfg->read_irq_en(sdev, irq_reg);
 	irq_val &= ~((DMA_IRQ_HALF | DMA_IRQ_PKG | DMA_IRQ_QUEUE) <<
 			(irq_offset * DMA_IRQ_CHAN_WIDTH));
 	irq_val |= vchan->irq_type << (irq_offset * DMA_IRQ_CHAN_WIDTH);
-	writel(irq_val, sdev->base + DMA_IRQ_EN(irq_reg));
+	sdev->cfg->write_irq_en(sdev, irq_reg, irq_val);
 
 	writel(pchan->desc->p_lli, pchan->base + DMA_CHAN_LLI_ADDR);
 	writel(DMA_CHAN_ENABLE_START, pchan->base + DMA_CHAN_ENABLE);
 
-	sun6i_dma_dump_com_regs(sdev);
+	sdev->cfg->dump_com_regs(sdev);
 	sun6i_dma_dump_chan_regs(sdev, pchan);
 
 	return 0;
@@ -549,14 +573,14 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
 	u32 status;
 
 	for (i = 0; i < sdev->num_pchans / DMA_IRQ_CHAN_NR; i++) {
-		status = readl(sdev->base + DMA_IRQ_STAT(i));
+		status = sdev->cfg->read_irq_stat(sdev, i);
 		if (!status)
 			continue;
 
 		dev_dbg(sdev->slave.dev, "DMA irq status %s: 0x%x\n",
 			str_high_low(i), status);
 
-		writel(status, sdev->base + DMA_IRQ_STAT(i));
+		sdev->cfg->write_irq_stat(sdev, i, status);
 
 		for (j = 0; (j < DMA_IRQ_CHAN_NR) && status; j++) {
 			pchan = sdev->pchans + j;
@@ -1124,6 +1148,11 @@ static struct sun6i_dma_config sun6i_a31_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_a31,
 	.set_drq          = sun6i_set_drq_a31,
 	.set_mode         = sun6i_set_mode_a31,
+	.dump_com_regs    = sun6i_dma_dump_com_regs,
+	.read_irq_en      = sun6i_read_irq_en,
+	.write_irq_en     = sun6i_write_irq_en,
+	.read_irq_stat    = sun6i_read_irq_stat,
+	.write_irq_stat   = sun6i_write_irq_stat,
 	.src_burst_lengths = BIT(1) | BIT(8),
 	.dst_burst_lengths = BIT(1) | BIT(8),
 	.src_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
@@ -1147,6 +1176,11 @@ static struct sun6i_dma_config sun8i_a23_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_a31,
 	.set_drq          = sun6i_set_drq_a31,
 	.set_mode         = sun6i_set_mode_a31,
+	.dump_com_regs    = sun6i_dma_dump_com_regs,
+	.read_irq_en      = sun6i_read_irq_en,
+	.write_irq_en     = sun6i_write_irq_en,
+	.read_irq_stat    = sun6i_read_irq_stat,
+	.write_irq_stat   = sun6i_write_irq_stat,
 	.src_burst_lengths = BIT(1) | BIT(8),
 	.dst_burst_lengths = BIT(1) | BIT(8),
 	.src_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
@@ -1165,6 +1199,11 @@ static struct sun6i_dma_config sun8i_a83t_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_a31,
 	.set_drq          = sun6i_set_drq_a31,
 	.set_mode         = sun6i_set_mode_a31,
+	.dump_com_regs    = sun6i_dma_dump_com_regs,
+	.read_irq_en      = sun6i_read_irq_en,
+	.write_irq_en     = sun6i_write_irq_en,
+	.read_irq_stat    = sun6i_read_irq_stat,
+	.write_irq_stat   = sun6i_write_irq_stat,
 	.src_burst_lengths = BIT(1) | BIT(8),
 	.dst_burst_lengths = BIT(1) | BIT(8),
 	.src_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
@@ -1190,6 +1229,11 @@ static struct sun6i_dma_config sun8i_h3_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_h3,
 	.set_drq          = sun6i_set_drq_a31,
 	.set_mode         = sun6i_set_mode_a31,
+	.dump_com_regs    = sun6i_dma_dump_com_regs,
+	.read_irq_en      = sun6i_read_irq_en,
+	.write_irq_en     = sun6i_write_irq_en,
+	.read_irq_stat    = sun6i_read_irq_stat,
+	.write_irq_stat   = sun6i_write_irq_stat,
 	.src_burst_lengths = BIT(1) | BIT(4) | BIT(8) | BIT(16),
 	.dst_burst_lengths = BIT(1) | BIT(4) | BIT(8) | BIT(16),
 	.src_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
@@ -1211,6 +1255,11 @@ static struct sun6i_dma_config sun50i_a64_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_h3,
 	.set_drq          = sun6i_set_drq_a31,
 	.set_mode         = sun6i_set_mode_a31,
+	.dump_com_regs    = sun6i_dma_dump_com_regs,
+	.read_irq_en      = sun6i_read_irq_en,
+	.write_irq_en     = sun6i_write_irq_en,
+	.read_irq_stat    = sun6i_read_irq_stat,
+	.write_irq_stat   = sun6i_write_irq_stat,
 	.src_burst_lengths = BIT(1) | BIT(4) | BIT(8) | BIT(16),
 	.dst_burst_lengths = BIT(1) | BIT(4) | BIT(8) | BIT(16),
 	.src_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
@@ -1232,6 +1281,11 @@ static struct sun6i_dma_config sun50i_a100_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_h3,
 	.set_drq          = sun6i_set_drq_h6,
 	.set_mode         = sun6i_set_mode_h6,
+	.dump_com_regs    = sun6i_dma_dump_com_regs,
+	.read_irq_en      = sun6i_read_irq_en,
+	.write_irq_en     = sun6i_write_irq_en,
+	.read_irq_stat    = sun6i_read_irq_stat,
+	.write_irq_stat   = sun6i_write_irq_stat,
 	.src_burst_lengths = BIT(1) | BIT(4) | BIT(8) | BIT(16),
 	.dst_burst_lengths = BIT(1) | BIT(4) | BIT(8) | BIT(16),
 	.src_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
@@ -1255,6 +1309,11 @@ static struct sun6i_dma_config sun50i_h6_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_h3,
 	.set_drq          = sun6i_set_drq_h6,
 	.set_mode         = sun6i_set_mode_h6,
+	.dump_com_regs    = sun6i_dma_dump_com_regs,
+	.read_irq_en      = sun6i_read_irq_en,
+	.write_irq_en     = sun6i_write_irq_en,
+	.read_irq_stat    = sun6i_read_irq_stat,
+	.write_irq_stat   = sun6i_write_irq_stat,
 	.src_burst_lengths = BIT(1) | BIT(4) | BIT(8) | BIT(16),
 	.dst_burst_lengths = BIT(1) | BIT(4) | BIT(8) | BIT(16),
 	.src_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
@@ -1281,6 +1340,11 @@ static struct sun6i_dma_config sun8i_v3s_dma_cfg = {
 	.set_burst_length = sun6i_set_burst_length_a31,
 	.set_drq          = sun6i_set_drq_a31,
 	.set_mode         = sun6i_set_mode_a31,
+	.dump_com_regs    = sun6i_dma_dump_com_regs,
+	.read_irq_en      = sun6i_read_irq_en,
+	.write_irq_en     = sun6i_write_irq_en,
+	.read_irq_stat    = sun6i_read_irq_stat,
+	.write_irq_stat   = sun6i_write_irq_stat,
 	.src_burst_lengths = BIT(1) | BIT(8),
 	.dst_burst_lengths = BIT(1) | BIT(8),
 	.src_addr_widths   = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |

-- 
2.54.0



^ permalink raw reply related

* [PATCH 0/5] dmaengine: sun6i-dma: Add support for Allwinner A733 DMA controller
From: Yuanshen Cao @ 2026-06-19  4:53 UTC (permalink / raw)
  To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
	Samuel Holland, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Maxime Ripard
  Cc: dmaengine, linux-arm-kernel, linux-sunxi, linux-kernel,
	devicetree, Yuanshen Cao

Hi everyone,

This patch series introduces support for the Allwinner A733 DMA
controller in the `sun6i-dma` driver.

The A733 DMA controller differs from previous generations in several key
ways:
1. It supports higher address (up to 32G).
2. It uses a different interrupt register layout and mapping.
3. It has a different number of channels per interrupt register.

To support these differences without introducing complex conditional
logic throughout the driver, this series first refactors the
`sun6i_dma_config` structure. By moving interrupt handling, register
dumping, and address configuration into function pointers within the
configuration structure. This allows the driver to support the A733 
and future hardware revisions. It also aligns with the DMA drivers in
Radxa BSP Package[1].

The series is organized as follows:
- Refactors the configuration structure to include function pointers for
  interrupt and register operations.
- Moves address setting logic into the configuration structure to handle
  varying address widths.
- Adds support for variable channels per interrupt register.
- Implements the A733-specific configuration and register mappings.
- Updates the device tree bindings documentation.

Tested on Radxa Cubie A7Z.

[1] https://github.com/radxa/allwinner-bsp/blob/cubie-aiot-v1.4.8/drivers/dma/sunxi-dma.c

Thanks!

Signed-off-by: Yuanshen Cao <alex.caoys@gmail.com>
---
Yuanshen Cao (5):
      dmaengine: sun6i-dma: Refactor to support A733 interrupt and register handling
      dmaengine: sun6i-dma: Add set_addr function pointer for variable address widths
      dmaengine: sun6i-dma: Add num_channels_per_reg for flexible interrupt mapping
      dmaengine: sun6i-dma: Implement support for Allwinner A733 DMA controller
      dt-bindings: dma: sun50i-a64-dma: Update device tree bindings documentation for A733

 .../bindings/dma/allwinner,sun50i-a64-dma.yaml     |   2 +
 drivers/dma/sun6i-dma.c                            | 223 +++++++++++++++++++--
 2 files changed, 203 insertions(+), 22 deletions(-)
---
base-commit: 8cd9520d35a6c38db6567e97dd93b1f11f185dc6
change-id: 20260619-sun60i-a733-dma-c2455149165d

Best regards,
--  
Yuanshen Cao <alex.caoys@gmail.com>



^ permalink raw reply


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