Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* Re: [PATCH 1/1] KVM: arm64: nv: Avoid full shadow s2 unmap
From: Wei-Lin Chang @ 2026-04-24 19:45 UTC (permalink / raw)
  To: Marc Zyngier
  Cc: linux-arm-kernel, kvmarm, linux-kernel, Oliver Upton, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon
In-Reply-To: <867bq72n7l.wl-maz@kernel.org>

On Thu, Apr 16, 2026 at 11:50:38AM +0100, Marc Zyngier wrote:
> On Thu, 16 Apr 2026 00:05:40 +0100,
> Wei-Lin Chang <weilin.chang@arm.com> wrote:
> > 
> > On Wed, Apr 15, 2026 at 09:38:55AM +0100, Marc Zyngier wrote:
> 
> [...]
> 
> > > > diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
> > > > index 851f6171751c..a97bd461c1e1 100644
> > > > --- a/arch/arm64/include/asm/kvm_host.h
> > > > +++ b/arch/arm64/include/asm/kvm_host.h
> > > > @@ -217,6 +217,10 @@ struct kvm_s2_mmu {
> > > >  	 */
> > > >  	bool	nested_stage2_enabled;
> > > >  
> > > > +	/* canonical IPA to nested IPA range lookup */
> > > > +	struct maple_tree nested_revmap_mt;
> > > > +	bool	nested_revmap_broken;
> > > > +
> > > 
> > > Consider moving this boolean next to the other ones so that you don't
> > > create too many holes in the kvm_s2_mmu structure (use pahole to find out).
> > > 
> > > But I have some misgivings about the way things are structured
> > > here. Only NV needs a revmap, yet this is present irrelevant of the
> > > nature of the VM and bloats the data structure a bit.
> > > 
> > > My naive approach would have been to only keep a pointer to the
> > > revmap, and make that pointer NULL when the tree is "broken", and
> > > freed under RCU if the context isn't the correct one.
> > 
> > Can you explain what you mean by "if the context isn't the correct one"?
> > If this refers to when selecting a specific kvm_s2_mmu instance for
> > another context, then IIUC refcnt would already be 0 and there would be
> > no other user of the tree.
> 
> Sorry, "context" is an overloaded word. I meant a situation in which
> you couldn't immediately free the maple-tree because you're holding
> locks and freeing (hypothetically) requires a sleeping "context". in
> this case, freeing under RCU, purely as a deferring mechanism, might
> be useful.

I experimented using RCU to free the tree as a deferring mechanism.
Here are a few observations:

  - At reverse map record time, if maple tree store fails, we have to
    change the maple tree pointer to a NULL, which is an RCU write
    operation. Therefore we need to either take another lock, or use a
    xchg(ptr, NULL) to avoid the lock.

  - Because we're holding the read-side mmu_lock, we shouldn't block
    during reverse map record. Therefore we should use call_rcu()
    instead of synchronize_rcu() to free the "broken" tree. This implies
    a pointer to a maple tree in kvm_s2_mmu will not suffice, an
    additional structure with both the maple tree and an rcu_head have
    to be created.

IMO looking at RCU calls mixed with mtree_{, un}lock(), and having a new
wrapper struct to make this dynamic allocation scheme work is not very
attractive to me.

Instead, what do you think if I aggregate all strictly NV-related
fields in kvm_s2_mmu i.e. tlb_vttbr, tlb_vtcr, nested_stage2_enabled,
shadow_pt_debugfs_dentry, pending_unmap, into a struct maybe called
kvm_s2_mmu_nested, add a maple tree in it, and have a pointer to this
struct in kvm_s2_mmu? kvm_s2_mmu_nested can then be allocated only if we
init a nested s2 mmu.

Do you think this can work and is better than the current approaches?

Thanks,
Wei-Lin Chang

> 
> [...]
> 
> > > > +/*
> > > > + * Per shadow S2 reverse map (IPA -> nested IPA range) maple tree payload
> > > > + * layout:
> > > > + *
> > > > + * bit 63: valid, 1 for non-polluted entries, prevents the case where the
> > > > + *         nested IPA is 0 and turns the whole value to 0
> > > > + * bits 55-12: nested IPA bits 55-12
> > > > + * bit 0: polluted, 1 for polluted, 0 for not
> > > > + */
> > > > +#define VALID_ENTRY		BIT(63)
> > > > +#define NESTED_IPA_MASK		GENMASK_ULL(55, 12)
> > > > +#define UNKNOWN_IPA		BIT(0)
> > > > +
> > > 
> > > This only works because you are using the "advanced" API, right?
> > > Otherwise, you'd be losing the high bit. It'd be good to add a comment
> > > so that people keep that in mind.
> > 
> > Sorry, I can't find any relationship between the advanced API and the
> > top most bit of the maple tree value, what am I missing?
> 
> From Documentation/core-api/maple_tree.rst:
> 
> <quote>
> The Maple Tree can store values between ``0`` and ``ULONG_MAX``.  The Maple
> Tree reserves values with the bottom two bits set to '10' which are below 4096
> (ie 2, 6, 10 .. 4094) for internal use.  If the entries may use reserved
> entries then the users can convert the entries using xa_mk_value() and convert
> them back by calling xa_to_value().  If the user needs to use a reserved
> value, then the user can convert the value when using the
> :ref:`maple-tree-advanced-api`, but are blocked by the normal API.
> </quote>
> 
> So depending how you read this, you can conclude that the bit patterns
> you encode in the MT may be considered as invalid. xa_mk_value() would
> make things always work, but that shifts the value left by one bit,
> hence you'd lose bit 63 (see how we use trap_config in
> emulate-nested.c to deal with this).
> 
> I think you are lucky that bits [11:1] are always 0 here, but that
> looks extremely fragile to me, so you never hit the [1:0]==10
> condition, but that's really fragile.
> 
> > 
> > > 
> > > >  void kvm_init_nested(struct kvm *kvm)
> > > >  {
> > > >  	kvm->arch.nested_mmus = NULL;
> > > > @@ -769,12 +783,57 @@ static struct kvm_s2_mmu *get_s2_mmu_nested(struct kvm_vcpu *vcpu)
> > > >  	return s2_mmu;
> > > >  }
> > > >  
> > > > +void kvm_record_nested_revmap(gpa_t ipa, struct kvm_s2_mmu *mmu,
> > > > +			      gpa_t fault_ipa, size_t map_size)
> > > > +{
> > > > +	struct maple_tree *mt = &mmu->nested_revmap_mt;
> > > > +	gpa_t start = ipa;
> > > > +	gpa_t end = ipa + map_size - 1;
> > > > +	u64 entry, new_entry = 0;
> > > > +	MA_STATE(mas, mt, start, end);
> > > > +
> > > > +	if (mmu->nested_revmap_broken)
> > > > +		return;
> > > > +
> > > > +	mtree_lock(mt);
> > > > +	entry = (u64)mas_find_range(&mas, end);
> > > > +
> > > > +	if (entry) {
> > > > +		/* maybe just a perm update... */
> > > > +		if (!(entry & UNKNOWN_IPA) && mas.index == start &&
> > > > +		    mas.last == end &&
> > > > +		    fault_ipa == (entry & NESTED_IPA_MASK))
> > > > +			goto unlock;
> > > > +		/*
> > > > +		 * Create a "polluted" range that spans all the overlapping
> > > > +		 * ranges and store it.
> > > > +		 */
> > > > +		while (entry && mas.index <= end) {
> > > > +			start = min(mas.index, start);
> > > > +			end = max(mas.last, end);
> > > > +			entry = (u64)mas_find_range(&mas, end);
> > > > +		}
> > > > +		new_entry |= UNKNOWN_IPA;
> > > > +	} else {
> > > > +		new_entry |= fault_ipa;
> > > > +		new_entry |= VALID_ENTRY;
> > > > +	}
> > > > +
> > > > +	mas_set_range(&mas, start, end);
> > > > +	if (mas_store_gfp(&mas, (void *)new_entry, GFP_NOWAIT | __GFP_ACCOUNT))
> > > > +		mmu->nested_revmap_broken = true;
> > > 
> > > Can we try and minimise the risk of allocation failure here?
> > > 
> > > user_mem_abort() tries very hard to pre-allocate pages for page
> > > tables by maintaining an memcache. Can we have a similar approach for
> > > the revmap?
> > 
> > Unfortunately, as I understand the maple tree can only pre-allocate for
> > a store when the range and the entry to be stored is given, but in this
> > case we must inspect the tree to get that information after we hold the
> > mmu and maple tree locks. It is possible to do a two pass approach:
> > 
> > pre-allocate -> take MMU lock -> take maple tree lock -> revalidate what
> > we pre-allocated is still usable (nobody changed the tree before we took
> > the maple tree lock)
> > 
> > But I am not fond of this extra complexity..
> 
> Fair enough. It would at least be interesting to get a feel for how
> often this happens, because if we fail often, it won't help much.
> 
> [...]
> 
> > > My other concern here is related to TLB invalidation. As the guest
> > > performs TLB invalidations that remove entries from the shadow S2,
> > > there is no way to update the revmap to account for this.
> > > 
> > > This obviously means that the revmap becomes more and more inaccurate
> > > over time, and that is likely to accumulate conflicting entries.
> > > 
> > > What is the plan to improve the situation on this front?
> > 
> > Right now I think using a direct map which goes from nested IPA to
> > canonical IPA could work while not generating too much complexity, if we
> > keep the reverse map and direct map in lockstep (direct map keeping the
> > same mappings as the reverse map but just in reverse).
> 
> Right, so that'd effectively a mirror of the guest's page tables at
> the point of taking the fault.
> 
> > I'll try to do that and include it in the next iteration.
> 
> Thanks,
> 
> 	M.
> 
> -- 
> Without deviation from the norm, progress is not possible.


^ permalink raw reply

* Re: [REGRESSION] rseq: refactoring in v6.19 broke everyone on arm64 and tcmalloc everywhere
From: Thomas Gleixner @ 2026-04-24 19:44 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Mathias Stearn, Dmitry Vyukov, Jinjie Ruan, linux-man,
	Mark Rutland, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Boqun Feng, Paul E. McKenney, Chris Kennelly, regressions,
	linux-kernel, linux-arm-kernel, Ingo Molnar, Blake Oler
In-Reply-To: <20260424150318.GE641209@noisy.programming.kicks-ass.net>

On Fri, Apr 24 2026 at 17:03, Peter Zijlstra wrote:
> On Fri, Apr 24, 2026 at 04:16:08PM +0200, Thomas Gleixner wrote:
>> > I was really hoping that we would only need to do the "redundant"
>> > cpu_id_start writes would only be needed on membarrier_rseq IPIs where
>> > it really is a pay-for-what-you-use functionality,
>> 
>> That's fine and can be solved without adding this sequence overhead into
>> the scheduler hotpath.
>
> Something like so? (probably needs help for !GENERIC bits)

Yes and yes :)

Let me stare at that !generic tif bits case.



^ permalink raw reply

* Re: [PATCH v3 1/4] kernel: param: initialize module_kset on-demand
From: Gary Guo @ 2026-04-24 19:26 UTC (permalink / raw)
  To: Shashank Balaji, Suzuki K Poulose, Mike Leach, James Clark,
	Alexander Shishkin, Maxime Coquelin, Alexandre Torgue,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Richard Cochran, Jonathan Corbet, Shuah Khan
  Cc: Rahul Bukte, linux-kernel, coresight, linux-arm-kernel,
	driver-core, rust-for-linux, linux-doc, Daniel Palmer, Tim Bird
In-Reply-To: <20260422-acpi_mod_name-v3-1-a184eff9ff6f@sony.com>

On Wed Apr 22, 2026 at 10:49 AM BST, Shashank Balaji wrote:
> module_kset is initialized in param_sysfs_init(), a subsys_initcall. A number
> of platform drivers register themselves prior to subsys_initcalls. With an
> upcoming patch ("driver core: platform: set mod_name in driver registration")
> that sets their mod_name in struct device_driver, lookup_or_create_module()
> will be called for those drivers, which calls kset_find_object(module_kset, mod_name).
> This fails because module_kset isn't alive yet.
>
> Fix this by initializing module_kset on-demand in lookup_or_create_module().
> Retain the param_sysfs_init() subsys_initcall to ensure that module_kset is
> live after subsys_initcalls (assuming no OOM) for any users who may need it,
> on the off chance that it wasn't init'd on-demand because of no
> pre-subsys_initcall drivers.
>
> This on-demand path can trigger before subsys_initcall. kset_create_and_add()
> be should safe in those contexts because the allocator is up and running by then,
> no userspace to start uevent helper or listen to a uevent socket.
>
> Suggested-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Co-developed-by: Rahul Bukte <rahul.bukte@sony.com>
> Signed-off-by: Rahul Bukte <rahul.bukte@sony.com>
> Signed-off-by: Shashank Balaji <shashank.mahadasyam@sony.com>
>
> ---
>
> Patch 3 depends on this patch.
> ---
>  kernel/params.c | 41 +++++++++++++++++++++++++----------------
>  1 file changed, 25 insertions(+), 16 deletions(-)
>
> diff --git a/kernel/params.c b/kernel/params.c
> index 74d620bc2521..f25d6fda159c 100644
> --- a/kernel/params.c
> +++ b/kernel/params.c
> @@ -745,6 +745,26 @@ void module_param_sysfs_remove(struct module *mod)
>  }
>  #endif
>  
> +static int uevent_filter(const struct kobject *kobj)
> +{
> +	const struct kobj_type *ktype = get_ktype(kobj);
> +
> +	if (ktype == &module_ktype)
> +		return 1;
> +	return 0;
> +}
> +
> +static const struct kset_uevent_ops module_uevent_ops = {
> +	.filter = uevent_filter,
> +};
> +
> +static struct kset *__init_or_module ensure_module_kset(void)
> +{
> +	if (!module_kset)
> +		module_kset = kset_create_and_add("module", &module_uevent_ops, NULL);
> +	return module_kset;
> +}
> +
>  struct module_kobject * __init_or_module
>  lookup_or_create_module_kobject(const char *name)
>  {
> @@ -752,6 +772,9 @@ lookup_or_create_module_kobject(const char *name)
>  	struct kobject *kobj;
>  	int err;
>  
> +	if (!ensure_module_kset())
> +		return NULL;
> +
>  	kobj = kset_find_obj(module_kset, name);
>  	if (kobj)
>  		return to_module_kobject(kobj);
> @@ -911,19 +934,6 @@ static const struct sysfs_ops module_sysfs_ops = {
>  	.store = module_attr_store,
>  };
>  
> -static int uevent_filter(const struct kobject *kobj)
> -{
> -	const struct kobj_type *ktype = get_ktype(kobj);
> -
> -	if (ktype == &module_ktype)
> -		return 1;
> -	return 0;
> -}
> -
> -static const struct kset_uevent_ops module_uevent_ops = {
> -	.filter = uevent_filter,
> -};
> -
>  struct kset *module_kset;
>  
>  static void module_kobj_release(struct kobject *kobj)
> @@ -940,7 +950,7 @@ const struct kobj_type module_ktype = {
>  };
>  
>  /*
> - * param_sysfs_init - create "module" kset
> + * param_sysfs_init - create module_kset if not already done
>   *
>   * This must be done before the initramfs is unpacked and
>   * request_module() thus becomes possible, because otherwise the
> @@ -948,8 +958,7 @@ const struct kobj_type module_ktype = {
>   */
>  static int __init param_sysfs_init(void)
>  {
> -	module_kset = kset_create_and_add("module", &module_uevent_ops, NULL);
> -	if (!module_kset) {
> +	if (!ensure_module_kset()) {
>  		printk(KERN_WARNING "%s (%d): error creating kset\n",
>  			__FILE__, __LINE__);
>  		return -ENOMEM;

I don't think lazily creation of module_kset is the correct approach.

The only thing that param_sysfs_init does is create a kset_create_and_add, which
can be done really early. Driver core creates a couple of them during
initialization too.

This is ultimately just an init order problem, and you need things to be
initialized earlier. Given lack of dependency of anything else, this could just
be a pure_initcall which will be initialized before everything else.

Best,
Gary


^ permalink raw reply

* [PATCH v4 3/3] ARM: dts: aspeed-g6: Add nodes for i3c controllers
From: Dawid Glazik @ 2026-04-24 20:21 UTC (permalink / raw)
  To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Joel Stanley, Andrew Jeffery, linux-aspeed
  Cc: devicetree, linux-arm-kernel, linux-kernel, dawid.glazik,
	maciej.lawniczak, Jeremy Kerr
In-Reply-To: <cover.1777058942.git.dawid.glazik@linux.intel.com>

Add the i3c controller devices to the ast2600 g6 common dts. We add all
6 busses to the common g6 definition, but leave disabled through the
status property, to be enabled per-platform.

Suggested-by: Jeremy Kerr <jk@codeconstruct.com.au>
Signed-off-by: Dawid Glazik <dawid.glazik@linux.intel.com>
---
v4:
 - removed i3c aliases
 - renamed i3c-global node name to generic: syscon
v3:
 - add i3c aliases
 - rebase on top of latest tree and solve conflicts
 - as agreed with Jeremy off-list, he said I can take authorship of this going forward
v2:
 - use inline bus representation, without the i3c: label
---
 arch/arm/boot/dts/aspeed/aspeed-g6.dtsi | 91 +++++++++++++++++++++++++
 1 file changed, 91 insertions(+)

diff --git a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
index f5641128614f..51a6a4157f1b 100644
--- a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
+++ b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
@@ -1066,6 +1066,97 @@ i2c15: i2c@800 {
 				};
 			};
 
+			bus@1e7a0000 {
+				compatible = "simple-bus";
+				#address-cells = <1>;
+				#size-cells = <1>;
+				ranges = <0 0x1e7a0000 0x8000>;
+
+				i3c_global: syscon@0 {
+					compatible = "aspeed,ast2600-i3c-global", "syscon";
+					reg = <0x0 0x1000>;
+					resets = <&syscon ASPEED_RESET_I3C_DMA>;
+				};
+
+				i3c0: i3c@2000 {
+					compatible = "aspeed,ast2600-i3c";
+					reg = <0x2000 0x1000>;
+					#address-cells = <3>;
+					#size-cells = <0>;
+					clocks = <&syscon ASPEED_CLK_GATE_I3C0CLK>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i3c1_default>;
+					interrupts = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>;
+					aspeed,global-regs = <&i3c_global 0>;
+					status = "disabled";
+				};
+
+				i3c1: i3c@3000 {
+					compatible = "aspeed,ast2600-i3c";
+					reg = <0x3000 0x1000>;
+					#address-cells = <3>;
+					#size-cells = <0>;
+					clocks = <&syscon ASPEED_CLK_GATE_I3C1CLK>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i3c2_default>;
+					interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
+					aspeed,global-regs = <&i3c_global 1>;
+					status = "disabled";
+				};
+
+				i3c2: i3c@4000 {
+					compatible = "aspeed,ast2600-i3c";
+					reg = <0x4000 0x1000>;
+					#address-cells = <3>;
+					#size-cells = <0>;
+					clocks = <&syscon ASPEED_CLK_GATE_I3C2CLK>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i3c3_default>;
+					interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>;
+					aspeed,global-regs = <&i3c_global 2>;
+					status = "disabled";
+				};
+
+				i3c3: i3c@5000 {
+					compatible = "aspeed,ast2600-i3c";
+					reg = <0x5000 0x1000>;
+					#address-cells = <3>;
+					#size-cells = <0>;
+					clocks = <&syscon ASPEED_CLK_GATE_I3C3CLK>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i3c4_default>;
+					interrupts = <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH>;
+					aspeed,global-regs = <&i3c_global 3>;
+					status = "disabled";
+				};
+
+				i3c4: i3c@6000 {
+					compatible = "aspeed,ast2600-i3c";
+					reg = <0x6000 0x1000>;
+					#address-cells = <3>;
+					#size-cells = <0>;
+					clocks = <&syscon ASPEED_CLK_GATE_I3C4CLK>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i3c5_default>;
+					interrupts = <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>;
+					aspeed,global-regs = <&i3c_global 4>;
+					status = "disabled";
+				};
+
+				i3c5: i3c@7000 {
+					compatible = "aspeed,ast2600-i3c";
+					reg = <0x7000 0x1000>;
+					#address-cells = <3>;
+					#size-cells = <0>;
+					clocks = <&syscon ASPEED_CLK_GATE_I3C5CLK>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i3c6_default>;
+					interrupts = <GIC_SPI 107 IRQ_TYPE_LEVEL_HIGH>;
+					aspeed,global-regs = <&i3c_global 5>;
+					status = "disabled";
+				};
+			};
+
 			fsim0: fsi@1e79b000 {
 				#interrupt-cells = <1>;
 				compatible = "aspeed,ast2600-fsi-master";
-- 
2.43.0



^ permalink raw reply related

* [PATCH v4 2/3] dt-bindings: mfd: syscon: add aspeed,ast2600-i3c-global compatible
From: Dawid Glazik @ 2026-04-24 20:21 UTC (permalink / raw)
  To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Joel Stanley, Andrew Jeffery, linux-aspeed
  Cc: devicetree, linux-arm-kernel, linux-kernel, dawid.glazik,
	maciej.lawniczak
In-Reply-To: <cover.1777058942.git.dawid.glazik@linux.intel.com>

Add aspeed,ast2600-i3c-global to the syscon binding compatible
lists to document the AST2600 I3C global register syscon node.

Signed-off-by: Dawid Glazik <dawid.glazik@linux.intel.com>
---
 Documentation/devicetree/bindings/mfd/syscon.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/mfd/syscon.yaml b/Documentation/devicetree/bindings/mfd/syscon.yaml
index e57add2bacd3..50c07038122f 100644
--- a/Documentation/devicetree/bindings/mfd/syscon.yaml
+++ b/Documentation/devicetree/bindings/mfd/syscon.yaml
@@ -49,6 +49,7 @@ select:
           - apm,xgene-mcb
           - apm,xgene-rb
           - apm,xgene-scu
+          - aspeed,ast2600-i3c-global
           - atmel,sama5d2-sfrbu
           - atmel,sama5d3-nfc-io
           - atmel,sama5d3-sfrbu
@@ -161,6 +162,7 @@ properties:
               - apm,xgene-mcb
               - apm,xgene-rb
               - apm,xgene-scu
+              - aspeed,ast2600-i3c-global
               - atmel,sama5d2-sfrbu
               - atmel,sama5d3-nfc-io
               - atmel,sama5d3-sfrbu
-- 
2.43.0



^ permalink raw reply related

* [PATCH v4 1/3] ARM: dts: aspeed-g6: move i2c controllers directly into apb node
From: Dawid Glazik @ 2026-04-24 20:20 UTC (permalink / raw)
  To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Joel Stanley, Andrew Jeffery, linux-aspeed
  Cc: devicetree, linux-arm-kernel, linux-kernel, dawid.glazik,
	maciej.lawniczak, Jeremy Kerr
In-Reply-To: <cover.1777058942.git.dawid.glazik@linux.intel.com>

We currently have the apb's mapping of the i2c controller space as a
labelled mostly-empty node:

  apb {
    i2c: bus@1e78a000 {
      ranges = <...>;
    };
  }

... and then define the contents of the i2c block later:

  i2c: {
    i2c0: i2c-bus@80 {
      reg = <0x80 0x80>;
    };
    i2c1: i2c-bus@100 {
      reg = <0x100 0x80>;
    };
  }

Krzysztof mentions[1] that isn't convention though, with the top-level
simple-bus being empty and linked via the label. So, drop the label
usage and move the i2c bus definition into the simple-bus node directly
under the apb:

  apb {
     bus@1e78a000 {
      ranges = <...>;

      i2c0: i2c-bus@80 {
        reg = <0x80 0x80>;
      };
      i2c1: i2c-bus@100 {
        reg = <0x100 0x80>;
      };
    };
  }

This will allow us to be consistent when we add new definitions for the
i3c nodes, which would require the latter format.

Link: https://lore.kernel.org/linux-devicetree/c5331cf8-7295-4e6a-ba39-e0751a2c357e@kernel.org/ [1]
Suggested-by: Jeremy Kerr <jk@codeconstruct.com.au>
Signed-off-by: Dawid Glazik <dawid.glazik@linux.intel.com>
---
v3:
 - pick up series after two years
 - rebase on top of latest tree and solve conflicts
 - as agreed with Jeremy off-list, he said I can take authorship of this going forward
v2:
 - new patch: reorganise i2c nodes before adding new-format i3c nodes
---
 arch/arm/boot/dts/aspeed/aspeed-g6.dtsi | 452 ++++++++++++------------
 1 file changed, 225 insertions(+), 227 deletions(-)

diff --git a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
index 189bc3bbb47c..f5641128614f 100644
--- a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
+++ b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
@@ -835,11 +835,235 @@ uart9: serial@1e790300 {
 				status = "disabled";
 			};
 
-			i2c: bus@1e78a000 {
+			bus@1e78a000 {
 				compatible = "simple-bus";
 				#address-cells = <1>;
 				#size-cells = <1>;
 				ranges = <0 0x1e78a000 0x1000>;
+
+				i2c0: i2c@80 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x80 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c1_default>;
+					status = "disabled";
+				};
+
+				i2c1: i2c@100 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x100 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 111 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c2_default>;
+					status = "disabled";
+				};
+
+				i2c2: i2c@180 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x180 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c3_default>;
+					status = "disabled";
+				};
+
+				i2c3: i2c@200 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x200 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 113 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c4_default>;
+					status = "disabled";
+				};
+
+				i2c4: i2c@280 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x280 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c5_default>;
+					status = "disabled";
+				};
+
+				i2c5: i2c@300 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x300 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c6_default>;
+					status = "disabled";
+				};
+
+				i2c6: i2c@380 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x380 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c7_default>;
+					status = "disabled";
+				};
+
+				i2c7: i2c@400 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x400 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c8_default>;
+					status = "disabled";
+				};
+
+				i2c8: i2c@480 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x480 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c9_default>;
+					status = "disabled";
+				};
+
+				i2c9: i2c@500 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x500 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c10_default>;
+					status = "disabled";
+				};
+
+				i2c10: i2c@580 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x580 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c11_default>;
+					status = "disabled";
+				};
+
+				i2c11: i2c@600 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x600 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c12_default>;
+					status = "disabled";
+				};
+
+				i2c12: i2c@680 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x680 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 122 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c13_default>;
+					status = "disabled";
+				};
+
+				i2c13: i2c@700 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x700 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c14_default>;
+					status = "disabled";
+				};
+
+				i2c14: i2c@780 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x780 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 124 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c15_default>;
+					status = "disabled";
+				};
+
+				i2c15: i2c@800 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0x800 0x80>;
+					compatible = "aspeed,ast2600-i2c-bus";
+					clocks = <&syscon ASPEED_CLK_APB2>;
+					resets = <&syscon ASPEED_RESET_I2C>;
+					interrupts = <GIC_SPI 125 IRQ_TYPE_LEVEL_HIGH>;
+					bus-frequency = <100000>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&pinctrl_i2c16_default>;
+					status = "disabled";
+				};
 			};
 
 			fsim0: fsi@1e79b000 {
@@ -870,229 +1094,3 @@ fsim1: fsi@1e79b100 {
 };
 
 #include "aspeed-g6-pinctrl.dtsi"
-
-&i2c {
-	i2c0: i2c@80 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x80 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c1_default>;
-		status = "disabled";
-	};
-
-	i2c1: i2c@100 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x100 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 111 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c2_default>;
-		status = "disabled";
-	};
-
-	i2c2: i2c@180 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x180 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c3_default>;
-		status = "disabled";
-	};
-
-	i2c3: i2c@200 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x200 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 113 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c4_default>;
-		status = "disabled";
-	};
-
-	i2c4: i2c@280 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x280 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c5_default>;
-		status = "disabled";
-	};
-
-	i2c5: i2c@300 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x300 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c6_default>;
-		status = "disabled";
-	};
-
-	i2c6: i2c@380 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x380 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c7_default>;
-		status = "disabled";
-	};
-
-	i2c7: i2c@400 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x400 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c8_default>;
-		status = "disabled";
-	};
-
-	i2c8: i2c@480 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x480 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c9_default>;
-		status = "disabled";
-	};
-
-	i2c9: i2c@500 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x500 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c10_default>;
-		status = "disabled";
-	};
-
-	i2c10: i2c@580 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x580 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c11_default>;
-		status = "disabled";
-	};
-
-	i2c11: i2c@600 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x600 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c12_default>;
-		status = "disabled";
-	};
-
-	i2c12: i2c@680 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x680 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 122 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c13_default>;
-		status = "disabled";
-	};
-
-	i2c13: i2c@700 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x700 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c14_default>;
-		status = "disabled";
-	};
-
-	i2c14: i2c@780 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x780 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 124 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c15_default>;
-		status = "disabled";
-	};
-
-	i2c15: i2c@800 {
-		#address-cells = <1>;
-		#size-cells = <0>;
-		reg = <0x800 0x80>;
-		compatible = "aspeed,ast2600-i2c-bus";
-		clocks = <&syscon ASPEED_CLK_APB2>;
-		resets = <&syscon ASPEED_RESET_I2C>;
-		interrupts = <GIC_SPI 125 IRQ_TYPE_LEVEL_HIGH>;
-		bus-frequency = <100000>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&pinctrl_i2c16_default>;
-		status = "disabled";
-	};
-};
-- 
2.43.0



^ permalink raw reply related

* [PATCH v4 0/3] ARM: dts: aspeed-g6: add AST2600 I3C nodes and bindings
From: Dawid Glazik @ 2026-04-24 20:20 UTC (permalink / raw)
  To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Joel Stanley, Andrew Jeffery, linux-aspeed
  Cc: devicetree, linux-arm-kernel, linux-kernel, dawid.glazik,
	maciej.lawniczak

This series reworks and resubmits AST2600 I3C DTS updates that were
originally posted in 2024, but stalled without further upstream
progress.[1] The series was rebased onto the current tree and merge
conflicts were resolved.

The patches first move I2C controller nodes under the APB simple-bus
for layout consistency, then document aspeed,ast2600-i3c-global in
the syscon binding, and finally add AST2600 I3C controller nodes in
aspeed-g6.dtsi.

Jeremy agreed in a separate email thread that I can continue this
series under my authorship.

Link: https://lore.kernel.org/all/9d8c03d742fa9767f30e23d75ddf0baf4296c88e.1714647917.git.jk@codeconstruct.com.au/

Dawid Glazik (3):
  ARM: dts: aspeed-g6: move i2c controllers directly into apb node
  dt-bindings: mfd: syscon: add aspeed,ast2600-i3c-global compatible
  ARM: dts: aspeed-g6: Add nodes for i3c controllers

 .../devicetree/bindings/mfd/syscon.yaml       |   2 +
 arch/arm/boot/dts/aspeed/aspeed-g6.dtsi       | 543 ++++++++++--------
 2 files changed, 318 insertions(+), 227 deletions(-)


base-commit: 6de23f81a5e08be8fbf5e8d7e9febc72a5b5f27f
-- 
2.43.0



^ permalink raw reply

* Re: [PATCH v3 05/11] iommu: Change group->devices to RCU-protected list
From: Nicolin Chen @ 2026-04-24 19:12 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: Baolu Lu, Will Deacon, Robin Murphy, Joerg Roedel, Bjorn Helgaas,
	Rafael J . Wysocki, Len Brown, Pranjal Shrivastava, Mostafa Saleh,
	Kevin Tian, linux-arm-kernel, iommu, linux-kernel, linux-acpi,
	linux-pci, vsethi, Shuai Xue
In-Reply-To: <20260424131148.GB3444440@nvidia.com>

On Fri, Apr 24, 2026 at 10:11:48AM -0300, Jason Gunthorpe wrote:
> On Thu, Apr 23, 2026 at 08:08:59PM -0700, Nicolin Chen wrote:
> > On Fri, Apr 24, 2026 at 10:53:49AM +0800, Baolu Lu wrote:
> > > On 4/17/26 07:28, Nicolin Chen wrote:
> > > 	    mutex_unlock(&group->mutex);
> > >             /*
> > >              * FIXME: Mis-locked because the ops->probe_finalize() call-back
> > >              * of some IOMMU drivers calls arm_iommu_attach_device() which
> > >              * in-turn might call back into IOMMU core code, where it tries
> > >              * to take group->mutex, resulting in a deadlock.
> > >              */
> > >              for_each_group_device(group, gdev)
> > >                         iommu_group_do_probe_finalize(gdev->dev);
> > >         }
> > > 
> > >         return 0;
> > > }
> > > 
> > > Will the change above trigger a lockdep splat due to this "mis-locked"
> > > case?"
> > 
> > Oh, I missed this one. That's a good finding!
> > 
> > Perhaps we can just change it to list_for_each_entry_rcu holding
> > rcu_read_lock() and drop the FIXME.
> 
> You can't hold rcu across that function IIRC

Oh right. I didn't think too carefully...

I tend to keep it as-is. So, maybe just list_for_each_entry?

Nicolin


^ permalink raw reply

* [PATCH RFC v2 2/2] arm64: vdso: Implement __vdso_futex_robust_try_unlock()
From: André Almeida @ 2026-04-24 18:56 UTC (permalink / raw)
  To: Catalin Marinas, Will Deacon, Thomas Gleixner, Mark Rutland,
	Mathieu Desnoyers, Sebastian Andrzej Siewior, Carlos O'Donell,
	Peter Zijlstra, Florian Weimer, Rich Felker, Torvald Riegel,
	Darren Hart, Ingo Molnar, Davidlohr Bueso, Arnd Bergmann,
	Liam R . Howlett, Uros Bizjak, Thomas Weißschuh
  Cc: linux-arm-kernel, linux-kernel, linux-arch, kernel-dev, LKML,
	André Almeida
In-Reply-To: <20260424-tonyk-robust_arm-v2-0-db4e46f752cf@igalia.com>

Based on the x86 implementation, implement the vDSO function for unlocking
a robust futex correctly.

Commit xxxxxxxxxxxx ("x86/vdso: Implement __vdso_futex_robust_try_unlock()") has
the full explanation about why this mechanism is needed.

The unlock assembly sequence for arm64 is:

	__futex_list64_try_unlock_cs_start:
		ldxr	x3, [x0] // Load the value at *futex
		cmp	x1, x3   // Compare with TID
		b.ne	__futex_list64_try_unlock_cs_end
		stlxr	w1, xzr, [x0] // Try to clear *futex
		cbnz	w1, __futex_list64_try_unlock_cs_start
	__futex_list64_try_unlock_cs_success:
		str	xzr, [x2] // After clearing *futex, clear *op_pending
	__futex_list64_try_unlock_cs_end:

The decision regarding if the pointer should be cleared or not lies on checking
the condition flag zero:

	return (regs->user_regs.pstate & PSR_Z_BIT) ?
		(void __user *) regs->user_regs.regs[2] : NULL;

If it's not zero, that means that the comparassion worked and the kernel should
clear op_pending (if userspace didn't managed to) stored at x2.

Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
RFC:
 - Should I duplicate the explanation found in the x86 commit or can I just
 point to it?
 - Only LL/SC for now but I can add LSE later if this looks good
 - It the objdump I see that op_pending is store at x2. But how stable is this,
 how can I write it in a way that's always x2?
---
 arch/arm64/Kconfig                                 |  1 +
 arch/arm64/include/asm/futex_robust.h              | 35 +++++++++++++
 arch/arm64/kernel/vdso/Makefile                    |  9 +++-
 arch/arm64/kernel/vdso/vdso.lds.S                  |  4 ++
 .../kernel/vdso/vfutex_robust_list_try_unlock.c    | 59 ++++++++++++++++++++++
 5 files changed, 107 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 427151a9db7f..e10cb97a51c7 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -249,6 +249,7 @@ config ARM64
 	select HAVE_RELIABLE_STACKTRACE
 	select HAVE_POSIX_CPU_TIMERS_TASK_WORK
 	select HAVE_FUNCTION_ARG_ACCESS_API
+	select HAVE_FUTEX_ROBUST_UNLOCK
 	select MMU_GATHER_RCU_TABLE_FREE
 	select HAVE_RSEQ
 	select HAVE_RUST if RUSTC_SUPPORTS_ARM64
diff --git a/arch/arm64/include/asm/futex_robust.h b/arch/arm64/include/asm/futex_robust.h
new file mode 100644
index 000000000000..f2b7a2b15cb5
--- /dev/null
+++ b/arch/arm64/include/asm/futex_robust.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_ARM64_FUTEX_ROBUST_H
+#define _ASM_ARM64_FUTEX_ROBUST_H
+
+#include <asm/ptrace.h>
+
+static __always_inline void __user *arm64_futex_robust_unlock_get_pop(struct pt_regs *regs)
+{
+	/*
+	 * RFC: According to the objdump bellow, x2 is the address of
+	 * op_pending. How stable is this?
+
+	 <__futex_list64_try_unlock_cs_start>:
+		ldxr	x3, [x0]
+		cmp	x1, x3
+		b.ne	d7c <__futex_list64_try_unlock_cs_end>  // b.any
+		stlxr	w1, xzr, [x0]
+		cbnz	w1, d64 <__futex_list64_try_unlock_cs_start>
+
+	<__futex_list64_try_unlock_cs_success>:
+		str	xzr, [x2]
+
+	<__futex_list64_try_unlock_cs_end>:
+		mov	w0, w3
+		ret
+	*/
+
+	return (regs->user_regs.pstate & PSR_Z_BIT) ? NULL
+		: (void __user *) regs->user_regs.regs[2];
+}
+
+#define arch_futex_robust_unlock_get_pop(regs)	\
+	arm64_futex_robust_unlock_get_pop(regs)
+
+#endif /* _ASM_ARM64_FUTEX_ROBUST_H */
diff --git a/arch/arm64/kernel/vdso/Makefile b/arch/arm64/kernel/vdso/Makefile
index 7dec05dd33b7..a65893d8100e 100644
--- a/arch/arm64/kernel/vdso/Makefile
+++ b/arch/arm64/kernel/vdso/Makefile
@@ -9,7 +9,8 @@
 # Include the generic Makefile to check the built vdso.
 include $(srctree)/lib/vdso/Makefile.include
 
-obj-vdso := vgettimeofday.o note.o sigreturn.o vgetrandom.o vgetrandom-chacha.o
+obj-vdso := vgettimeofday.o note.o sigreturn.o vgetrandom.o vgetrandom-chacha.o \
+	    vfutex_robust_list_try_unlock.o
 
 # Build rules
 targets := $(obj-vdso) vdso.so vdso.so.dbg
@@ -45,9 +46,11 @@ CC_FLAGS_ADD_VDSO := -O2 -mcmodel=tiny -fasynchronous-unwind-tables
 
 CFLAGS_REMOVE_vgettimeofday.o = $(CC_FLAGS_REMOVE_VDSO)
 CFLAGS_REMOVE_vgetrandom.o = $(CC_FLAGS_REMOVE_VDSO)
+CFLAGS_REMOVE_vfutex_robust_list_try_unlock.o = $(CC_FLAGS_REMOVE_VDSO)
 
 CFLAGS_vgettimeofday.o = $(CC_FLAGS_ADD_VDSO)
 CFLAGS_vgetrandom.o = $(CC_FLAGS_ADD_VDSO)
+CFLAGS_vfutex_robust_list_try_unlock.o = $(CC_FLAGS_ADD_VDSO)
 
 ifneq ($(c-gettimeofday-y),)
   CFLAGS_vgettimeofday.o += -include $(c-gettimeofday-y)
@@ -57,6 +60,10 @@ ifneq ($(c-getrandom-y),)
   CFLAGS_vgetrandom.o += -include $(c-getrandom-y)
 endif
 
+ifneq ($(c-vfutex_robust_list_try_unlock-y),)
+  CFLAGS_vfutex_robust_list_try_unlock.o += -include $(c-vfutex_robust_list_try_unlock-y)
+endif
+
 targets += vdso.lds
 CPPFLAGS_vdso.lds += -P -C -U$(ARCH)
 
diff --git a/arch/arm64/kernel/vdso/vdso.lds.S b/arch/arm64/kernel/vdso/vdso.lds.S
index c532dae6ac8e..c324764cb6f9 100644
--- a/arch/arm64/kernel/vdso/vdso.lds.S
+++ b/arch/arm64/kernel/vdso/vdso.lds.S
@@ -104,6 +104,10 @@ VERSION
 		__kernel_clock_gettime;
 		__kernel_clock_getres;
 		__kernel_getrandom;
+		__vdso_futex_robust_list64_try_unlock;
+#ifdef CONFIG_COMPAT_VDSO
+		__vdso_futex_robust_list32_try_unlock;
+#endif
 	local: *;
 	};
 }
diff --git a/arch/arm64/kernel/vdso/vfutex_robust_list_try_unlock.c b/arch/arm64/kernel/vdso/vfutex_robust_list_try_unlock.c
new file mode 100644
index 000000000000..e8a8fb22a2fa
--- /dev/null
+++ b/arch/arm64/kernel/vdso/vfutex_robust_list_try_unlock.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <vdso/futex.h>
+#include <linux/stringify.h>
+
+#define LABEL(name, sz) __stringify(__futex_list##sz##_try_unlock_cs_##name)
+
+#define GLOBLS(sz) ".globl " LABEL(start, sz) ", " LABEL(success, sz) ", " LABEL(end, sz) "\n"
+
+__u32 __vdso_futex_robust_list64_try_unlock(__u32 *lock, __u32 tid, __u64 *pop)
+{
+	__u32 val, result;
+
+	asm volatile (
+		GLOBLS(64)
+		"	prfm pstl1strm, %[lock]			\n"
+		LABEL(start, 64)":				\n"
+		"	ldxr %[val], %[lock]			\n"
+		"	cmp %[tid], %[val]			\n"
+		"	bne " LABEL(end, 64)"			\n"
+		"	stlxr %w[result], xzr, %[lock]		\n"
+		"	cbnz %w[result], " LABEL(start, 64)"	\n"
+		LABEL(success, 64)":				\n"
+		"	str xzr, %[pop]				\n"
+		LABEL(end, 64)":				\n"
+
+		: [val] "=&r" (val), [result] "=r" (result)
+		: [tid] "r" (tid), [lock] "Q" (*lock), [pop] "Q" (*pop)
+		: "memory"
+	);
+
+	return val;
+}
+
+#ifdef CONFIG_COMPAT_VDSO
+__u32 __vdso_futex_robust_list32_try_unlock(__u32 *lock, __u32 tid, __u32 *pop)
+{
+	__u32 val, result;
+
+	asm volatile (
+		GLOBLS(32)
+		"	prfm pstl1strm, %[lock]			\n"
+		LABEL(start, 32)":				\n"
+		"	ldxr %w[val], %[lock]			\n"
+		"	cmp %w[tid], %w[val]			\n"
+		"	bne " LABEL(end, 32)"			\n"
+		"	stlxr %w[result], wzr, %w[lock]		\n"
+		"	cbnz %w[result], " LABEL(start, 32)"	\n"
+		LABEL(success, 32)":				\n"
+		"	str wzr, %w[pop]			\n"
+		LABEL(end, 32)":				\n"
+
+		: [val] "=&r" (val), [result] "=r" (result)
+		: [tid] "r" (tid), [lock] "Q" (*lock), [pop] "Q" (*pop)
+		: "memory"
+	);
+
+	return val;
+}
+#endif

-- 
2.53.0



^ permalink raw reply related

* [PATCH RFC v2 1/2] arm64: vdso: Prepare for robust futex unlock support
From: André Almeida @ 2026-04-24 18:56 UTC (permalink / raw)
  To: Catalin Marinas, Will Deacon, Thomas Gleixner, Mark Rutland,
	Mathieu Desnoyers, Sebastian Andrzej Siewior, Carlos O'Donell,
	Peter Zijlstra, Florian Weimer, Rich Felker, Torvald Riegel,
	Darren Hart, Ingo Molnar, Davidlohr Bueso, Arnd Bergmann,
	Liam R . Howlett, Uros Bizjak, Thomas Weißschuh
  Cc: linux-arm-kernel, linux-kernel, linux-arch, kernel-dev, LKML,
	André Almeida
In-Reply-To: <20260424-tonyk-robust_arm-v2-0-db4e46f752cf@igalia.com>

There will be a VDSO function to unlock non-contended robust futexes in
user space. The unlock sequence is racy vs. clearing the list_pending_op
pointer in the task's robust list head. To plug this race the kernel needs
to know the critical section window so it can clear the pointer when the
task is interrupted within that race window. The window is determined by
labels in the inline assembly.

Signed-off-by: André Almeida <andrealmeid@igalia.com>
---
Changes from v1:
 - Fixed linker not finding VDSO symbols
---
 arch/arm64/kernel/vdso.c          | 30 ++++++++++++++++++++++++++++++
 arch/arm64/kernel/vdso/vdso.lds.S |  7 +++++++
 2 files changed, 37 insertions(+)

diff --git a/arch/arm64/kernel/vdso.c b/arch/arm64/kernel/vdso.c
index 592dd8668de4..f9c520a1c942 100644
--- a/arch/arm64/kernel/vdso.c
+++ b/arch/arm64/kernel/vdso.c
@@ -11,6 +11,7 @@
 #include <linux/clocksource.h>
 #include <linux/elf.h>
 #include <linux/err.h>
+#include <linux/futex.h>
 #include <linux/errno.h>
 #include <linux/gfp.h>
 #include <linux/kernel.h>
@@ -57,6 +58,33 @@ static struct vdso_abi_info vdso_info[] __ro_after_init = {
 #endif /* CONFIG_COMPAT_VDSO */
 };
 
+#ifdef CONFIG_FUTEX_ROBUST_UNLOCK
+static void vdso_futex_robust_unlock_update_ips(enum vdso_abi abi, struct mm_struct *mm)
+{
+	unsigned long vdso = (unsigned long) mm->context.vdso;
+	struct futex_mm_data *fd = &mm->futex;
+	uintptr_t success, end;
+
+	if (abi == VDSO_ABI_AA64) {
+		success = (uintptr_t) VDSO_SYMBOL(vdso, futex_list64_try_unlock_cs_success);
+		end = (uintptr_t) VDSO_SYMBOL(vdso, futex_list64_try_unlock_cs_end);
+
+		futex_set_vdso_cs_range(fd, 0, vdso, success, end, false);
+	}
+
+#ifdef CONFIG_COMPAT_VDSO
+	if (abi == VDSO_ABI_AA32) {
+		success = (uintptr_t) VDSO_SYMBOL(vdso, futex_list32_try_unlock_cs_success);
+		end = (uintptr_t) VDSO_SYMBOL(vdso, futex_list32_try_unlock_cs_end);
+
+		futex_set_vdso_cs_range(fd, 1, vdso, success, end, true);
+	}
+#endif
+}
+#else
+static inline void vdso_futex_robust_unlock_update_ips(enum vdso_abi abi, struct mm_struct *mm) { }
+#endif /* CONFIG_FUTEX_ROBUST_UNLOCK */
+
 static int vdso_mremap(const struct vm_special_mapping *sm,
 		struct vm_area_struct *new_vma)
 {
@@ -134,6 +162,8 @@ static int __setup_additional_pages(enum vdso_abi abi,
 	if (IS_ERR(ret))
 		goto up_fail;
 
+	vdso_futex_robust_unlock_update_ips(abi, mm);
+
 	return 0;
 
 up_fail:
diff --git a/arch/arm64/kernel/vdso/vdso.lds.S b/arch/arm64/kernel/vdso/vdso.lds.S
index 52314be29191..c532dae6ac8e 100644
--- a/arch/arm64/kernel/vdso/vdso.lds.S
+++ b/arch/arm64/kernel/vdso/vdso.lds.S
@@ -112,3 +112,10 @@ VERSION
  * Make the sigreturn code visible to the kernel.
  */
 VDSO_sigtramp		= __kernel_rt_sigreturn;
+
+VDSO_futex_list64_try_unlock_cs_success = __futex_list64_try_unlock_cs_success;
+VDSO_futex_list64_try_unlock_cs_end = __futex_list64_try_unlock_cs_end;
+#ifdef CONFIG_COMPAT_VDSO
+VDSO_futex_list32_try_unlock_cs_success = __futex_list32_try_unlock_cs_success;
+VDSO_futex_list32_try_unlock_cs_end = __futex_list32_try_unlock_cs_end;
+#endif

-- 
2.53.0



^ permalink raw reply related

* [PATCH RFC v2 0/2] arm64: vdso: Implement __vdso_futex_robust_try_unlock()
From: André Almeida @ 2026-04-24 18:55 UTC (permalink / raw)
  To: Catalin Marinas, Will Deacon, Thomas Gleixner, Mark Rutland,
	Mathieu Desnoyers, Sebastian Andrzej Siewior, Carlos O'Donell,
	Peter Zijlstra, Florian Weimer, Rich Felker, Torvald Riegel,
	Darren Hart, Ingo Molnar, Davidlohr Bueso, Arnd Bergmann,
	Liam R . Howlett, Uros Bizjak, Thomas Weißschuh
  Cc: linux-arm-kernel, linux-kernel, linux-arch, kernel-dev, LKML,
	André Almeida

Hi folks,

This is my take on implementing the new vDSO for unlocking a robust futex in
arm64. If you don't know what's that, Thomas wrote a good summary,
including the motivation for this work and the x86 implementation:

   https://lore.kernel.org/lkml/878qb89g7b.ffs@tglx/

There are some loose ends in my patchset so I'm sending as a RFC to ask
some questions:

 - If the process is interrupted between the labels, we need to check the
 conditional flags and clear the op_pending address from the register. Using
 objdump I see that op_pending addr is being stored at x2, but I suspect that
 this isn't stable, so I need to figure out how to make sure that the address
 will always be stored in the same register.
 - So far I have implemented only the LL/SC version to make review easier, but I
 can do the LSE version as well.

This patchset works fine with the tests proposed at
https://lore.kernel.org/lkml/20260330120118.012924430@kernel.org/

Changes from v1:
 - s/CONFIG_COMPAT/CONFIG_COMPAT_VDSO (Thomas Weißschuh)
 - Fixed linker not finding the symbols (Thomas Weißschuh)
v1: https://patch.msgid.link/20260417-tonyk-robust_arm-v1-0-03aa64e2ff1a@igalia.com

---
André Almeida (2):
      arm64: vdso: Prepare for robust futex unlock support
      arm64: vdso: Implement __vdso_futex_robust_try_unlock()

 arch/arm64/Kconfig                                 |  1 +
 arch/arm64/include/asm/futex_robust.h              | 35 +++++++++++++
 arch/arm64/kernel/vdso.c                           | 30 +++++++++++
 arch/arm64/kernel/vdso/Makefile                    |  9 +++-
 arch/arm64/kernel/vdso/vdso.lds.S                  | 11 ++++
 .../kernel/vdso/vfutex_robust_list_try_unlock.c    | 59 ++++++++++++++++++++++
 6 files changed, 144 insertions(+), 1 deletion(-)
---
base-commit: 0e8896e9899b607bb168c1cce340596b8c2e3e2b
change-id: 20260416-tonyk-robust_arm-54ff77d2c4e4

Best regards,
--  
André Almeida <andrealmeid@igalia.com>



^ permalink raw reply

* Re: [PATCH RFC 1/2] arm64: vdso: Prepare for robust futex unlock support
From: André Almeida @ 2026-04-24 18:53 UTC (permalink / raw)
  To: Thomas Weißschuh
  Cc: Catalin Marinas, Will Deacon, Thomas Gleixner, Mark Rutland,
	Mathieu Desnoyers, Sebastian Andrzej Siewior, Carlos O'Donell,
	Peter Zijlstra, Florian Weimer, Rich Felker, Torvald Riegel,
	Darren Hart, Ingo Molnar, Davidlohr Bueso, Arnd Bergmann,
	Liam R . Howlett, Uros Bizjak, linux-arm-kernel, linux-kernel,
	linux-arch, kernel-dev
In-Reply-To: <1a06fee7-65ff-4828-85f1-e3ee1e82610f@t-8ch.de>

Em 22/04/2026 10:17, Thomas Weißschuh escreveu:
> On 2026-04-22 15:02:45+0200, Thomas Weißschuh wrote:
>> On 2026-04-17 11:56:10-0300, André Almeida wrote:
>>> There will be a VDSO function to unlock non-contended robust futexes in
>>> user space. The unlock sequence is racy vs. clearing the list_pending_op
>>> pointer in the task's robust list head. To plug this race the kernel needs
>>> to know the critical section window so it can clear the pointer when the
>>> task is interrupted within that race window. The window is determined by
>>> labels in the inline assembly.
>>>
>>> Signed-off-by: André Almeida <andrealmeid@igalia.com>
>>> ---
>>> RFC: Those symbols can't be found by the linker after patch 2/2, it fails with:
>>>
>>> ld: arch/arm64/kernel/vdso.o: in function `vdso_futex_robust_unlock_update_ips':
>>> arch/arm64/kernel/vdso.c:72:(.text+0x200): undefined reference to `__futex_list64_try_unlock_cs_success'
>>> ld: arch/arm64/kernel/vdso.o: relocation R_AARCH64_ADR_PREL_PG_HI21 against symbol `__futex_list64_try_unlock_cs_success' which may bind externally can not be used when making a shared object; recompile with -fPIC
>>> arch/arm64/kernel/vdso.c:72:(.text+0x200): dangerous relocation: unsupported relocation
>>
>> arch/arm64/kernel/vdso.o is a kernel object.
>> __futex_list64_try_unlock_cs_success is a vDSO symbol.
>> They live in wholly different objects, which are never linked together.
>> Look at VDSO_SYMBOL() to get the offset of the vDSO symbol in kernel code.
>>
>> (...)
> 
> The diff below shows the idea. Compat code is not fixed, and I didn't
> even try to run it. Also please use CONFIG_COMPAT_VDSO over plain
> CONFIG_COMPAT.
> 


VDSO_SYMBOL() did the tricky, I fixed this for the v2, thanks Thomas!

	André


^ permalink raw reply

* [PATCH] EDAC/xilinx: Fix stack off-by-one in debugfs UE injection handlers
From: Shengzhuo Wei @ 2026-04-24 18:49 UTC (permalink / raw)
  To: Shubhrajyoti Datta, Sai Krishna Potthuri, Borislav Petkov,
	Tony Luck, Michal Simek
  Cc: linux-edac, linux-kernel, linux-arm-kernel, Shengzhuo Wei

Two EDAC debugfs write handlers copy up to sizeof(buf) bytes into a
fixed-size stack buffer and then unconditionally NUL-terminate it via
buf[len] = '\0'.  When userspace writes >= sizeof(buf) bytes, len
becomes sizeof(buf) and the NUL write lands 1 byte past the end of the
stack buffer.

Fix by clamping the copy length to sizeof(buf) - 1 so that the NUL
terminator is always in-bounds.

Fixes: 3bd2706c910f ("EDAC/zynqmp: Add EDAC support for Xilinx ZynqMP OCM")
Fixes: 83bf24051a60 ("EDAC/versal: Make the bit position of injected errors configurable")
Signed-off-by: Shengzhuo Wei <me@cherr.cc>
---
 drivers/edac/versal_edac.c | 2 +-
 drivers/edac/zynqmp_edac.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/edac/versal_edac.c b/drivers/edac/versal_edac.c
index 5a43b5d43ca28027c829f53aea50588297484c5c..917d7d1762aa9ec9f752e8419c24fd265048ff28 100644
--- a/drivers/edac/versal_edac.c
+++ b/drivers/edac/versal_edac.c
@@ -856,7 +856,7 @@ static ssize_t inject_data_ue_store(struct file *file, const char __user *data,
 	u8 len, ue0, ue1;
 	int i, ret;
 
-	len = min_t(size_t, count, sizeof(buf));
+	len = min_t(size_t, count, sizeof(buf) - 1);
 	if (copy_from_user(buf, data, len))
 		return -EFAULT;
 
diff --git a/drivers/edac/zynqmp_edac.c b/drivers/edac/zynqmp_edac.c
index cdffc9e4194d42d4d11c5218c9f341ac46301a94..048a7b9becd622a5eeebf9c893ffdf9e163f5e9b 100644
--- a/drivers/edac/zynqmp_edac.c
+++ b/drivers/edac/zynqmp_edac.c
@@ -304,7 +304,7 @@ static ssize_t inject_ue_write(struct file *file, const char __user *data,
 	if (!data)
 		return -EFAULT;
 
-	len = min_t(size_t, count, sizeof(buf));
+	len = min_t(size_t, count, sizeof(buf) - 1);
 	if (copy_from_user(buf, data, len))
 		return -EFAULT;
 

---
base-commit: dd6c438c3e64a5ff0b5d7e78f7f9be547803ef1b
change-id: 20260425-edac-stack-off-by-one-f6703cfe8213

Best regards,
-- 
Shengzhuo Wei <me@cherr.cc>


^ permalink raw reply related

* Re: [PATCH rc v2 2/5] iommu/arm-smmu-v3: Implement is_attach_deferred() for kdump
From: Nicolin Chen @ 2026-04-24 18:44 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: will, robin.murphy, kevin.tian, joro, praan, baolu.lu,
	miko.lenczewski, smostafa, linux-arm-kernel, iommu, linux-kernel,
	stable, jamien
In-Reply-To: <20260424165927.GD3444440@nvidia.com>

On Fri, Apr 24, 2026 at 01:59:27PM -0300, Jason Gunthorpe wrote:
> On Wed, Apr 15, 2026 at 02:17:37PM -0700, Nicolin Chen wrote:
> > +static bool arm_smmu_is_attach_deferred(struct device *dev)
> > +{
> > +	struct arm_smmu_master *master = dev_iommu_priv_get(dev);
> > +	struct arm_smmu_device *smmu = master->smmu;
> > +	int i;
> > +
> > +	if (!(smmu->options & ARM_SMMU_OPT_KDUMP))
> > +		return false;
> > +
> > +	for (i = 0; i < master->num_streams; i++) {
> > +		u32 sid = master->streams[i].id;
> > +		struct arm_smmu_ste *step;
> > +
> > +		/* Guard against unpopulated L2 entries in the adopted table */
> > +		if ((smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) &&
> > +		    !smmu->strtab_cfg.l2.l2ptrs[arm_smmu_strtab_l1_idx(sid)])
> > +			continue;
> 
> This can probably just call arm_smmu_init_sid_strtab()
> 
> I think it is OK to allocate another level 2 here and it also has
> protections for SID out of range..

Actually, sashiko pointed out that this guard is a dead code.

arm_smmu_init_sid_strtab() is called in arm_smmu_insert_master().

Thanks
Nicolin


^ permalink raw reply

* Re: [PATCH rc v2 1/5] iommu/arm-smmu-v3: Add arm_smmu_adopt_strtab() for kdump
From: Nicolin Chen @ 2026-04-24 18:33 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: will, robin.murphy, kevin.tian, joro, praan, baolu.lu,
	miko.lenczewski, smostafa, linux-arm-kernel, iommu, linux-kernel,
	stable, jamien
In-Reply-To: <20260424165613.GC3444440@nvidia.com>

On Fri, Apr 24, 2026 at 01:56:13PM -0300, Jason Gunthorpe wrote:
> On Wed, Apr 15, 2026 at 02:17:36PM -0700, Nicolin Chen wrote:
> > +static int arm_smmu_adopt_strtab_2lvl(struct arm_smmu_device *smmu, u32 cfg_reg,
[..]
> > +	cfg->l2.l1tab = devm_memremap(
> > +		smmu->dev, dma, num_l1_ents * sizeof(struct arm_smmu_strtab_l1),
> > +		MEMREMAP_WB);
> 
> WB shouldn't be unconditional? If the SMMU is working non-coherently
> we need to map it NC. Same remark everwhere

Hmm, I am trying to add a coherent-only gate for the series.

MEMREMAP_WC might work. But we cannot verify that on a coherent
SMMU, right?

Nicolin


^ permalink raw reply

* Re: [PATCH] cpu/hotplug: Fix NULL kobject warning in cpuhp_smt_enable()
From: Jonathan Cameron @ 2026-04-24 18:27 UTC (permalink / raw)
  To: Catalin Marinas
  Cc: Jinjie Ruan, Thomas Gleixner, peterz, sudeep.holla, yangyicong,
	dietmar.eggemann, linux-kernel, James Morse, linux-arm-kernel
In-Reply-To: <aetnTT51ucm2azGG@arm.com>

On Fri, 24 Apr 2026 13:51:25 +0100
Catalin Marinas <catalin.marinas@arm.com> wrote:

> (updating Jonathan's email address to match MAINTAINERS)
Thanks!
> 
> On Fri, Apr 24, 2026 at 09:56:24AM +0800, Jinjie Ruan wrote:
> > On 4/24/2026 4:11 AM, Catalin Marinas wrote:  
> > > On Thu, Apr 23, 2026 at 08:32:34PM +0800, Jinjie Ruan wrote:  
> > >> On 4/23/2026 6:08 PM, Thomas Gleixner wrote:  
> > >>> On Sat, Apr 18 2026 at 12:55, Catalin Marinas wrote:  
> > >>>> Another option would have been to avoid marking such CPUs present but I
> > >>>> think this will break other things. Yet another option is to register
> > >>>> all CPU devices even if they never come up (like maxcpus greater than
> > >>>> actual CPUs).
> > >>>>
> > >>>> Opinions? It might be an arm64+ACPI-only thing.  
> > >>>
> > >>> I think so. The proper thing to do is to apply sane limits:
> > >>>
> > >>>  1) The possible CPUs enumerated by firmware N_POSSIBLE_FW
> > >>>
> > >>>  2) The maxcpus limit on the command line N_MAXCPUS_CL
> > >>>
> > >>> So the actual possible CPUs evaluates to:
> > >>>
> > >>>    num_possible = min(N_POSSIBLE_FW, N_MAXCPUS_CL, CONFIG_NR_CPUS);
> > >>>
> > >>> The evaluation of the firmware should not mark CPUs present which are
> > >>> actually not. ACPI gives you that information. See:
> > >>>
> > >>>          5.2.12.14 GIC CPU Interface (GICC) Structure
> > >>>
> > >>> in the ACPI spec. That has two related bits:
> > >>>
> > >>> Enabled:
> > >>>
> > >>>    If this bit is set, the processor is ready for use. If this bit is
> > >>>    clear and the Online Capable bit is set, the system supports enabling
> > >>>    this processor during OS runtime. If this bit is clear and the Online
> > >>>    Capable bit is also clear, this processor is un- usable, and the
> > >>>    operating system support will not attempt to use it.
> > >>>
> > >>> Online Capable:
> > >>>
> > >>>    The information conveyed by this bit depends on the value of the
> > >>>    Enabled bit. If the Enabled bit is set, this bit is reserved and must
> > >>>    be zero. Otherwise, if this bit is set, the system supports enabling
> > >>>    this processor later during OS runtime
> > >>>
> > >>> So the combination of those gives you the right answer:
> > >>>
> > >>>    Enabled	Online
> > >>>    	        Capable
> > >>>    0            0        Not present, not possible
> > >>>    0            1        Not present, but possible to "hotplug" layter
> > >>>    1            0        Present
> > >>>    1            1        Invalid  
> > >>
> > >> On x86, it seems that all CPUs with the ACPI_MADT_ENABLED bit set will
> > >> be marked as present.
> > >>
> > >> acpi_parse_x2apic()  
> > >>   -> enabled = processor->lapic_flags & ACPI_MADT_ENABLED
> > >>   -> topology_register_apic(enabled)
> > >>      -> topo_register_apic(enabled)
> > >>         -> set_cpu_present(cpu, true)  
> > > 
> > > Yes but arm64 marks all CPUs present even if !ACPI_MADT_ENABLED as we
> > > don't have the notion of hardware CPU hotplug.
> > > 
> > > I need to dig some more into the original vCPU hotplug support and why
> > > we ended up with all CPUs marked as present even if not calling
> > > register_cpu():

James may remember more but I think there were some usecases
which broken if we reported CPUs that might turn up later as
not present.  We spent some time discussing what to do about this
on various calls, leading to just giving up and marking them present
always.

I think it was something to do with userspace code that would allocate
stuff based on the sysfs present information and die horribly when more
CPUs turned up later.

I'm not sure I ever came across that code. Maybe it was something
James had come across.  I may be completely wrong but my memory
is hinting that it was something Android related?

> > > 
> > > https://lore.kernel.org/linux-arm-kernel/20240529133446.28446-1-Jonathan.Cameron@huawei.com/
> > > 
> > > What's the MADT GICC provided by qemu with "-smp cpus=4,maxcpus=8"? If
> > > it says Enabled for the first 4 and Online Capable for the rest, maybe
> > > we can try something like below:  
> > 
> > Yes, you are absolutely right,Enabled for the first 4(with GIC Flags:
> > 0x1, bit0 set) and Online Capable for the rest(with GIC Flags: 0x8, bit3
> > set). The ACPI MADT disassembly result is as follows:  
> 
> That's great, thanks for checking.
> 
> I'd like to get some feedback from Jonathan and James as they
> contributed the vCPU hotplug support. The reason was for kubernetes to
> add vCPUs to an existing VM. Hopefully no-one relied on
> /sys/devices/system/cpu/present to report 0-7 in the above
> configuration.

I think not in the kubernetes case as such, but in general userspace
code which might run on the same systems :(

J
> 
> Have you tried the vCPU hotplug with this patch (the original use-case)?
> 
> Anyway, feel free to post a v2 with the above proposal, cc Jonathan (on
> kernel.org) and James Morse and we'll take it from there. You can add a
> suggested-by me.
> 
> Thanks.
> 



^ permalink raw reply

* Re: [PATCH 15/15] arm64: dts: ti: beagley-ai: Enable HDMI display and audio
From: Robert Nelson @ 2026-04-24 18:26 UTC (permalink / raw)
  To: Tomi Valkeinen, Andrei Aldea
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Aradhya Bhatia, Nishanth Menon, Vignesh Raghavendra,
	Swamil Jain, Devarsh Thakkar, Louis Chauvet, devicetree,
	dri-devel, linux-kernel, linux-arm-kernel, Andrew Davis
In-Reply-To: <cb19eafe-6c25-4a2a-bd31-9fb280837623@ideasonboard.com>

On Fri, Apr 24, 2026 at 12:04 PM Tomi Valkeinen
<tomi.valkeinen@ideasonboard.com> wrote:
>
> Hi,
>
> On 24/04/2026 19:16, Robert Nelson wrote:
> > On Mon, Apr 20, 2026 at 8:04 AM Tomi Valkeinen
> > <tomi.valkeinen@ideasonboard.com> wrote:
> >>
> >> From: Andrew Davis <afd@ti.com>
> >>
> >> Enable HDMI support for BeagleY-AI platform. The display controller used is
> >> TIDSS and the HDMI bridge used is IT66122.
> >>
> >> Based on DT by: Robert Nelson <robertcnelson@gmail.com>
> >> Signed-off-by: Andrew Davis <afd@ti.com>
> >> Signed-off-by: Swamil Jain <s-jain1@ti.com>
> >> [tomi.valkeinen: cosmetic fixes]
> >> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >
> > Tested-by: Robert Nelson <robertcnelson@gmail.com>
> >
> > Thank you for getting the display back end working on j722s family!
> What displays did you test? I don't have OLDI panel...

I was buggin @Andrei Aldea to get you one. I also don't have the
'special' Lincoln tech display or the TI cloned panel that works on
OLDI..  Right now just HDMI as i decided to tackle cc33xx wifi again
today on mainline..


Regards,

--
Robert Nelson
https://rcn-ee.com/


^ permalink raw reply

* Re: [PATCH v11 00/14] barrier: Add smp_cond_load_{relaxed,acquire}_timeout()
From: Ankur Arora @ 2026-04-24 18:10 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Ankur Arora, linux-kernel, linux-arch, linux-arm-kernel, linux-pm,
	bpf, arnd, catalin.marinas, will, peterz, mark.rutland, harisokn,
	cl, ast, rafael, daniel.lezcano, memxor, zhenglifeng1, xueshuai,
	rdunlap, david.laight.linux, joao.m.martins, boris.ostrovsky,
	konrad.wilk, ashok.bhat
In-Reply-To: <20260424072845.5eba94c2d732204f143b8629@linux-foundation.org>


Andrew Morton <akpm@linux-foundation.org> writes:

> On Wed,  8 Apr 2026 17:55:24 +0530 Ankur Arora <ankur.a.arora@oracle.com> wrote:
>
>> The core kernel often uses smp_cond_load_{relaxed,acquire}() to spin
>> on condition variables with architectural primitives used to avoid
>> hammering the relevant cachelines.
>>
>> ...
>>
>> Accordingly add two interfaces (with their generic and arm64 specific
>> implementations):
>>
>>    smp_cond_load_relaxed_timeout(ptr, cond_expr, time_expr, timeout)
>>    smp_cond_load_acquire_timeout(ptr, cond_expr, time_expr, timeout)
>>
>> Also add tif_need_resched_relaxed_wait() which wraps the polling
>> pattern and its scheduler specific details in poll_idle().
>> In addition add atomic_cond_read_*_timeout(),
>> atomic64_cond_read_*_timeout(), and atomic_long wrappers.
>
> AI review has been busy:
> 	https://sashiko.dev/#/patchset/20260408122538.3610871-1-ankur.a.arora@oracle.com

Quite busy. Thanks for flagging them.

From a quick look most seemed largely benign but let me go through them
in more detail.

--
ankur


^ permalink raw reply

* Re: [RFC PATCH 1/5] media: dt-bindings: Add rockchip rkisp2 bindings
From: Paul Elder @ 2026-04-24 18:05 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: michael.riesch, xuhf, stefan.klug, linux-media, linux-arm-kernel,
	linux-rockchip, linux-kernel, Heiko Stuebner
In-Reply-To: <20260424175853.638202-2-paul.elder@ideasonboard.com>

Hi me,

Quoting Paul Elder (2026-04-25 02:58:46)
> Add documentation for the Rockchip rkisp2 bindings. This is meant to
> support multiple versions of Rockchip ISPs going forward, including the
> 2.x series and 3.x series. The current version only adds the compatible
> for the RK3588, which is a 3.0.
> 
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> ---
>  .../bindings/media/rockchip-isp2.yaml         | 127 ++++++++++++++++++
>  1 file changed, 127 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/rockchip-isp2.yaml
> 
> diff --git a/Documentation/devicetree/bindings/media/rockchip-isp2.yaml b/Documentation/devicetree/bindings/media/rockchip-isp2.yaml
> new file mode 100644
> index 000000000000..f4ef089adf6e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/rockchip-isp2.yaml
> @@ -0,0 +1,127 @@
> +# SPDX-License-Identifier: (GPL-2.0+ OR MIT)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/rockchip-isp2.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Rockchip SoC Image Signal Processing unit v2
> +
> +maintainers:
> +  - Paul Elder <paul.elder@ideasonboard.com>
> +
> +description: |
> +  Rockchip ISP2 is the Camera interface for the Rockchip series of SoCs which
> +  contains image processing, scaling, and compression functions.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - rockchip,rk3588-isp
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    minItems: 3
> +    maxItems: 3

These should be 2.

> +
> +  interrupt-names:
> +    items:
> +      - const: isp_irq
> +      - const: mi_irq
> +
> +  clocks:
> +    minItems: 3
> +    items:
> +      - description: ISP AXI clock (aclk)
> +      - description: ISP AHB clock (hclk)
> +      - description: ISP core clock (isp)
> +      # for rk3588
> +      - description: ISP core clock (marvin)
> +      - description: ISP core clock (vicap)
> +
> +  clock-names:
> +    minItems: 3
> +    items:
> +      - const: aclk
> +      - const: hclk
> +      - const: clk_core
> +      # for rk3588
> +      - const: clk_core_marvin
> +      - const: clk_core_vicap
> +
> +  iommus:
> +    maxItems: 1
> +
> +  power-domains:
> +    minItems: 1

This can be removed.

> +    items:
> +      - description: ISP power domain
> +
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/$defs/port-base
> +        unevaluatedProperties: false
> +        description: connection point for VICAP in inline mode
> +
> +        properties:
> +          endpoint:
> +            $ref: video-interfaces.yaml#
> +            unevaluatedProperties: false
> +
> +    required:
> +      - port@0
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - clocks
> +  - clock-names
> +  - power-domains
> +
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: rockchip,rk3588-isp
> +    then:
> +      properties:
> +        clocks:
> +          minItems: 5
> +        clock-names:
> +          minItems: 5
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +
> +    #include <dt-bindings/clock/rockchip,rk3588-cru.h>
> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> +    #include <dt-bindings/power/rk3588-power.h>
> +
> +    parent0: parent {
> +        #address-cells = <2>;
> +        #size-cells = <2>;
> +
> +        isp0: isp@fdcb0000 {
> +            compatible = "rockchip,rk3588-isp";
> +            reg = <0x0 0xfdcb0000 0x0 0x7f00>;
> +            interrupts = <GIC_SPI 131 IRQ_TYPE_LEVEL_HIGH 0>,
> +                         <GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH 0>,
> +                         <GIC_SPI 134 IRQ_TYPE_LEVEL_HIGH 0>;
> +            interrupt-names = "isp_irq", "mi_irq";

I removed the interrupt name and forgot to remove the interrupt.

I think the same problem is present in the dt node in the next patch.


Paul

> +            clocks = <&cru ACLK_ISP0>, <&cru HCLK_ISP0>,
> +                     <&cru CLK_ISP0_CORE>, <&cru CLK_ISP0_CORE_MARVIN>,
> +                     <&cru CLK_ISP0_CORE_VICAP>;
> +            clock-names = "aclk", "hclk", "clk_core",
> +                        "clk_core_marvin", "clk_core_vicap";
> +            power-domains = <&power RK3588_PD_VI>;
> +            iommus = <&isp0_mmu>;
> +        };
> +    };
> -- 
> 2.47.2
>


^ permalink raw reply

* [RFC PATCH 5/5] media: rkisp2: Add statistics capture video node
From: Paul Elder @ 2026-04-24 17:58 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: Paul Elder, michael.riesch, xuhf, stefan.klug, linux-media,
	linux-arm-kernel, linux-rockchip, linux-kernel
In-Reply-To: <20260424175853.638202-1-paul.elder@ideasonboard.com>

Implement support for getting statistics out of the ISP by dequeueing
statistics buffers from rkisp2.

Support for configuring the statistics engines in the ISP is added as
well, implemented as configurations in parameter buffers.

Support for the following statistics engines is added:
- AE (auto exposure) lite
- Histogram lite
- Histogram big
- AWB (auto white balance)

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

I've kept the debug counters that helped me debug interrupts for the
stats engines.
---
 .../media/platform/rockchip/rkisp2/Makefile   |   3 +-
 .../platform/rockchip/rkisp2/rkisp2-common.h  |  56 ++
 .../platform/rockchip/rkisp2/rkisp2-debug.c   |  16 +
 .../platform/rockchip/rkisp2/rkisp2-dev.c     |  13 +
 .../platform/rockchip/rkisp2/rkisp2-isp.c     |   6 +
 .../platform/rockchip/rkisp2/rkisp2-params.c  | 238 +++++++++
 .../rockchip/rkisp2/rkisp2-regs-v2x.h         |  39 +-
 .../rockchip/rkisp2/rkisp2-regs-v3x.h         |  13 +
 .../platform/rockchip/rkisp2/rkisp2-stats.c   | 482 ++++++++++++++++++
 drivers/media/v4l2-core/v4l2-ioctl.c          |   1 +
 include/uapi/linux/rkisp2-config.h            | 233 ++++++++-
 include/uapi/linux/videodev2.h                |   1 +
 12 files changed, 1098 insertions(+), 3 deletions(-)
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-stats.c

diff --git a/drivers/media/platform/rockchip/rkisp2/Makefile b/drivers/media/platform/rockchip/rkisp2/Makefile
index 0fa014afcae4..8e323a560a39 100644
--- a/drivers/media/platform/rockchip/rkisp2/Makefile
+++ b/drivers/media/platform/rockchip/rkisp2/Makefile
@@ -5,7 +5,8 @@ rockchip-isp2-y := rkisp2-capture.o \
            rkisp2-dev.o \
            rkisp2-dmarx.o \
            rkisp2-isp.o \
-           rkisp2-params.o
+           rkisp2-params.o \
+           rkisp2-stats.o
 
 rockchip-isp2-$(CONFIG_DEBUG_FS) += rkisp2-debug.o
 
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h b/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
index 7473dae6c525..742954e8b569 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
@@ -91,6 +91,7 @@ enum rkisp2_isp_pad {
 	RKISP2_ISP_PAD_SINK_VIDEO,
 	RKISP2_ISP_PAD_SINK_PARAMS,
 	RKISP2_ISP_PAD_SOURCE_VIDEO,
+	RKISP2_ISP_PAD_SOURCE_STATS,
 	RKISP2_ISP_PAD_MAX
 };
 
@@ -314,6 +315,46 @@ struct rkisp2_capture {
 	struct v4l2_rect crop;
 };
 
+/*
+ * struct rkisp2_stats - ISP Statistics device
+ *
+ * @vnode:	  video node
+ * @rkisp2:	  pointer to the rkisp2 device
+ * @lock:	  locks the buffer list 'stat'
+ * @stat:	  queue of rkisp2_buffer
+ * @vdev_fmt:	  v4l2_format of the metadata format
+ * @imsc:	  interrupts that the stats is listening for (for stats 3a)
+ * @icr:	  interrupts that the stats has handled for the current frame (for stats 3a)
+ * @cur_buf:	  the current buffer to accumulate stats over multiple interrupts
+ *
+ * The imsc and icr fields are necessary to accumulate the stats because there
+ * seem to be separate interrupts in stats 3a for big modules and non-big
+ * modules. The conditions that cause separate interrupts are not yet fully
+ * understood, but is hypothesized to be big-mode & different window size
+ * compared to lite mode. We may need a similar construct for the core stats,
+ * but they have not been implemented yet (and they may not even exist).
+ *
+ * The imsc field is filled at v_start time to store what stats 3a interrupts
+ * we expect, then as they come in they are cleared by signalling in icr. The
+ * interrupt that fully sets icr (compared to imsc) will complete the stats
+ * buffer. On the next v_start any leftovers are cleared and logged as a
+ * counter to debugfs.
+ */
+struct rkisp2_stats {
+	struct rkisp2_vdev_node vnode;
+	struct rkisp2_device *rkisp2;
+
+	spinlock_t lock; /* locks the buffers list 'stats' */
+	struct list_head stat;
+	struct v4l2_format vdev_fmt;
+
+	u32 imsc;
+	u32 icr;
+	struct rkisp2_buffer *cur_buf;
+
+	unsigned int awb_window_offset;
+};
+
 /*
  * struct rkisp2_params - ISP input parameters device
  *
@@ -342,9 +383,17 @@ struct rkisp2_debug {
 	unsigned long irq_delay;
 	unsigned long mipi_error;
 	unsigned long stats_error;
+	unsigned long stats3a_irq;
+	unsigned long stats_irq_delay;
 	unsigned long stop_timeout[2];
 	unsigned long frame_drop[2];
 	unsigned long complete_frames;
+	unsigned long stats3a_hist_ch0_count;
+	unsigned long stats3a_hist_ch1_count;
+	unsigned long stats3a_hist_ch2_count;
+	unsigned long stats3a_hist_big_count;
+	unsigned long stats3a_awb_count;
+	unsigned long stats3a_awb_done_count;
 };
 
 /*
@@ -366,6 +415,7 @@ struct rkisp2_debug {
  * @capture_devs:  capture devices
  * @dmarx:	   ISP memory read device
  * @params:	   ISP parameters metadata output device
+ * @stats:	   ISP statistics metadata capture device
  * @pipe:	   media pipeline
  * @stream_lock:   serializes {start/stop}_streaming callbacks between the capture devices.
  * @debug:	   debug params to be exposed on debugfs
@@ -388,6 +438,7 @@ struct rkisp2_device {
 	struct rkisp2_capture capture_devs[2];
 	struct rkisp2_dmarx dmarx;
 	struct rkisp2_params params;
+	struct rkisp2_stats stats;
 	struct media_pipeline pipe;
 	struct mutex stream_lock; /* serialize {start/stop}_streaming cb between capture devices */
 	struct rkisp2_debug debug;
@@ -478,6 +529,8 @@ irqreturn_t rkisp2_capture_isr(int irq, void *ctx);
 irqreturn_t rkisp2_mipi_isr(int irq, void *ctx);
 void rkisp2_dmarx_isr(struct rkisp2_device *rkisp2, u32 status);
 void rkisp2_params_isr(struct rkisp2_params *params);
+void rkisp2_stats_isr_v_start(struct rkisp2_stats *);
+irqreturn_t rkisp2_stats_isr_3a(struct rkisp2_stats *);
 
 /* register/unregisters functions of the entities */
 int rkisp2_capture_devs_register(struct rkisp2_device *rkisp2);
@@ -494,6 +547,9 @@ void rkisp2_params_pre_configure(struct rkisp2_params *params,
 				 enum rkisp2_fmt_raw_pat_type bayer_pat);
 void rkisp2_params_post_configure(struct rkisp2_params *params);
 
+int rkisp2_stats_register(struct rkisp2_device *rkisp2);
+void rkisp2_stats_unregister(struct rkisp2_device *rkisp2);
+
 #if IS_ENABLED(CONFIG_DEBUG_FS)
 void rkisp2_debug_init(struct rkisp2_device *rkisp2);
 void rkisp2_debug_cleanup(struct rkisp2_device *rkisp2);
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-debug.c b/drivers/media/platform/rockchip/rkisp2/rkisp2-debug.c
index 4f439bd9156b..92beae3530c4 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-debug.c
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-debug.c
@@ -191,6 +191,10 @@ void rkisp2_debug_init(struct rkisp2_device *rkisp2)
 			     &debug->mipi_error);
 	debugfs_create_ulong("stats_error", 0444, debug->debugfs_dir,
 			     &debug->stats_error);
+	debugfs_create_ulong("stats3a_irq", 0444, debug->debugfs_dir,
+			     &debug->stats3a_irq);
+	debugfs_create_ulong("stats_irq_delay", 0444, debug->debugfs_dir,
+			     &debug->stats_irq_delay);
 	debugfs_create_ulong("mp_stop_timeout", 0444, debug->debugfs_dir,
 			     &debug->stop_timeout[RKISP2_MAINPATH]);
 	debugfs_create_ulong("sp_stop_timeout", 0444, debug->debugfs_dir,
@@ -201,6 +205,18 @@ void rkisp2_debug_init(struct rkisp2_device *rkisp2)
 			     &debug->frame_drop[RKISP2_SELFPATH]);
 	debugfs_create_ulong("complete_frames", 0444, debug->debugfs_dir,
 			     &debug->complete_frames);
+	debugfs_create_ulong("stats3a_hist_ch0_count", 0444, debug->debugfs_dir,
+			     &debug->stats3a_hist_ch0_count);
+	debugfs_create_ulong("stats3a_hist_ch1_count", 0444, debug->debugfs_dir,
+			     &debug->stats3a_hist_ch1_count);
+	debugfs_create_ulong("stats3a_hist_ch2_count", 0444, debug->debugfs_dir,
+			     &debug->stats3a_hist_ch2_count);
+	debugfs_create_ulong("stats3a_hist_big_count", 0444, debug->debugfs_dir,
+			     &debug->stats3a_hist_big_count);
+	debugfs_create_ulong("stats3a_awb_count", 0444, debug->debugfs_dir,
+			     &debug->stats3a_awb_count);
+	debugfs_create_ulong("stats3a_awb_done_count", 0444, debug->debugfs_dir,
+			     &debug->stats3a_awb_done_count);
 	debugfs_create_file("input_status", 0444, debug->debugfs_dir, rkisp2,
 			    &rkisp2_debug_input_status_fops);
 
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c b/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
index 0356ef2a1cf1..4042bf43d287 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
@@ -138,12 +138,21 @@ static int rkisp2_create_links(struct rkisp2_device *rkisp2)
 	if (ret)
 		return ret;
 
+	/* stats links */
+	ret = media_create_pad_link(&rkisp2->isp.sd.entity,
+				    RKISP2_ISP_PAD_SOURCE_STATS,
+				    &rkisp2->stats.vnode.vdev.entity, 0,
+				    MEDIA_LNK_FL_ENABLED |
+				    MEDIA_LNK_FL_IMMUTABLE);
+	if (ret)
+		return ret;
 
 	return 0;
 }
 
 static void rkisp2_entities_unregister(struct rkisp2_device *rkisp2)
 {
+	rkisp2_stats_unregister(rkisp2);
 	rkisp2_params_unregister(rkisp2);
 	rkisp2_dmarx_unregister(rkisp2);
 	rkisp2_capture_devs_unregister(rkisp2);
@@ -170,6 +179,10 @@ static int rkisp2_entities_register(struct rkisp2_device *rkisp2)
 	if (ret)
 		goto error;
 
+	ret = rkisp2_stats_register(rkisp2);
+	if (ret)
+		goto error;
+
 	ret = rkisp2_create_links(rkisp2);
 	if (ret)
 		goto error;
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c b/drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c
index 0967d5772bc9..f8f34f4f5919 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c
@@ -812,6 +812,7 @@ int rkisp2_isp_register(struct rkisp2_device *rkisp2)
 						MEDIA_PAD_FL_MUST_CONNECT;
 	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;
 
 	ret = media_entity_pads_init(&sd->entity, RKISP2_ISP_PAD_MAX, pads);
 	if (ret)
@@ -876,6 +877,9 @@ irqreturn_t rkisp2_isp_isr(int irq, void *ctx)
 	if (!status)
 		return IRQ_NONE;
 
+	/* This is in a separate register so we need to check it separately */
+	rkisp2_stats_isr_3a(&rkisp2->stats);
+
 	/* Vertical sync signal, starting generating new frame */
 	if (status & RKISP2_CIF_ISP_V_START) {
 		rkisp2->isp.frame_sequence++;
@@ -884,6 +888,8 @@ irqreturn_t rkisp2_isp_isr(int irq, void *ctx)
 			WARN_ONCE(1, "irq delay is too long, buffers might not be in sync\n");
 			rkisp2->debug.irq_delay++;
 		}
+
+		rkisp2_stats_isr_v_start(&rkisp2->stats);
 	}
 	if (status & RKISP2_CIF_ISP_PIC_SIZE_ERROR) {
 		/* Clear pic_size_error */
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-params.c b/drivers/media/platform/rockchip/rkisp2/rkisp2-params.c
index b7b27d0e90c6..b149c20a4763 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-params.c
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-params.c
@@ -33,6 +33,10 @@
 		.priority = RKISP2_PARAMS_CONFIG_PRIO_ ## prio_postfix, \
 	}
 
+/*
+ * We only need one instance of hist_big here since it's only for accessing the
+ * members of the union
+ */
 union rkisp2_params_block {
 	const struct v4l2_isp_params_block_header *header;
 	const struct rkisp2_params_bls *bls;
@@ -41,6 +45,10 @@ union rkisp2_params_block {
 	const struct rkisp2_params_ccm *ccm;
 	const struct rkisp2_params_goc *goc;
 	const struct rkisp2_params_lsc *lsc;
+	const struct rkisp2_params_ae_lite *ae_lite;
+	const struct rkisp2_params_hist_lite *hist_lite;
+	const struct rkisp2_params_hist_big *hist_big;
+	const struct rkisp2_params_awb_meas *awb_meas;
 	const __u8 *data;
 };
 
@@ -56,6 +64,14 @@ static void rkisp2_params_goc(struct rkisp2_params *params,
 			      union rkisp2_params_block block);
 static void rkisp2_params_lsc(struct rkisp2_params *params,
 			      union rkisp2_params_block block);
+static void rkisp2_params_ae_lite(struct rkisp2_params *params,
+				  union rkisp2_params_block block);
+static void rkisp2_params_hist_lite(struct rkisp2_params *params,
+				    union rkisp2_params_block block);
+static void rkisp2_params_hist_big(struct rkisp2_params *params,
+				   union rkisp2_params_block block);
+static void rkisp2_params_awb_meas(struct rkisp2_params *params,
+				   union rkisp2_params_block block);
 
 typedef void (*rkisp2_params_handler)(struct rkisp2_params *params,
 				      const union rkisp2_params_block block);
@@ -79,6 +95,12 @@ rkisp2_params_handlers[] = {
 	RKISP2_PARAMS_BLOCK_HANDLER_INFO(CCM, ccm, PRE),
 	RKISP2_PARAMS_BLOCK_HANDLER_INFO(GOC, goc, PRE),
 	RKISP2_PARAMS_BLOCK_HANDLER_INFO(LSC, lsc, POST),
+	RKISP2_PARAMS_BLOCK_HANDLER_INFO(AE_LITE, ae_lite, PRE),
+	RKISP2_PARAMS_BLOCK_HANDLER_INFO(HIST_LITE, hist_lite, PRE),
+	RKISP2_PARAMS_BLOCK_HANDLER_INFO(HIST_BIG0, hist_big, PRE),
+	RKISP2_PARAMS_BLOCK_HANDLER_INFO(HIST_BIG1, hist_big, PRE),
+	RKISP2_PARAMS_BLOCK_HANDLER_INFO(HIST_BIG2, hist_big, PRE),
+	RKISP2_PARAMS_BLOCK_HANDLER_INFO(AWB_MEAS, awb_meas, PRE),
 };
 
 static const struct v4l2_isp_params_block_type_info
@@ -89,6 +111,12 @@ rkisp2_params_block_types_info[] = {
 	RKISP2_PARAMS_BLOCK_INFO(CCM, ccm),
 	RKISP2_PARAMS_BLOCK_INFO(GOC, goc),
 	RKISP2_PARAMS_BLOCK_INFO(LSC, lsc),
+	RKISP2_PARAMS_BLOCK_INFO(AE_LITE, ae_lite),
+	RKISP2_PARAMS_BLOCK_INFO(HIST_LITE, hist_lite),
+	RKISP2_PARAMS_BLOCK_INFO(HIST_BIG0, hist_big),
+	RKISP2_PARAMS_BLOCK_INFO(HIST_BIG1, hist_big),
+	RKISP2_PARAMS_BLOCK_INFO(HIST_BIG2, hist_big),
+	RKISP2_PARAMS_BLOCK_INFO(AWB_MEAS, awb_meas),
 };
 
 static_assert(ARRAY_SIZE(rkisp2_params_handlers) ==
@@ -415,6 +443,216 @@ static void rkisp2_params_lsc(struct rkisp2_params *params,
 	rkisp2_param_set_bits(params, ISP3X_LSC_CTRL, data);
 }
 
+static void rkisp2_params_ae_lite(struct rkisp2_params *params,
+				  union rkisp2_params_block block)
+{
+	const struct rkisp2_params_ae_lite *arg = block.ae_lite;
+	u32 control;
+
+	if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) {
+		rkisp2_param_clear_bits(params, ISP_RAWAE_LITE_CTRL,
+					ISP3X_RAWAE_LITE_EN);
+		rkisp2_param_clear_bits(params, ISP_ISP3A_IMSC, ISP2X_3A_RAWAE_CH0);
+		return;
+	}
+
+	if (!(block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_ENABLE))
+		return;
+
+	/*
+	 * \todo clamp these: width and height must be even, min size is 16x4,
+	 * and total height must be < frame height
+	 */
+	rkisp2_write(params->rkisp2, ISP_RAWAE_LITE_BLK_SIZ,
+		     ISP3X_RAWAE_LITE_H(arg->meas_window.h_size) |
+		     ISP3X_RAWAE_LITE_V(arg->meas_window.v_size));
+
+	rkisp2_write(params->rkisp2, ISP_RAWAE_LITE_OFFSET,
+		     ISP3X_RAWAE_LITE_H(arg->meas_window.h_offs) |
+		     ISP3X_RAWAE_LITE_V(arg->meas_window.v_offs));
+
+	control = arg->window_num ? ISP3X_RAWAE_LITE_WNDNUM : 0;
+	control |= ISP3X_RAWAE_LITE_EN;
+
+	rkisp2_param_set_bits(params, ISP_RAWAE_LITE_CTRL,
+			      control);
+	/* TODO figure out CH1 and CH2 */
+	rkisp2_param_set_bits(params, ISP_ISP3A_IMSC, ISP2X_3A_RAWAE_CH0);
+}
+
+static void rkisp2_params_hist_lite(struct rkisp2_params *params,
+				    union rkisp2_params_block block)
+{
+	const struct rkisp2_params_hist_lite *arg = block.hist_lite;
+	unsigned int i;
+	u32 control;
+
+	if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) {
+		rkisp2_param_clear_bits(params, ISP_RAWHIST_LITE_BASE, ISP_RAWHIST_CTRL_EN);
+		rkisp2_param_clear_bits(params, ISP_ISP3A_IMSC, ISP2X_3A_RAWHIST_CH0);
+		return;
+	}
+
+	if (!(block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_ENABLE))
+		return;
+
+	rkisp2_write(params->rkisp2, ISP_RAWHIST_LITE_SIZE,
+		     ISP_RAWHIST_H_SIZE(arg->meas_window.h_size) |
+		     ISP_RAWHIST_V_SIZE(arg->meas_window.v_size));
+
+	rkisp2_write(params->rkisp2, ISP_RAWHIST_LITE_OFFS,
+		     ISP_RAWHIST_H_OFFS(arg->meas_window.h_offs) |
+		     ISP_RAWHIST_V_OFFS(arg->meas_window.v_offs));
+
+	rkisp2_write(params->rkisp2, ISP_RAWHIST_LITE_RAW2Y_CC,
+		     ISP_RAWHIST_RAW2Y_CC_RCC(arg->coeffs.r) |
+		     ISP_RAWHIST_RAW2Y_CC_GCC(arg->coeffs.g) |
+		     ISP_RAWHIST_RAW2Y_CC_BCC(arg->coeffs.b));
+
+	for (i = 0; i < 6; i++) {
+		rkisp2_write(params->rkisp2, ISP_RAWHIST_LITE_WEIGHT + 4 * i,
+			     ISP_RAWHIST_LITE_WEIGHT_WND0(arg->weights[4 * i + 0]) |
+			     ISP_RAWHIST_LITE_WEIGHT_WND1(arg->weights[4 * i + 1]) |
+			     ISP_RAWHIST_LITE_WEIGHT_WND2(arg->weights[4 * i + 2]) |
+			     ISP_RAWHIST_LITE_WEIGHT_WND3(arg->weights[4 * i + 3]));
+	}
+
+	rkisp2_write(params->rkisp2, ISP_RAWHIST_LITE_WEIGHT + 4 * 6,
+		     ISP_RAWHIST_LITE_WEIGHT_WND0(arg->weights[24]));
+
+	control = ISP_RAWHIST_CTRL_EN |
+		  ISP_RAWHIST_CTRL_STEPSIZE(arg->stepsize) |
+		  ISP_RAWHIST_CTRL_MODE(arg->mode) |
+		  ISP_RAWHIST_CTRL_WATERLINE(arg->waterline) |
+		  ISP_RAWHIST_CTRL_DATA_SEL(arg->data_sel);
+	rkisp2_write(params->rkisp2, ISP_RAWHIST_LITE_CTRL, control);
+	// I think you can choose the channels in VI_ISP_PATH
+	rkisp2_param_set_bits(params, ISP_ISP3A_IMSC, ISP2X_3A_RAWHIST_CH0);
+}
+
+static void rkisp2_params_hist_big(struct rkisp2_params *params,
+				   union rkisp2_params_block block)
+{
+	const struct rkisp2_params_hist_big *arg = block.hist_big;
+	unsigned int i, reg_base, ctrl;
+	u32 control;
+
+	switch (block.header->type) {
+	case RKISP2_PARAMS_BLOCK_HIST_BIG0:
+		reg_base = ISP_RAWHIST_BIG1_BASE;
+		break;
+	case RKISP2_PARAMS_BLOCK_HIST_BIG1:
+		reg_base = ISP_RAWHIST_BIG2_BASE;
+		break;
+	case RKISP2_PARAMS_BLOCK_HIST_BIG2:
+		reg_base = ISP_RAWHIST_BIG3_BASE;
+		break;
+	default:
+		WARN_ONCE(1, "invalid big histogram base %x\n", reg_base);
+		return;
+	}
+
+	ctrl = reg_base + ISP_RAWHIST_BIG_CTRL;
+
+	if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) {
+		rkisp2_param_clear_bits(params, ctrl, ISP_RAWHIST_CTRL_EN);
+		rkisp2_param_clear_bits(params, ISP_ISP3A_IMSC, ISP2X_3A_RAWHIST_BIG);
+		return;
+	}
+
+	if (!(block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_ENABLE))
+		return;
+
+	rkisp2_write(params->rkisp2, reg_base + ISP_RAWHIST_BIG_SIZE,
+		     ISP_RAWHIST_H_SIZE(arg->meas_window.h_size) |
+		     ISP_RAWHIST_V_SIZE(arg->meas_window.v_size));
+
+	rkisp2_write(params->rkisp2, reg_base + ISP_RAWHIST_BIG_OFFS,
+		     ISP_RAWHIST_H_OFFS(arg->meas_window.h_offs) |
+		     ISP_RAWHIST_V_OFFS(arg->meas_window.v_offs));
+
+	rkisp2_write(params->rkisp2, reg_base + ISP_RAWHIST_BIG_RAW2Y_CC,
+		     ISP_RAWHIST_RAW2Y_CC_RCC(arg->coeffs.r) |
+		     ISP_RAWHIST_RAW2Y_CC_GCC(arg->coeffs.g) |
+		     ISP_RAWHIST_RAW2Y_CC_BCC(arg->coeffs.b));
+
+	/*
+	 * TODO check if the other weights are programmed in the registers
+	 * after the bins, or if it's sequential write
+	 */
+	for (i = 0; i < 16; i++) {
+		rkisp2_write(params->rkisp2, reg_base + ISP_RAWHIST_BIG_WEIGHT_BASE + 4 * i,
+			     ISP_RAWHIST_BIG_WEIGHT_WND0(arg->weights[5 * i + 0]) |
+			     ISP_RAWHIST_BIG_WEIGHT_WND1(arg->weights[5 * i + 1]) |
+			     ISP_RAWHIST_BIG_WEIGHT_WND2(arg->weights[5 * i + 2]) |
+			     ISP_RAWHIST_BIG_WEIGHT_WND3(arg->weights[5 * i + 3]) |
+			     ISP_RAWHIST_BIG_WEIGHT_WND4(arg->weights[5 * i + 4]));
+	}
+
+	rkisp2_param_set_bits(params, ISP_CTRL1, ISP21_BIGMODE_MODE | ISP21_BIGMODE_FORCE_EN);
+
+	control = ISP_RAWHIST_CTRL_EN |
+		  ISP_RAWHIST_CTRL_STEPSIZE(arg->stepsize) |
+		  ISP_RAWHIST_CTRL_MODE(arg->mode) |
+		  ISP_RAWHIST_CTRL_WATERLINE(arg->waterline) |
+		  ISP_RAWHIST_CTRL_DATA_SEL(arg->data_sel) |
+		  (arg->window_num & ISP_RAWHIST_BIG_CTRL_WND_MASK);
+	rkisp2_write(params->rkisp2, ctrl, control);
+	rkisp2_param_set_bits(params, ISP_ISP3A_IMSC, ISP2X_3A_RAWHIST_BIG);
+}
+
+static void rkisp2_params_awb_meas(struct rkisp2_params *params,
+				   union rkisp2_params_block block)
+{
+	const struct rkisp2_params_awb_meas *arg = block.awb_meas;
+	unsigned int i;
+	u32 val;
+	u32 control = 0;
+
+	if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) {
+		rkisp2_param_clear_bits(params, ISP3X_RAWAWB_CTRL,
+					ISP3X_RAWAWB_CTRL_EN);
+		rkisp2_param_clear_bits(params, ISP_ISP3A_IMSC, ISP2X_3A_RAWAWB);
+		rkisp2_param_clear_bits(params, ISP21_RAWAWB_BLK_CTRL, 0x1);
+		return;
+	}
+
+	if (!(block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_ENABLE))
+		return;
+
+	rkisp2_write(params->rkisp2, ISP21_RAWAWB_WIN_OFFS,
+		     ISP3X_RAWAWB_WIN(arg->meas_window.h_offs,
+				    arg->meas_window.v_offs));
+	rkisp2_write(params->rkisp2, ISP21_RAWAWB_WIN_SIZE,
+		     ISP3X_RAWAWB_WIN(arg->meas_window.h_size,
+				    arg->meas_window.v_size));
+
+	rkisp2_write(params->rkisp2, ISP21_RAWAWB_LIMIT_RG_MAX,
+		     ISP3X_RAWAWB_LIMITS(arg->limits[1].r, arg->limits[1].g));
+	rkisp2_write(params->rkisp2, ISP21_RAWAWB_LIMIT_BY_MAX,
+		     ISP3X_RAWAWB_LIMITS(arg->limits[1].b, arg->limits[1].y));
+	rkisp2_write(params->rkisp2, ISP21_RAWAWB_LIMIT_RG_MIN,
+		     ISP3X_RAWAWB_LIMITS(arg->limits[0].r, arg->limits[0].g));
+	rkisp2_write(params->rkisp2, ISP21_RAWAWB_LIMIT_BY_MIN,
+		     ISP3X_RAWAWB_LIMITS(arg->limits[0].b, arg->limits[0].y));
+
+	for (i = 0; i < RKISP2_ISP_AWB_COUNTS_SIZE / 5; i++) {
+		val = (arg->weights[5 * i] & 0x3f)
+		    | ((arg->weights[5 * i + 1] & 0x3f) << 6)
+		    | ((arg->weights[5 * i + 2] & 0x3f) << 12)
+		    | ((arg->weights[5 * i + 3] & 0x3f) << 18)
+		    | ((arg->weights[5 * i + 4] & 0x3f) << 24);
+		rkisp2_write(params->rkisp2, ISP21_RAWAWB_WRAM_DATA_BASE, val);
+	}
+
+	// This options looks like it's required to get "useful" data out
+	rkisp2_param_set_bits(params, ISP21_RAWAWB_BLK_CTRL, 0x1);
+
+	control |= ISP3X_RAWAWB_CTRL_EN;
+	rkisp2_write(params->rkisp2, ISP3X_RAWAWB_CTRL, control);
+	rkisp2_param_set_bits(params, ISP_ISP3A_IMSC, ISP2X_3A_RAWAWB);
+}
+
 static void rkisp2_params_configure(struct rkisp2_params *params,
 				    struct rkisp2_params_buffer *buf,
 				    enum rkisp2_params_configure_priority prio)
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v2x.h b/drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v2x.h
index 883bdb7c2a61..26deacb0b63d 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v2x.h
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v2x.h
@@ -1429,7 +1429,7 @@
 #define ISP_RAWAE_LITE_BLK_SIZ			(ISP_RAWAE_LITE_BASE + 0x00004)
 #define ISP_RAWAE_LITE_OFFSET			(ISP_RAWAE_LITE_BASE + 0x00008)
 #define ISP_RAWAE_LITE_R2Y_CC			(ISP_RAWAE_LITE_BASE + 0x0000c)
-#define ISP_RAWAE_LITE_RO_MEAN			(ISP_RAWAE_LITE_BASE + 0x00010)
+#define ISP_RAWAE_LITE_RO_MEAN(i)		(ISP_RAWAE_LITE_BASE + 0x00010 + ((i) * 4))
 #define ISP_RAWAE_LITE_RO_DBG1			(ISP_RAWAE_LITE_BASE + 0x00074)
 #define ISP_RAWAE_LITE_RO_DBG2			(ISP_RAWAE_LITE_BASE + 0x00078)
 
@@ -2585,4 +2585,41 @@
 /* ISP21 DHAZ/DRC/BAY3D */
 #define ISP21_SELF_FORCE_UPD		BIT(31)
 
+/* RAWHIST */
+#define ISP_RAWHIST_CTRL_EN			BIT(0)
+#define ISP_RAWHIST_CTRL_STEPSIZE(a)		(((a) & 0x7) << 1)
+#define ISP_RAWHIST_CTRL_MODE(a)		(((a) & 0x7) << 8)
+#define ISP_RAWHIST_CTRL_WATERLINE(a)		(((a) & 0xfff) << 12)
+#define ISP_RAWHIST_CTRL_DATA_SEL(a)		(((a) & 0x7) << 24)
+#define ISP_RAWHIST_CTRL_MEAS_DONE		BIT(31)
+#define ISP_RAWHIST_BIG_CTRL_WND_5X5_1		BIT(28)
+#define ISP_RAWHIST_BIG_CTRL_WND_15X15_0	BIT(29)
+#define ISP_RAWHIST_BIG_CTRL_WND_15X15_1	(0x3 << 28)
+#define ISP_RAWHIST_BIG_CTRL_WND_MASK		GENMASK(29, 28)
+
+#define ISP_RAWHIST_H_SIZE(a)			((a) & 0x7ff)
+#define ISP_RAWHIST_V_SIZE(a)			(((a) & 0x7ff) << 16)
+#define ISP_RAWHIST_H_OFFS(a)			((a) & 0x1fff)
+#define ISP_RAWHIST_V_OFFS(a)			(((a) & 0x1fff) << 16)
+
+#define ISP_RAWHIST_RAW2Y_CC_RCC(a)		((a) & 0xff)
+#define ISP_RAWHIST_RAW2Y_CC_GCC(a)		(((a) & 0xff) << 8)
+#define ISP_RAWHIST_RAW2Y_CC_BCC(a)		(((a) & 0xff) << 16)
+#define ISP_RAWHIST_RAW2Y_CC_OFF(a)		(((a) & 0xff) << 24)
+
+#define ISP_RAWHIST_LITE_WEIGHT_WND0(a)		((a) & 0x3f)
+#define ISP_RAWHIST_LITE_WEIGHT_WND1(a)		(((a) & 0x3f) << 8)
+#define ISP_RAWHIST_LITE_WEIGHT_WND2(a)		(((a) & 0x3f) << 16)
+#define ISP_RAWHIST_LITE_WEIGHT_WND3(a)		(((a) & 0x3f) << 24)
+
+#define ISP_RAWHIST_BIG_WEIGHT_WND0(a)		((a) & 0x3f)
+#define ISP_RAWHIST_BIG_WEIGHT_WND1(a)		(((a) & 0x3f) << 6)
+#define ISP_RAWHIST_BIG_WEIGHT_WND2(a)		(((a) & 0x3f) << 12)
+#define ISP_RAWHIST_BIG_WEIGHT_WND3(a)		(((a) & 0x3f) << 18)
+#define ISP_RAWHIST_BIG_WEIGHT_WND4(a)		(((a) & 0x3f) << 24)
+
+#define ISP_RAWHIST_BIN_MASK 			GENMASK(27, 0)
+
+#define ISP_RAWHIST_RAM_OFFSET(a)		((a) & 0xff)
+
 #endif /* _RKISP_REGS_V2X_H */
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v3x.h b/drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v3x.h
index d27d9a48887e..73dc3b22873c 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v3x.h
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v3x.h
@@ -3367,6 +3367,13 @@
 #define ISP3X_RAWAE_BIG_WND3_EN		BIT(6)
 #define ISP3X_RAWAE_BIG_WND4_EN		BIT(7)
 
+#define ISP3X_RAWAE_LITE_H(a)			((a) & 0x1fff)
+#define ISP3X_RAWAE_LITE_V(a)			(((a) & 0x1fff) << 16)
+
+#define ISP3X_RAWAE_LITE_RO_MEAN_R(a)		(((a) >> 22) & 0x3ff)
+#define ISP3X_RAWAE_LITE_RO_MEAN_B(a)		(((a) >> 12) & 0x3ff)
+#define ISP3X_RAWAE_LITE_RO_MEAN_G(a)		((a) & 0xfff)
+
 /* RAWHIST */
 #define ISP3X_RAWHIST_EN		BIT(0)
 #define ISP3X_RAWHIST_STEPSIZE(x)	(((x) & 0x7) << 1)
@@ -3397,6 +3404,12 @@
 #define ISP3X_RAWAF_INTLINE0_EN		BIT(27)
 
 /* RAWAWB */
+#define ISP3X_RAWAWB_CTRL_EN		BIT(0)
+#define ISP3X_RAWAWB_CTRL_MEAS_DONE	BIT(31)
+
+#define ISP3X_RAWAWB_WIN(h, v)		(((h) & 0x1fff) | (((v) & 0x1fff) << 16))
+#define ISP3X_RAWAWB_LIMITS(a, b)		(((a) & 0xff) | (((b) & 0xff) << 16))
+
 #define ISP32_RAWAWB_2DDR_PATH_EN	BIT(23)
 #define ISP32_RAWAWB_2DDR_PATH_DS	BIT(27)
 #define ISP32_RAWAWB_2DDR_PATH_ERR	BIT(29)
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-stats.c b/drivers/media/platform/rockchip/rkisp2/rkisp2-stats.c
new file mode 100644
index 000000000000..f9dbe05bf218
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-stats.c
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: (GPL-2.0-or-later OR MIT)
+/*
+ * Rockchip ISP2 Driver - Stats subdevice
+ *
+ * Copyright (C) 2017 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2026 Ideas on Board Oy.
+ */
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "rkisp2-common.h"
+
+#define RKISP2_STATS_DEV_NAME	RKISP2_DRIVER_NAME "_stats"
+
+static int rkisp2_stats_enum_fmt_meta_cap(struct file *file, void *priv,
+					  struct v4l2_fmtdesc *f)
+{
+	if (f->index)
+		return -EINVAL;
+
+	f->pixelformat = V4L2_META_FMT_RKISP2_STATS;
+
+	return 0;
+}
+
+static int rkisp2_stats_g_fmt_meta_cap(struct file *file, void *priv,
+				       struct v4l2_format *f)
+{
+	static const struct v4l2_meta_format mfmt = {
+		.dataformat = V4L2_META_FMT_RKISP2_STATS,
+		.buffersize = sizeof(struct rkisp2_stats_buffer)
+	};
+
+	f->fmt.meta = mfmt;
+
+	return 0;
+}
+
+static int rkisp2_stats_querycap(struct file *file,
+				 void *priv, struct v4l2_capability *cap)
+{
+	struct video_device *vdev = video_devdata(file);
+
+	strscpy(cap->driver, RKISP2_DRIVER_NAME, sizeof(cap->driver));
+	strscpy(cap->card, vdev->name, sizeof(cap->card));
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops rkisp2_stats_ioctl = {
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+	.vidioc_enum_fmt_meta_cap = rkisp2_stats_enum_fmt_meta_cap,
+	.vidioc_g_fmt_meta_cap = rkisp2_stats_g_fmt_meta_cap,
+	.vidioc_s_fmt_meta_cap = rkisp2_stats_g_fmt_meta_cap,
+	.vidioc_try_fmt_meta_cap = rkisp2_stats_g_fmt_meta_cap,
+	.vidioc_querycap = rkisp2_stats_querycap,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_file_operations rkisp2_stats_fops = {
+	.mmap = vb2_fop_mmap,
+	.unlocked_ioctl = video_ioctl2,
+	.poll = vb2_fop_poll,
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release
+};
+
+static int rkisp2_stats_vb2_queue_setup(struct vb2_queue *vq,
+					unsigned int *num_buffers,
+					unsigned int *num_planes,
+					unsigned int sizes[],
+					struct device *alloc_devs[])
+{
+	/* TODO num_buffers */
+
+	*num_planes = 1;
+
+	sizes[0] = sizeof(struct rkisp2_stats_buffer);
+
+	return 0;
+}
+
+static void rkisp2_stats_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct rkisp2_buffer *stats_buf =
+		container_of(vbuf, struct rkisp2_buffer, vb);
+	struct vb2_queue *vq = vb->vb2_queue;
+	struct rkisp2_stats *stats_dev = vq->drv_priv;
+
+	spin_lock_irq(&stats_dev->lock);
+	list_add_tail(&stats_buf->queue, &stats_dev->stat);
+	spin_unlock_irq(&stats_dev->lock);
+}
+
+static int rkisp2_stats_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+	if (vb2_plane_size(vb, 0) < sizeof(struct rkisp2_stats_buffer))
+		return -EINVAL;
+
+	vb2_set_plane_payload(vb, 0, sizeof(struct rkisp2_stats_buffer));
+
+	return 0;
+}
+
+static int rkisp2_stats_vb2_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct rkisp2_stats *stats = vq->drv_priv;
+	stats->imsc = 0;
+	stats->icr = 0;
+	stats->cur_buf = NULL;
+	stats->awb_window_offset = 0;
+
+	return 0;
+}
+
+static void rkisp2_stats_vb2_stop_streaming(struct vb2_queue *vq)
+{
+	struct rkisp2_stats *stats = vq->drv_priv;
+	struct rkisp2_buffer *buf;
+	LIST_HEAD(tmp_list);
+
+	spin_lock_irq(&stats->lock);
+	list_splice_init(&stats->stat, &tmp_list);
+	spin_unlock_irq(&stats->lock);
+
+	list_for_each_entry(buf, &tmp_list, queue)
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+
+	if (stats->cur_buf != NULL) {
+		vb2_buffer_done(&stats->cur_buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		stats->cur_buf = NULL;
+	}
+}
+
+static const struct vb2_ops rkisp2_stats_vb2_ops = {
+	.queue_setup = rkisp2_stats_vb2_queue_setup,
+	.buf_queue = rkisp2_stats_vb2_buf_queue,
+	.buf_prepare = rkisp2_stats_vb2_buf_prepare,
+	.start_streaming = rkisp2_stats_vb2_start_streaming,
+	.stop_streaming = rkisp2_stats_vb2_stop_streaming,
+};
+
+static int
+rkisp2_stats_init_vb2_queue(struct vb2_queue *q, struct rkisp2_stats *stats)
+{
+	struct rkisp2_vdev_node *node;
+
+	node = container_of(q, struct rkisp2_vdev_node, buf_queue);
+
+	q->type = V4L2_BUF_TYPE_META_CAPTURE;
+	q->io_modes = VB2_MMAP | VB2_DMABUF;
+	q->drv_priv = stats;
+	q->ops = &rkisp2_stats_vb2_ops;
+	q->mem_ops = &vb2_dma_contig_memops;
+	q->buf_struct_size = sizeof(struct rkisp2_buffer);
+	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	q->lock = &node->vlock;
+	q->dev = stats->rkisp2->dev;
+
+	return vb2_queue_init(q);
+}
+
+static void rkisp2_stats_get_rawae_lite(struct rkisp2_stats *stats,
+					u32 status,
+					struct rkisp2_stats_buffer *pbuf)
+{
+	struct rkisp2_device *rkisp2 = stats->rkisp2;
+	struct rkisp2_isp_ae_lite *ae_lite;
+	unsigned int i;
+	u32 val;
+
+	/* TODO figure out a nicer way to synchronize this with params and imsc */
+	/* TODO figure out what the other channels are for */
+	if (!(status & ISP2X_3A_RAWAE_CH0))
+		return;
+	stats->icr |= ISP2X_3A_RAWAE_CH0;
+
+	ae_lite = &pbuf->ae_lite;
+
+	for (i = 0; i < RKISP2_ISP_AE_MEAN_MAX_LITE; i++) {
+		val = rkisp2_read(rkisp2, ISP_RAWAE_LITE_RO_MEAN(i));
+		ae_lite->exp_mean_r[i] = ISP3X_RAWAE_LITE_RO_MEAN_R(val);
+		ae_lite->exp_mean_g[i] = ISP3X_RAWAE_LITE_RO_MEAN_G(val);
+		ae_lite->exp_mean_b[i] = ISP3X_RAWAE_LITE_RO_MEAN_B(val);
+	}
+
+	/*
+	 * The done bit is never set in the register; set it here to signal
+	 * done to userspace
+	 */
+	ae_lite->done = 1;
+}
+
+static void rkisp2_stats_hist_read(struct rkisp2_stats *stats, u32 reg_base,
+				   struct rkisp2_isp_hist *hist)
+{
+	struct rkisp2_device *rkisp2 = stats->rkisp2;
+	unsigned int i;
+	u32 val, done;
+	unsigned int ctrl = reg_base + ISP_RAWHIST_BIG_CTRL;
+
+	/*
+	 * Unlike rawae lite, this actually does get set, and we need to use it
+	 * to determine which big block to process
+	 */
+	done = rkisp2_read(rkisp2, ctrl) & ISP_RAWHIST_CTRL_MEAS_DONE;
+	hist->done = done ? 1 : 0;
+	if (!done)
+		return;
+
+	rkisp2_write(rkisp2, reg_base + ISP_RAWHIST_BIG_HRAM_CTRL,
+		     ISP_RAWHIST_RAM_OFFSET(0));
+
+	for (i = 0; i < RKISP2_ISP_HIST_BIN_N_MAX; i++) {
+		val = rkisp2_read(rkisp2, reg_base + ISP_RAWHIST_BIG_RO_BASE_BIN);
+		hist->hist_bins[i] = val;
+	}
+
+	/* Set the done bit */
+	val = rkisp2_read(rkisp2, ctrl);
+	rkisp2_write(rkisp2, ctrl, val | ISP_RAWHIST_CTRL_MEAS_DONE);
+}
+
+static void rkisp2_stats_get_hist(struct rkisp2_stats *stats,
+				  u32 status,
+				  struct rkisp2_stats_buffer *pbuf)
+{
+	/*
+	 * It seems that big only triggers the big interrupt, and lite only
+	 * triggers the ch0 interrupts. ch1 and ch2 interrupts are therefore
+	 * unknown, and we need to check the done bits of every big module to
+	 * determine which to process
+	 */
+
+	/*
+	 * It looks like we get separate interrupts for big in big-mode (ie.
+	 * using the 15x15 grid) and for non-big stuff
+	 */
+
+	if (status & ISP2X_3A_RAWHIST_CH0) {
+		stats->rkisp2->debug.stats3a_hist_ch0_count++;
+		stats->icr |= ISP2X_3A_RAWHIST_CH0;
+		rkisp2_stats_hist_read(stats, ISP_RAWHIST_LITE_BASE, &pbuf->hist_lite);
+	}
+
+	if (status & ISP2X_3A_RAWHIST_BIG) {
+		stats->rkisp2->debug.stats3a_hist_big_count++;
+		stats->icr |= ISP2X_3A_RAWHIST_BIG;
+		rkisp2_stats_hist_read(stats, ISP_RAWHIST_BIG1_BASE, &pbuf->hist_big0);
+		rkisp2_stats_hist_read(stats, ISP_RAWHIST_BIG2_BASE, &pbuf->hist_big1);
+		rkisp2_stats_hist_read(stats, ISP_RAWHIST_BIG3_BASE, &pbuf->hist_big2);
+	}
+
+}
+
+static void rkisp2_stats_get_rawawb(struct rkisp2_stats *stats,
+				    u32 status,
+				    struct rkisp2_stats_buffer *pbuf)
+{
+	struct rkisp2_device *rkisp2 = stats->rkisp2;
+	struct rkisp2_isp_awb *awb = &pbuf->awb;
+	unsigned int ctrl = ISP21_RAWAWB_CTRL;
+	unsigned int i;
+	u32 val1, val2;
+	u32 done;
+
+	if (!(status & ISP2X_3A_RAWAWB))
+		return;
+	stats->icr |= ISP2X_3A_RAWAWB;
+
+	rkisp2->debug.stats3a_awb_count++;
+
+	done = rkisp2_read(rkisp2, ctrl) & ISP3X_RAWAWB_CTRL_MEAS_DONE;
+	awb->done = done ? 1 : 0;
+	if (!done)
+		return;
+
+	rkisp2->debug.stats3a_awb_done_count++;
+
+	for (i = 0; i < RKISP2_ISP_AWB_COUNTS_SIZE; i++) {
+		val1 = rkisp2_read(rkisp2, ISP21_RAWAWB_RAM_DATA_BASE);
+		val2 = rkisp2_read(rkisp2, ISP21_RAWAWB_RAM_DATA_BASE);
+		awb->counts_r[i] = (val2 >> 4) & 0x3ffff;
+		awb->counts_g[i] = (val1 >> 18) | ((val2 & 0xf) << 14);
+		awb->counts_b[i] = val1 & 0x3ffff;
+		awb->counts_w[i] = val2 >> 22;
+	}
+
+	/*
+	 * Clear the done bit (reference says write 0 but writing 1 seems to be
+	 * the correct reset, plus all the other stats 3a write 1 to reset)
+	 */
+	val1 = rkisp2_read(rkisp2, ctrl);
+	rkisp2_write(rkisp2, ctrl, val1 | ISP3X_RAWAWB_CTRL_MEAS_DONE);
+}
+
+/* This is always called in an intterupt context */
+static struct rkisp2_buffer *rkisp2_stats_get_buf(struct rkisp2_stats *stats)
+{
+	struct rkisp2_buffer *ret = NULL;
+
+	if (stats->cur_buf != NULL)
+		return stats->cur_buf;
+
+	/* get one empty buffer */
+	if (!list_empty(&stats->stat)) {
+		ret = list_first_entry(&stats->stat, struct rkisp2_buffer, queue);
+		list_del(&ret->queue);
+	}
+
+	stats->cur_buf = ret;
+
+	return ret;
+}
+
+static void rkisp2_stats_complete_buf(struct rkisp2_stats *stats, struct rkisp2_buffer *buf)
+{
+	unsigned int frame_sequence = stats->rkisp2->isp.frame_sequence;
+	u64 timestamp = ktime_get_ns();
+
+	vb2_set_plane_payload(&buf->vb.vb2_buf, 0,
+			      sizeof(struct rkisp2_stats_buffer));
+	buf->vb.sequence = frame_sequence;
+	buf->vb.vb2_buf.timestamp = timestamp;
+	vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+
+	stats->cur_buf = NULL;
+}
+
+static void rkisp2_stats_send_measurement_3a(struct rkisp2_stats *stats, u32 status)
+{
+	struct rkisp2_stats_buffer *cur_stat_buf;
+	struct rkisp2_buffer *cur_buf;
+
+	cur_buf = rkisp2_stats_get_buf(stats);
+	if (!cur_buf)
+		return;
+
+	cur_stat_buf = (struct rkisp2_stats_buffer *)
+			vb2_plane_vaddr(&cur_buf->vb.vb2_buf, 0);
+
+	rkisp2_stats_get_rawae_lite(stats, status, cur_stat_buf);
+
+	rkisp2_stats_get_hist(stats, status, cur_stat_buf);
+
+	rkisp2_stats_get_rawawb(stats, status, cur_stat_buf);
+
+	rkisp2_write(stats->rkisp2, ISP_ISP3A_ICR, stats->icr);
+
+	if (stats->imsc == stats->icr) {
+		rkisp2_stats_complete_buf(stats, cur_buf);
+		stats->icr = 0;
+	}
+}
+
+void rkisp2_stats_isr_v_start(struct rkisp2_stats *stats)
+{
+	if (stats->imsc != stats->icr && stats->cur_buf != NULL) {
+		WARN_ONCE(1, "unhandled stats irq: expected %x got %x\n",
+			  stats->imsc, stats->icr);
+		stats->rkisp2->debug.stats_irq_delay++;
+
+		/*
+		 * If we are in this block then it means the stats buffer has
+		 * not been completed in rkisp2_stats_send_measurement_3a, so
+		 * complete it here.
+		 */
+		spin_lock(&stats->lock);
+		rkisp2_stats_complete_buf(stats, stats->cur_buf);
+		spin_unlock(&stats->lock);
+	}
+
+	/* TODO do we need locking for these two fields? */
+	stats->imsc = rkisp2_read(stats->rkisp2, ISP_ISP3A_IMSC);
+	stats->icr = 0;
+}
+
+/* TODO maybe we can just return void */
+irqreturn_t rkisp2_stats_isr_3a(struct rkisp2_stats *stats)
+{
+	struct rkisp2_device *rkisp2 = stats->rkisp2;
+	u32 status;
+
+	status = rkisp2_read(rkisp2, ISP_ISP3A_MIS);
+	if (!status)
+		return IRQ_NONE;
+
+	spin_lock(&stats->lock);
+
+	rkisp2->debug.stats3a_irq++;
+
+	if (status & stats->imsc)
+		rkisp2_stats_send_measurement_3a(stats, status);
+
+	spin_unlock(&stats->lock);
+
+	return IRQ_HANDLED;
+}
+
+static void rkisp2_init_stats(struct rkisp2_stats *stats)
+{
+	stats->vdev_fmt.fmt.meta.dataformat =
+		V4L2_META_FMT_RKISP2_STATS;
+	stats->vdev_fmt.fmt.meta.buffersize =
+		sizeof(struct rkisp2_stats_buffer);
+}
+
+int rkisp2_stats_register(struct rkisp2_device *rkisp2)
+{
+	struct rkisp2_stats *stats = &rkisp2->stats;
+	struct rkisp2_vdev_node *node = &stats->vnode;
+	struct video_device *vdev = &node->vdev;
+	int ret;
+
+	stats->rkisp2 = rkisp2;
+	mutex_init(&node->vlock);
+	INIT_LIST_HEAD(&stats->stat);
+	spin_lock_init(&stats->lock);
+
+	strscpy(vdev->name, RKISP2_STATS_DEV_NAME, sizeof(vdev->name));
+
+	video_set_drvdata(vdev, stats);
+	vdev->ioctl_ops = &rkisp2_stats_ioctl;
+	vdev->fops = &rkisp2_stats_fops;
+	vdev->release = video_device_release_empty;
+	vdev->lock = &node->vlock;
+	vdev->v4l2_dev = &rkisp2->v4l2_dev;
+	vdev->queue = &node->buf_queue;
+	vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
+	vdev->vfl_dir =  VFL_DIR_RX;
+	rkisp2_stats_init_vb2_queue(vdev->queue, stats);
+	rkisp2_init_stats(stats);
+	video_set_drvdata(vdev, stats);
+
+	node->pad.flags = MEDIA_PAD_FL_SINK;
+	ret = media_entity_pads_init(&vdev->entity, 1, &node->pad);
+	if (ret)
+		goto error;
+
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	if (ret) {
+		dev_err(&vdev->dev,
+			"failed to register %s, ret=%d\n", vdev->name, ret);
+		goto error;
+	}
+
+	return 0;
+
+error:
+	media_entity_cleanup(&vdev->entity);
+	mutex_destroy(&node->vlock);
+	stats->rkisp2 = NULL;
+	return ret;
+}
+
+void rkisp2_stats_unregister(struct rkisp2_device *rkisp2)
+{
+	struct rkisp2_stats *stats = &rkisp2->stats;
+	struct rkisp2_vdev_node *node = &stats->vnode;
+	struct video_device *vdev = &node->vdev;
+
+	if (!stats->rkisp2)
+		return;
+
+	vb2_video_unregister_device(vdev);
+	media_entity_cleanup(&vdev->entity);
+	mutex_destroy(&node->vlock);
+}
diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
index 470e5b84ed15..1b5776487991 100644
--- a/drivers/media/v4l2-core/v4l2-ioctl.c
+++ b/drivers/media/v4l2-core/v4l2-ioctl.c
@@ -1468,6 +1468,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
 	case V4L2_META_FMT_RK_ISP1_STAT_3A:	descr = "Rockchip ISP1 3A Statistics"; break;
 	case V4L2_META_FMT_RK_ISP1_EXT_PARAMS:	descr = "Rockchip ISP1 Ext 3A Params"; break;
 	case V4L2_META_FMT_RKISP2_PARAMS:	descr = "Rockchip ISP2 3A Parameters"; break;
+	case V4L2_META_FMT_RKISP2_STATS:	descr = "Rockchip ISP2 3A Statistics"; break;
 	case V4L2_META_FMT_C3ISP_PARAMS:	descr = "Amlogic C3 ISP Parameters"; break;
 	case V4L2_META_FMT_C3ISP_STATS:		descr = "Amlogic C3 ISP Statistics"; break;
 	case V4L2_META_FMT_MALI_C55_PARAMS:	descr = "ARM Mali-C55 ISP Parameters"; break;
diff --git a/include/uapi/linux/rkisp2-config.h b/include/uapi/linux/rkisp2-config.h
index 0e42dbf2c9f2..a45fe79a7716 100644
--- a/include/uapi/linux/rkisp2-config.h
+++ b/include/uapi/linux/rkisp2-config.h
@@ -20,6 +20,17 @@
 #define RKISP2_ISP_LSC_SAMPLES_MAX		17
 #define RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX	16
 
+#define RKISP2_ISP_AE_MEAN_MAX_LITE	25
+#define RKISP2_ISP_AE_MEAN_MAX_BIG	225
+
+#define RKISP2_ISP_HIST_WEIGHT_GRIDS_SIZE_LITE	25
+#define RKISP2_ISP_HIST_WEIGHT_GRIDS_SIZE_BIG	225
+#define RKISP2_ISP_HIST_WEIGHT_GRIDS_SIZE_MAX		RKISP2_ISP_HIST_WEIGHT_GRIDS_SIZE_BIG
+
+#define RKISP2_ISP_HIST_BIN_N_MAX	256
+
+#define RKISP2_ISP_AWB_COUNTS_SIZE	225
+
 /**
  * enum rkisp2_isp_version - ISP variants
  *
@@ -55,6 +66,98 @@ enum rkisp2_isp_set_active_table_when {
 	RKISP2_ISP_LSC_SET_ACTIVE_TABLE_BEFORE,
 };
 
+enum rkisp2_isp_histogram_mode {
+	RKISP2_ISP_HISTOGRAM_MODE_DISABLE,
+	RKISP2_ISP_HISTOGRAM_MODE_R_HISTOGRAM = 2,
+	RKISP2_ISP_HISTOGRAM_MODE_G_HISTOGRAM,
+	RKISP2_ISP_HISTOGRAM_MODE_B_HISTOGRAM,
+	RKISP2_ISP_HISTOGRAM_MODE_Y_HISTOGRAM
+};
+
+/*
+ * This selects which bits are used from the input data to compute the
+ * histogram
+ */
+enum rkisp2_isp_histogram_data_sel {
+	RKISP2_ISP_HISTOGRAM_DATA_SEL_11_4,
+	RKISP2_ISP_HISTOGRAM_DATA_SEL_10_3,
+	RKISP2_ISP_HISTOGRAM_DATA_SEL_9_2,
+	RKISP2_ISP_HISTOGRAM_DATA_SEL_8_1,
+	RKISP2_ISP_HISTOGRAM_DATA_SEL_7_0,
+};
+
+/*---------- Statistics ------------*/
+
+/**
+ * struct rkisp2_isp_ae_lite - statistics auto exposure data
+ *
+ * @exp_mean_r: Mean luminance value of block xy for r channel
+ * @exp_mean_g: Mean luminance value of block xy for g channel
+ * @exp_mean_b: Mean luminance value of block xy for b channel
+ * @done: This set to nonzero when the stats are ready
+ *
+ * Image is divided into 5x5 blocks on lite and 15x15 blocks on big.
+ */
+struct rkisp2_isp_ae_lite {
+	__u16 exp_mean_r[RKISP2_ISP_AE_MEAN_MAX_LITE];
+	__u16 exp_mean_g[RKISP2_ISP_AE_MEAN_MAX_LITE];
+	__u16 exp_mean_b[RKISP2_ISP_AE_MEAN_MAX_LITE];
+	__u8 done;
+};
+
+/**
+ * struct rkisp2_cif_isp_hist_stat - statistics histogram data
+ *
+ * @hist_bins: measured bin counters. Each bin is a 28 bits unsigned fixed point
+ *	       type. Bits 0-4 are the fractional part and bits 5-27 are the
+ *	       integer part.
+ * @done: This set to nonzero when the stats are ready
+ *
+ * There are 256 bins, at least on 3.x.
+ */
+struct rkisp2_isp_hist {
+	__u32 hist_bins[RKISP2_ISP_HIST_BIN_N_MAX];
+	__u8 done;
+};
+
+/**
+ * struct rkisp2_isp_awb - statistics auto white balance data
+ *
+ * @counts_r: Counts of red (18-bits)
+ * @counts_g: Counts of green (18-bits)
+ * @counts_b: Counts of blue (18-bits)
+ * @counts_w: Counts of white point (10-bits)
+ * @done: This set to nonzero when the stats are ready
+ *
+ * TODO Figure out what is being counted
+ */
+struct rkisp2_isp_awb {
+	__u32 counts_r[RKISP2_ISP_AWB_COUNTS_SIZE];
+	__u32 counts_g[RKISP2_ISP_AWB_COUNTS_SIZE];
+	__u32 counts_b[RKISP2_ISP_AWB_COUNTS_SIZE];
+	__u16 counts_w[RKISP2_ISP_AWB_COUNTS_SIZE];
+	__u8 done;
+};
+
+/**
+ * struct rkisp2_stats_buffer - 3A statistics for the RkISP2
+ *
+ * @ae_lite: ae lite stats
+ * @hist_lite: histogram lite stats
+ * @hist_big0: histogram big0 stats
+ * @hist_big1: histogram big0 stats
+ * @hist_big2: histogram big0 stats
+ * @awb: awb stats
+ */
+struct rkisp2_stats_buffer {
+	struct rkisp2_isp_ae_lite ae_lite;
+	struct rkisp2_isp_hist hist_lite;
+	struct rkisp2_isp_hist hist_big0;
+	struct rkisp2_isp_hist hist_big1;
+	struct rkisp2_isp_hist hist_big2;
+	struct rkisp2_isp_awb awb;
+};
+
 /*---------- Parameters ------------*/
 
 /**
@@ -66,6 +169,12 @@ enum rkisp2_isp_set_active_table_when {
  * @RKISP2_PARAMS_BLOCK_CCM: Color correction matrix (in the CCM block)
  * @RKISP2_PARAMS_BLOCK_GOC: Gamma out correction
  * @RKISP2_PARAMS_BLOCK_LSC: Lens shading correction
+ * @RKISP2_PARAMS_BLOCK_AE_LITE: AE measurement config (lite)
+ * @RKISP2_PARAMS_BLOCK_HIST_LITE: Histogram measurement config (lite)
+ * @RKISP2_PARAMS_BLOCK_HIST_BIG0: Histogram measurement config (zeroth big block)
+ * @RKISP2_PARAMS_BLOCK_HIST_BIG1: Histogram measurement config (first big block)
+ * @RKISP2_PARAMS_BLOCK_HIST_BIG2: Histogram measurement config (second big block)
+ * @RKISP2_PARAMS_BLOCK_AWB_MEAS: AWB measurements config
  * */
 enum rkisp2_params_block_type {
 	RKISP2_PARAMS_BLOCK_BLS,
@@ -74,6 +183,12 @@ enum rkisp2_params_block_type {
 	RKISP2_PARAMS_BLOCK_CCM,
 	RKISP2_PARAMS_BLOCK_GOC,
 	RKISP2_PARAMS_BLOCK_LSC,
+	RKISP2_PARAMS_BLOCK_AE_LITE,
+	RKISP2_PARAMS_BLOCK_HIST_LITE,
+	RKISP2_PARAMS_BLOCK_HIST_BIG0,
+	RKISP2_PARAMS_BLOCK_HIST_BIG1,
+	RKISP2_PARAMS_BLOCK_HIST_BIG2,
+	RKISP2_PARAMS_BLOCK_AWB_MEAS,
 };
 
 /**
@@ -134,6 +249,36 @@ struct rkisp2_isp_awb_gains {
 	__u16 gb;
 };
 
+/**
+ * struct rkisp2_isp_color_cc - Color coefficients
+ *
+ * @r: Red coefficient
+ * @g: Green coefficient
+ * @b: Blue coefficient
+ */
+struct rkisp2_isp_color_cc {
+	__u8 r;
+	__u8 g;
+	__u8 b;
+};
+
+/**
+ * struct rkisp2_isp_awb_color_quad - Group of RGB and luminance for AWB
+ *
+ * TODO redesign this?
+ *
+ * @r: Red
+ * @g: Green
+ * @b: Blue
+ * @y: Y (luminance)
+ */
+struct rkisp2_isp_awb_color_quad {
+	__u8 r;
+	__u8 g;
+	__u8 b;
+	__u8 y;
+};
+
 /**
  * struct rkisp2_params_bls - RkISP2 params BLS config
  *
@@ -323,12 +468,98 @@ struct rkisp2_params_lsc {
 	__u8 window_mode;
 };
 
+/**
+ * struct rkisp2_params_ae_lite - RKISP2 params AE lite config
+ *
+ * RkISP2 parameters auto exposure measurement configuration block.
+ * Identified by :c:type:`RKISP2_PARAMS_BLOCK_AE_LITE`.
+ *
+ * TODO change window_num to enum?
+ *
+ * @header: The RkISP2 parameters block header
+ * @window_num: 0 for 1x1, 1 for 5x5
+ * @meas_window: Size of measurement window. First window for 5x5.
+ */
+struct rkisp2_params_ae_lite {
+	struct v4l2_isp_params_block_header header;
+	__u8 window_num;
+	struct rkisp2_isp_window meas_window;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp2_params_hist_lite RKISP2 params histogram lite config
+ *
+ * RkISP2 parameters histogram configuration block.
+ * Identified by :c:type:`RKISP2_PARAMS_BLOCK_HIST_LITE`.
+ *
+ * @header: The RkISP2 parameters block header
+ * @data_sel: Data selection mode (from enum rkisp2_isp_histogram_data_sel)
+ * @mode: Histogram mode (from enum rkisp2_isp_histogram_mode)
+ * @stepsize: Predivider (count every <stepsize> pixel)
+ * @waterline: Waterline for region statics
+ * @coeffs: Coefficients for raw2y formula
+ * @meas_window: Size of first measurement subwindow
+ * @weights: Weights
+ */
+struct rkisp2_params_hist_lite {
+	struct v4l2_isp_params_block_header header;
+	__u8 data_sel;
+	__u8 mode;
+	__u8 stepsize;
+	__u16 waterline;
+	struct rkisp2_isp_color_cc coeffs;
+	struct rkisp2_isp_window meas_window;
+	__u8 weights[RKISP2_ISP_HIST_WEIGHT_GRIDS_SIZE_LITE];
+} __attribute__((aligned(8)));
+
+/**
+ * Same as struct rkisp2_params_hist_lite but for big channel
+ *
+ * RkISP2 parameters histogram configuration block.
+ * Identified by :c:type:`RKISP2_PARAMS_BLOCK_HIST_BIG{0,1,2}`.
+ *
+ * @window_num: 0 or 1 for 5x5, 2 or 3 for 15x15
+ */
+struct rkisp2_params_hist_big {
+	struct v4l2_isp_params_block_header header;
+	__u8 window_num;
+	__u8 data_sel;
+	__u8 mode;
+	__u8 stepsize;
+	__u16 waterline;
+	struct rkisp2_isp_color_cc coeffs;
+	struct rkisp2_isp_window meas_window;
+	__u8 weights[RKISP2_ISP_HIST_WEIGHT_GRIDS_SIZE_BIG];
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp2_params_awb_meas - Configuration used by rawawb
+ *
+ * RkISP2 parameters AWB measurement configuration block.
+ * Identified by :c:type:`RKISP2_PARAMS_BLOCK_AWB_MEAS`.
+ *
+ * @header: The RkISP2 parameters block header
+ * @meas_window: Size of first measurement subwindow (13 bits)
+ * @limits: Limits for white point detection [min, max] (8 bits)
+ * @weights: Weights (6-bits)
+ */
+struct rkisp2_params_awb_meas {
+	struct v4l2_isp_params_block_header header;
+	struct rkisp2_isp_window meas_window;
+	struct rkisp2_isp_awb_color_quad limits[2];
+	__u8 weights[RKISP2_ISP_AWB_COUNTS_SIZE];
+};
+
 #define RKISP2_PARAMS_MAX_SIZE					\
 	(sizeof(struct rkisp2_params_bls)			+\
 	sizeof(struct rkisp2_params_awb_gains)			+\
 	sizeof(struct rkisp2_params_csm)			+\
 	sizeof(struct rkisp2_params_ccm)			+\
 	sizeof(struct rkisp2_params_goc)			+\
-	sizeof(struct rkisp2_params_lsc))
+	sizeof(struct rkisp2_params_lsc)			+\
+	sizeof(struct rkisp2_params_ae_lite)			+\
+	sizeof(struct rkisp2_params_hist_lite)			+\
+	sizeof(struct rkisp2_params_hist_big) * 3		+\
+	sizeof(struct rkisp2_params_awb_meas))
 
 #endif /* _UAPI_RKISP2_CONFIG_H */
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index 1d418a752d89..399f4d73af15 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -878,6 +878,7 @@ struct v4l2_pix_format {
 
 /* Vendor specific - used for RKISP2 camera sub-system */
 #define V4L2_META_FMT_RKISP2_PARAMS	v4l2_fourcc('R', 'K', '2', 'P') /* Rockchip ISP2 Parameters */
+#define V4L2_META_FMT_RKISP2_STATS	v4l2_fourcc('R', 'K', '2', 'S') /* Rockchip ISP2 3A Statistics */
 
 /* Vendor specific - used for C3_ISP */
 #define V4L2_META_FMT_C3ISP_PARAMS	v4l2_fourcc('C', '3', 'P', 'M') /* Amlogic C3 ISP Parameters */
-- 
2.47.2



^ permalink raw reply related

* [RFC PATCH 4/5] media: rkisp2: Add parameters output video node
From: Paul Elder @ 2026-04-24 17:58 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: Paul Elder, michael.riesch, xuhf, stefan.klug, linux-media,
	linux-arm-kernel, linux-rockchip, linux-kernel
In-Reply-To: <20260424175853.638202-1-paul.elder@ideasonboard.com>

Implement support for setting parameters on the ISP by queueing
parameter buffers to rkisp2.

Support for the following parameters is added:
- BLS (black level subtraction)
- AWB gains (color gains)
- CSM (color space conversion)
- CCM (color correction matrix)
- GOC (gamma out correction)
- LSC (lens shading correction)

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
---
 .../media/platform/rockchip/rkisp2/Makefile   |   3 +-
 .../platform/rockchip/rkisp2/rkisp2-common.h  |  50 ++
 .../platform/rockchip/rkisp2/rkisp2-dev.c     |  15 +
 .../platform/rockchip/rkisp2/rkisp2-isp.c     |  11 +-
 .../platform/rockchip/rkisp2/rkisp2-params.c  | 775 ++++++++++++++++++
 .../rockchip/rkisp2/rkisp2-regs-v3x.h         |  31 +
 drivers/media/v4l2-core/v4l2-ioctl.c          |   1 +
 include/uapi/linux/rkisp2-config.h            | 314 +++++++
 include/uapi/linux/videodev2.h                |   3 +
 9 files changed, 1201 insertions(+), 2 deletions(-)
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-params.c

diff --git a/drivers/media/platform/rockchip/rkisp2/Makefile b/drivers/media/platform/rockchip/rkisp2/Makefile
index 9a9d7b5233c4..0fa014afcae4 100644
--- a/drivers/media/platform/rockchip/rkisp2/Makefile
+++ b/drivers/media/platform/rockchip/rkisp2/Makefile
@@ -4,7 +4,8 @@ rockchip-isp2-y := rkisp2-capture.o \
            rkisp2-common.o \
            rkisp2-dev.o \
            rkisp2-dmarx.o \
-           rkisp2-isp.o
+           rkisp2-isp.o \
+           rkisp2-params.o
 
 rockchip-isp2-$(CONFIG_DEBUG_FS) += rkisp2-debug.o
 
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h b/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
index e08adfec2c50..7473dae6c525 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
@@ -22,6 +22,7 @@
 #include <media/media-entity.h>
 #include <media/v4l2-ctrls.h>
 #include <media/v4l2-device.h>
+#include <media/v4l2-isp.h>
 #include <media/videobuf2-v4l2.h>
 
 #include "rkisp2-regs.h"
@@ -88,6 +89,7 @@ enum rkisp2_fmt_raw_pat_type {
 /* enum for the isp pads */
 enum rkisp2_isp_pad {
 	RKISP2_ISP_PAD_SINK_VIDEO,
+	RKISP2_ISP_PAD_SINK_PARAMS,
 	RKISP2_ISP_PAD_SOURCE_VIDEO,
 	RKISP2_ISP_PAD_MAX
 };
@@ -182,6 +184,26 @@ struct rkisp2_buffer {
 	dma_addr_t buff_addr[VIDEO_MAX_PLANES];
 };
 
+/*
+ * struct rkisp2_params_buffer - A container for the vb2 buffers used by the
+ *				 params video device
+ *
+ * @vb:		vb2 buffer
+ * @queue:	entry of the buffer in the queue
+ * @cfg:	scratch buffer used for caching the ISP configuration parameters
+ */
+struct rkisp2_params_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct list_head queue;
+	struct v4l2_isp_params_buffer *cfg;
+};
+
+static inline struct rkisp2_params_buffer *
+to_rkisp2_params_buffer(struct vb2_v4l2_buffer *vbuf)
+{
+	return container_of(vbuf, struct rkisp2_params_buffer, vb);
+}
+
 /*
  * struct rkisp2_dummy_buffer - A buffer to write the next frame to in case
  *				there are no vb2 buffers available.
@@ -292,6 +314,25 @@ struct rkisp2_capture {
 	struct v4l2_rect crop;
 };
 
+/*
+ * struct rkisp2_params - ISP input parameters device
+ *
+ * @vnode:		video node
+ * @rkisp2:		pointer to the rkisp2 device
+ * @buf_lock:	locks the buffer list 'params'
+ * @params:		queue of rkisp2_buffer
+ * @raw_type:		the bayer pattern on the isp video sink pad
+ */
+struct rkisp2_params {
+	struct rkisp2_vdev_node vnode;
+	struct rkisp2_device *rkisp2;
+
+	spinlock_t buf_lock; /* locks the buffers list 'params' */
+	struct list_head params;
+
+	enum rkisp2_fmt_raw_pat_type raw_type;
+};
+
 struct rkisp2_debug {
 	struct dentry *debugfs_dir;
 	unsigned long data_loss;
@@ -324,6 +365,7 @@ struct rkisp2_debug {
  * @resizer_devs:  resizer sub-devices
  * @capture_devs:  capture devices
  * @dmarx:	   ISP memory read device
+ * @params:	   ISP parameters metadata output device
  * @pipe:	   media pipeline
  * @stream_lock:   serializes {start/stop}_streaming callbacks between the capture devices.
  * @debug:	   debug params to be exposed on debugfs
@@ -345,6 +387,7 @@ struct rkisp2_device {
 	struct rkisp2_isp isp;
 	struct rkisp2_capture capture_devs[2];
 	struct rkisp2_dmarx dmarx;
+	struct rkisp2_params params;
 	struct media_pipeline pipe;
 	struct mutex stream_lock; /* serialize {start/stop}_streaming cb between capture devices */
 	struct rkisp2_debug debug;
@@ -434,6 +477,7 @@ irqreturn_t rkisp2_isp_isr(int irq, void *ctx);
 irqreturn_t rkisp2_capture_isr(int irq, void *ctx);
 irqreturn_t rkisp2_mipi_isr(int irq, void *ctx);
 void rkisp2_dmarx_isr(struct rkisp2_device *rkisp2, u32 status);
+void rkisp2_params_isr(struct rkisp2_params *params);
 
 /* register/unregisters functions of the entities */
 int rkisp2_capture_devs_register(struct rkisp2_device *rkisp2);
@@ -444,6 +488,12 @@ void rkisp2_isp_unregister(struct rkisp2_device *rkisp2);
 int rkisp2_dmarx_register(struct rkisp2_device *rkisp2);
 void rkisp2_dmarx_unregister(struct rkisp2_device *rkisp2);
 
+int rkisp2_params_register(struct rkisp2_device *rkisp2);
+void rkisp2_params_unregister(struct rkisp2_device *rkisp2);
+void rkisp2_params_pre_configure(struct rkisp2_params *params,
+				 enum rkisp2_fmt_raw_pat_type bayer_pat);
+void rkisp2_params_post_configure(struct rkisp2_params *params);
+
 #if IS_ENABLED(CONFIG_DEBUG_FS)
 void rkisp2_debug_init(struct rkisp2_device *rkisp2);
 void rkisp2_debug_cleanup(struct rkisp2_device *rkisp2);
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c b/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
index 4d5c41850395..0356ef2a1cf1 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
@@ -129,11 +129,22 @@ static int rkisp2_create_links(struct rkisp2_device *rkisp2)
 			return ret;
 	}
 
+	/* params links */
+	ret = media_create_pad_link(&rkisp2->params.vnode.vdev.entity, 0,
+				    &rkisp2->isp.sd.entity,
+				    RKISP2_ISP_PAD_SINK_PARAMS,
+				    MEDIA_LNK_FL_ENABLED |
+				    MEDIA_LNK_FL_IMMUTABLE);
+	if (ret)
+		return ret;
+
+
 	return 0;
 }
 
 static void rkisp2_entities_unregister(struct rkisp2_device *rkisp2)
 {
+	rkisp2_params_unregister(rkisp2);
 	rkisp2_dmarx_unregister(rkisp2);
 	rkisp2_capture_devs_unregister(rkisp2);
 	rkisp2_isp_unregister(rkisp2);
@@ -155,6 +166,10 @@ static int rkisp2_entities_register(struct rkisp2_device *rkisp2)
 	if (ret)
 		goto error;
 
+	ret = rkisp2_params_register(rkisp2);
+	if (ret)
+		goto error;
+
 	ret = rkisp2_create_links(rkisp2);
 	if (ret)
 		goto error;
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c b/drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c
index 4140139da3c1..0967d5772bc9 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c
@@ -120,6 +120,10 @@ static int rkisp2_config_isp(struct rkisp2_isp *isp,
 		    RKISP2_CIF_ISP_PIC_SIZE_ERROR;
 	rkisp2_write(rkisp2, RKISP2_CIF_ISP_IMSC, irq_mask);
 
+	src_frm = v4l2_subdev_state_get_format(sd_state,
+					       RKISP2_ISP_PAD_SOURCE_VIDEO);
+	rkisp2_params_pre_configure(&rkisp2->params, sink_fmt->bayer_pat);
+
 	isp->sink_fmt = sink_fmt;
 
 	return 0;
@@ -248,6 +252,8 @@ static int rkisp2_isp_start(struct rkisp2_isp *isp,
 	       RKISP2_CIF_ISP_CTRL_ISP_INFORM_ENABLE | RKISP2_CIF_ISP_CTRL_ISP_CFG_UPD_PERMANENT;
 	rkisp2_write(rkisp2, RKISP2_CIF_ISP_CTRL, val);
 
+	rkisp2_params_post_configure(&rkisp2->params);
+
 	return 0;
 }
 
@@ -804,6 +810,7 @@ int rkisp2_isp_register(struct rkisp2_device *rkisp2)
 
 	pads[RKISP2_ISP_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK |
 						MEDIA_PAD_FL_MUST_CONNECT;
+	pads[RKISP2_ISP_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
 	pads[RKISP2_ISP_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
 
 	ret = media_entity_pads_init(&sd->entity, RKISP2_ISP_PAD_MAX, pads);
@@ -893,8 +900,10 @@ irqreturn_t rkisp2_isp_isr(int irq, void *ctx)
 		rkisp2->debug.data_loss++;
 	}
 
-	if (status & RKISP2_CIF_ISP_FRAME)
+	if (status & RKISP2_CIF_ISP_FRAME) {
 		rkisp2->debug.complete_frames++;
+		rkisp2_params_isr(&rkisp2->params);
+	}
 
 	rkisp2_write(rkisp2, RKISP2_CIF_ISP_ICR, status);
 
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-params.c b/drivers/media/platform/rockchip/rkisp2/rkisp2-params.c
new file mode 100644
index 000000000000..b7b27d0e90c6
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-params.c
@@ -0,0 +1,775 @@
+// SPDX-License-Identifier: (GPL-2.0-or-later OR MIT)
+/*
+ * Rockchip ISP2 Driver - Params subdevice
+ *
+ * Copyright (C) 2017 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2026 Ideas on Board Oy.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/build_bug.h>
+#include <linux/math.h>
+#include <linux/string.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-isp.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include "rkisp2-common.h"
+
+#define RKISP2_PARAMS_DEV_NAME	RKISP2_DRIVER_NAME "_params"
+
+#define RKISP2_PARAMS_BLOCK_INFO(block, data) \
+	[RKISP2_PARAMS_BLOCK_ ## block] = { \
+		.size = sizeof(struct rkisp2_params_ ## data ), \
+	}
+
+#define RKISP2_PARAMS_BLOCK_HANDLER_INFO(block, handler_postfix, prio_postfix) \
+	[RKISP2_PARAMS_BLOCK_ ## block] = { \
+		.handler = rkisp2_params_ ## handler_postfix,\
+		.priority = RKISP2_PARAMS_CONFIG_PRIO_ ## prio_postfix, \
+	}
+
+union rkisp2_params_block {
+	const struct v4l2_isp_params_block_header *header;
+	const struct rkisp2_params_bls *bls;
+	const struct rkisp2_params_awb_gains *awb_gains;
+	const struct rkisp2_params_csm *csm;
+	const struct rkisp2_params_ccm *ccm;
+	const struct rkisp2_params_goc *goc;
+	const struct rkisp2_params_lsc *lsc;
+	const __u8 *data;
+};
+
+static void rkisp2_params_bls(struct rkisp2_params *params,
+			      union rkisp2_params_block block);
+static void rkisp2_params_awb_gains(struct rkisp2_params *params,
+				    union rkisp2_params_block block);
+static void rkisp2_params_csm(struct rkisp2_params *params,
+			      union rkisp2_params_block block);
+static void rkisp2_params_ccm(struct rkisp2_params *params,
+			      union rkisp2_params_block block);
+static void rkisp2_params_goc(struct rkisp2_params *params,
+			      union rkisp2_params_block block);
+static void rkisp2_params_lsc(struct rkisp2_params *params,
+			      union rkisp2_params_block block);
+
+typedef void (*rkisp2_params_handler)(struct rkisp2_params *params,
+				      const union rkisp2_params_block block);
+
+enum rkisp2_params_configure_priority {
+	RKISP2_PARAMS_CONFIG_PRIO_NONE = 0,
+	RKISP2_PARAMS_CONFIG_PRIO_PRE,
+	RKISP2_PARAMS_CONFIG_PRIO_POST,
+};
+
+struct rkisp2_params_block_handler_info {
+	rkisp2_params_handler handler;
+	enum rkisp2_params_configure_priority priority;
+};
+
+static const struct rkisp2_params_block_handler_info
+rkisp2_params_handlers[] = {
+	RKISP2_PARAMS_BLOCK_HANDLER_INFO(BLS, bls, PRE),
+	RKISP2_PARAMS_BLOCK_HANDLER_INFO(AWB_GAINS, awb_gains, PRE),
+	RKISP2_PARAMS_BLOCK_HANDLER_INFO(CSM, csm, PRE),
+	RKISP2_PARAMS_BLOCK_HANDLER_INFO(CCM, ccm, PRE),
+	RKISP2_PARAMS_BLOCK_HANDLER_INFO(GOC, goc, PRE),
+	RKISP2_PARAMS_BLOCK_HANDLER_INFO(LSC, lsc, POST),
+};
+
+static const struct v4l2_isp_params_block_type_info
+rkisp2_params_block_types_info[] = {
+	RKISP2_PARAMS_BLOCK_INFO(BLS, bls),
+	RKISP2_PARAMS_BLOCK_INFO(AWB_GAINS, awb_gains),
+	RKISP2_PARAMS_BLOCK_INFO(CSM, csm),
+	RKISP2_PARAMS_BLOCK_INFO(CCM, ccm),
+	RKISP2_PARAMS_BLOCK_INFO(GOC, goc),
+	RKISP2_PARAMS_BLOCK_INFO(LSC, lsc),
+};
+
+static_assert(ARRAY_SIZE(rkisp2_params_handlers) ==
+	      ARRAY_SIZE(rkisp2_params_block_types_info));
+
+static inline void
+rkisp2_param_set_bits(struct rkisp2_params *params, u32 reg, u32 bit_mask)
+{
+	u32 val;
+
+	val = rkisp2_read(params->rkisp2, reg);
+	rkisp2_write(params->rkisp2, reg, val | bit_mask);
+}
+
+static inline void
+rkisp2_param_clear_bits(struct rkisp2_params *params, u32 reg, u32 bit_mask)
+{
+	u32 val;
+
+	val = rkisp2_read(params->rkisp2, reg);
+	rkisp2_write(params->rkisp2, reg, val & ~bit_mask);
+}
+
+static void rkisp2_params_bls(struct rkisp2_params *params,
+			      union rkisp2_params_block block)
+{
+	const struct rkisp2_params_bls *arg = block.bls;
+	u32 control;
+
+	if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) {
+		rkisp2_param_clear_bits(params, ISP_BLS_CTRL, ISP_BLS_ENA | ISP_BLS_BLS1_EN);
+		return;
+	}
+
+	if (!(block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_ENABLE))
+		return;
+
+	control = ISP_BLS_ENA;
+
+	if (!arg->enable_auto) {
+		/* TODO plumb BLS1 */
+
+		rkisp2_write(params->rkisp2, ISP_BLS_A_FIXED, arg->bls_fixed_val.a);
+		rkisp2_write(params->rkisp2, ISP_BLS_B_FIXED, arg->bls_fixed_val.b);
+		rkisp2_write(params->rkisp2, ISP_BLS_C_FIXED, arg->bls_fixed_val.c);
+		rkisp2_write(params->rkisp2, ISP_BLS_D_FIXED, arg->bls_fixed_val.d);
+
+		/* Set fixed mode */
+		rkisp2_param_clear_bits(params, ISP_BLS_CTRL,
+					ISP_BLS_MODE_MEASURED);
+		return;
+	}
+
+	if (arg->enabled_windows & BIT(1)) {
+		rkisp2_write(params->rkisp2, ISP_BLS_H2_START,
+			     arg->bls_window2.h_offs);
+		rkisp2_write(params->rkisp2, ISP_BLS_H2_STOP,
+			     arg->bls_window2.h_size);
+		rkisp2_write(params->rkisp2, ISP_BLS_V2_START,
+			     arg->bls_window2.v_offs);
+		rkisp2_write(params->rkisp2, ISP_BLS_V2_STOP,
+			     arg->bls_window2.v_size);
+		control |= ISP_BLS_WINDOW_2;
+	}
+
+	if (arg->enabled_windows & BIT(0)) {
+		rkisp2_write(params->rkisp2, ISP_BLS_H1_START,
+			     arg->bls_window1.h_offs);
+		rkisp2_write(params->rkisp2, ISP_BLS_H1_STOP,
+			     arg->bls_window1.h_size);
+		rkisp2_write(params->rkisp2, ISP_BLS_V1_START,
+			     arg->bls_window1.v_offs);
+		rkisp2_write(params->rkisp2, ISP_BLS_V1_STOP,
+			     arg->bls_window1.v_size);
+		control |= ISP_BLS_WINDOW_1;
+	}
+
+	rkisp2_write(params->rkisp2, ISP_BLS_SAMPLES,
+		     arg->bls_samples);
+
+	control |= ISP_BLS_MODE_MEASURED;
+	rkisp2_write(params->rkisp2, ISP_BLS_CTRL, control);
+}
+
+static void rkisp2_params_awb_gains(struct rkisp2_params *params,
+				    union rkisp2_params_block block)
+{
+	const struct rkisp2_params_awb_gains *arg = block.awb_gains;
+	unsigned int i;
+
+	if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) {
+		rkisp2_param_clear_bits(params, RKISP2_CIF_ISP_CTRL,
+					RKISP2_CIF_ISP_CTRL_ISP_AWB_ENA);
+		return;
+	}
+
+	if (!(block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_ENABLE))
+		return;
+
+	for (i = 0; i < ARRAY_SIZE(arg->gains); i++) {
+		rkisp2_write(params->rkisp2, ISP21_AWB_GAIN0_G + i * 8,
+			     arg->gains[i].gr << 16 | arg->gains[i].gb);
+		rkisp2_write(params->rkisp2, ISP21_AWB_GAIN0_RB + i * 8,
+			     arg->gains[i].r << 16 | arg->gains[i].b);
+	}
+
+	rkisp2_param_set_bits(params, RKISP2_CIF_ISP_CTRL,
+			      RKISP2_CIF_ISP_CTRL_ISP_AWB_ENA);
+}
+
+static void rkisp2_params_csm_reset(struct rkisp2_params *params)
+{
+	/* Write back the default values. */
+	rkisp2_write(params->rkisp2, RKISP2_CIF_ISP_CC_COEFF_0, 0x80);
+	rkisp2_write(params->rkisp2, RKISP2_CIF_ISP_CC_COEFF_1, 0);
+	rkisp2_write(params->rkisp2, RKISP2_CIF_ISP_CC_COEFF_2, 0);
+	rkisp2_write(params->rkisp2, RKISP2_CIF_ISP_CC_COEFF_3, 0);
+	rkisp2_write(params->rkisp2, RKISP2_CIF_ISP_CC_COEFF_4, 0x80);
+	rkisp2_write(params->rkisp2, RKISP2_CIF_ISP_CC_COEFF_5, 0);
+	rkisp2_write(params->rkisp2, RKISP2_CIF_ISP_CC_COEFF_6, 0);
+	rkisp2_write(params->rkisp2, RKISP2_CIF_ISP_CC_COEFF_7, 0);
+	rkisp2_write(params->rkisp2, RKISP2_CIF_ISP_CC_COEFF_8, 0x80);
+}
+
+static void rkisp2_params_csm(struct rkisp2_params *params,
+			      union rkisp2_params_block block)
+{
+	const struct rkisp2_params_csm *arg = block.csm;
+	unsigned int i, j, k = 0;
+
+	if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) {
+		rkisp2_params_csm_reset(params);
+		return;
+	}
+
+	if (!(block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_ENABLE))
+		return;
+
+	for (i = 0; i < 3; i++)
+		for (j = 0; j < 3; j++)
+			rkisp2_write(params->rkisp2,
+				     RKISP2_CIF_ISP_CC_COEFF_0 + 4 * k++,
+				     arg->coeff[i][j]);
+}
+
+static void rkisp2_params_ccm(struct rkisp2_params *params,
+			      union rkisp2_params_block block)
+{
+	const struct rkisp2_params_ccm *arg = block.ccm;
+	unsigned int i;
+	u32 control = 0;
+
+	if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) {
+		rkisp2_param_clear_bits(params, ISP_CCM_CTRL, ISP_CCM_EN);
+		return;
+	}
+
+	if (!(block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_ENABLE))
+		return;
+
+	for (i = 0; i < 3; i++) {
+		rkisp2_write(params->rkisp2, ISP_CCM_COEFF0_R + 8 * i,
+			     ISP3X_CCM_COEFF(arg->coeff[i][0], arg->coeff[i][1]));
+		rkisp2_write(params->rkisp2, ISP_CCM_COEFF1_R + 8 * i,
+			     ISP3X_CCM_COEFF(arg->coeff[i][2], arg->offset[i]));
+	}
+
+	rkisp2_write(params->rkisp2, ISP_CCM_COEFF0_Y,
+		     ISP3X_CCM_COEFF(arg->y_coeff[0], arg->y_coeff[1]));
+	rkisp2_write(params->rkisp2, ISP_CCM_COEFF1_Y, arg->y_coeff[2]);
+
+	for (i = 0; i < 8; i++)
+		rkisp2_write(params->rkisp2, ISP_CCM_ALP_Y0 + 4 * i,
+			     ISP3X_CCM_COEFF(arg->alp[2 * i], arg->alp[2 * i + 1]));
+	rkisp2_write(params->rkisp2, ISP_CCM_ALP_Y8, arg->alp[16]);
+
+	rkisp2_write(params->rkisp2, ISP_CCM_BOUND_BIT, arg->inflection_point);
+
+	if (!arg->high_y_alpha_adj_en)
+		control = ISP3X_CCM_HIGHY_ADJ_DIS;
+	control |= ISP_CCM_EN;
+	rkisp2_write(params->rkisp2, ISP_CCM_CTRL, control);
+}
+
+static void rkisp2_params_goc(struct rkisp2_params *params,
+			      union rkisp2_params_block block)
+{
+	const struct rkisp2_params_goc *arg = block.goc;
+	unsigned int i;
+	u32 control = 0;
+
+	if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) {
+		rkisp2_param_clear_bits(params, ISP3X_GAMMA_OUT_CTRL,
+					ISP3X_GAMMA_OUT_CTRL_EN);
+		return;
+	}
+
+	if (!(block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_ENABLE))
+		return;
+
+	for (i = 0; i < RKISP2_ISP_GAMMA_OUT_MAX_SEGMENTS >> 1; i++)
+		rkisp2_write(params->rkisp2,
+			     ISP3X_GAMMA_OUT_Y0 + 4 * i,
+			     ISP3X_GAMMA_OUT_SAMPLE(arg->gamma_y[2 * i],
+						  arg->gamma_y[2 * i + 1]));
+
+	rkisp2_write(params->rkisp2, ISP3X_GAMMA_OUT_Y24,
+		     arg->gamma_y[RKISP2_ISP_GAMMA_OUT_MAX_SEGMENTS - 1]);
+
+	rkisp2_write(params->rkisp2, ISP3X_GAMMA_OUT_OFFSET, arg->offset);
+
+	if (arg->mode == RKISP2_ISP_GOC_MODE_EQUIDISTANT)
+		control = ISP3X_GAMMA_OUT_CTRL_MODE_EQUIDISTANT;
+	if (arg->mode == RKISP2_ISP_GOC_SEGMENTS_48)
+		control |= ISP3X_GAMMA_OUT_CTRL_SEGMENTS_48;
+	control |= ISP3X_GAMMA_OUT_CTRL_EN;
+	rkisp2_write(params->rkisp2, ISP3X_GAMMA_OUT_CTRL, control);
+}
+
+static void rkisp2_params_lsc(struct rkisp2_params *params,
+			      union rkisp2_params_block block)
+{
+	const struct rkisp2_params_lsc *arg = block.lsc;
+	struct rkisp2_device *rkisp2 = params->rkisp2;
+	u32 sram_addr;
+	u32 data;
+	unsigned int i, j, table_i;
+
+	if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) {
+		rkisp2_param_clear_bits(params, ISP3X_LSC_CTRL, ISP3X_LSC_CTRL_EN);
+		return;
+	}
+
+	if (!(block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_ENABLE))
+		return;
+
+	if (arg->set_active_table_when == RKISP2_ISP_LSC_SET_ACTIVE_TABLE_BEFORE)
+		rkisp2_write(rkisp2, ISP3X_LSC_TABLE_SEL, arg->active_table ? 1 : 0);
+
+	/*
+	 * - No need to disable the lsc before writing the table 
+	 * - Table 0 starts at 0, table 1 starts at 153.
+	 * - TABLE_SEL selects which table is active, but programming the tables
+	 *   is done by just writing to the right address.
+	 * - The address automatically increments, so no need to increment it.
+	 */
+
+	for (table_i = 0; table_i < 2; table_i++) {
+		if (!arg->write_table[table_i])
+			continue;
+
+		sram_addr = table_i * 153;
+
+		rkisp2_write(rkisp2, ISP3X_LSC_R_TABLE_ADDR, sram_addr);
+		rkisp2_write(rkisp2, ISP3X_LSC_GR_TABLE_ADDR, sram_addr);
+		rkisp2_write(rkisp2, ISP3X_LSC_B_TABLE_ADDR, sram_addr);
+		rkisp2_write(rkisp2, ISP3X_LSC_GB_TABLE_ADDR, sram_addr);
+
+		/* Program data tables (table size is 9 * 17 = 153) */
+		for (i = 0; i < RKISP2_ISP_LSC_SAMPLES_MAX; i++) {
+			const __u16 *r_row = arg->r_data_tbl[table_i][i];
+			const __u16 *gr_row = arg->gr_data_tbl[table_i][i];
+			const __u16 *gb_row = arg->gb_data_tbl[table_i][i];
+			const __u16 *b_row = arg->b_data_tbl[table_i][i];
+
+			/*
+			 * 17 sectors with 2 values in one DWORD = 9
+			 * DWORDs (2nd value of last DWORD unused)
+			 */
+			for (j = 0; j < RKISP2_ISP_LSC_SAMPLES_MAX / 2; j++) {
+				rkisp2_write(rkisp2, ISP3X_LSC_R_TABLE_DATA,
+					     ISP3X_LSC_TABLE_DATA(r_row[2 * j], r_row[2 * j + 1]));
+				rkisp2_write(rkisp2, ISP3X_LSC_GR_TABLE_DATA,
+					     ISP3X_LSC_TABLE_DATA(gr_row[2 * j], gr_row[2 * j + 1]));
+				rkisp2_write(rkisp2, ISP3X_LSC_GB_TABLE_DATA,
+					     ISP3X_LSC_TABLE_DATA(gb_row[2 * j], gb_row[2 * j + 1]));
+				rkisp2_write(rkisp2, ISP3X_LSC_B_TABLE_DATA,
+					     ISP3X_LSC_TABLE_DATA(b_row[2 * j], b_row[2 * j + 1]));
+			}
+
+			rkisp2_write(rkisp2, ISP3X_LSC_R_TABLE_DATA,
+				     ISP3X_LSC_TABLE_DATA(r_row[2 * j], 0));
+			rkisp2_write(rkisp2, ISP3X_LSC_GR_TABLE_DATA,
+				     ISP3X_LSC_TABLE_DATA(gr_row[2 * j], 0));
+			rkisp2_write(rkisp2, ISP3X_LSC_GB_TABLE_DATA,
+				     ISP3X_LSC_TABLE_DATA(gb_row[2 * j], 0));
+			rkisp2_write(rkisp2, ISP3X_LSC_B_TABLE_DATA,
+				     ISP3X_LSC_TABLE_DATA(b_row[2 * j], 0));
+
+		}
+	}
+
+	/* Program grid sizes and interpolation gradients */
+	for (i = 0; i < RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX / 2; i++) {
+		/* program x size tables */
+		data = ISP3X_LSC_SECT_SIZE(arg->x_sizes[i * 2],
+					   arg->x_sizes[i * 2 + 1]);
+		rkisp2_write(rkisp2, ISP3X_LSC_XSIZE(i), data);
+
+		/* program x grad tables */
+		data = ISP3X_LSC_GRAD_SIZE(arg->x_grads[i * 2],
+					   arg->x_grads[i * 2 + 1]);
+		rkisp2_write(rkisp2, ISP3X_LSC_XGRAD(i), data);
+
+		/* program y size tables */
+		data = ISP3X_LSC_SECT_SIZE(arg->y_sizes[i * 2],
+					   arg->y_sizes[i * 2 + 1]);
+		rkisp2_write(rkisp2, ISP3X_LSC_YSIZE(i), data);
+
+		/* program y grad tables */
+		data = ISP3X_LSC_GRAD_SIZE(arg->y_grads[i * 2],
+					   arg->y_grads[i * 2 + 1]);
+		rkisp2_write(rkisp2, ISP3X_LSC_YGRAD(i), data);
+	}
+
+	rkisp2_write(rkisp2, ISP3X_LSC_TABLE_SEL, arg->active_table ? 1 : 0);
+
+	data = 0;
+	if (arg->window_mode)
+		data |= ISP3X_LSC_SECTOR_16X16;
+	/* TODO plumb the rest of the ctrl fields */
+	data |= ISP3X_LSC_CTRL_EN;
+
+	rkisp2_param_set_bits(params, ISP3X_LSC_CTRL, data);
+}
+
+static void rkisp2_params_configure(struct rkisp2_params *params,
+				    struct rkisp2_params_buffer *buf,
+				    enum rkisp2_params_configure_priority prio)
+{
+	struct v4l2_isp_params_buffer *cfg;
+	const struct rkisp2_params_block_handler_info *info;
+	size_t block_offset = 0;
+	size_t max_offset;
+
+	buf->vb.sequence = params->rkisp2->isp.frame_sequence + 1;
+	cfg = buf->cfg;
+
+	max_offset = cfg->data_size;
+
+	/* Walk the list of parameter blocks and process them. */
+	while (max_offset && block_offset < max_offset) {
+		union rkisp2_params_block block;
+
+		/* \todo Check if we want to avoid this copy */
+		block.data = &cfg->data[block_offset];
+
+		block_offset += block.header->size;
+
+		info = &rkisp2_params_handlers[block.header->type];
+
+		if (prio != RKISP2_PARAMS_CONFIG_PRIO_NONE &&
+		    prio != info->priority)
+			continue;
+
+		info->handler(params, block);
+	}
+
+	/* update shadow register immediately */
+	rkisp2_param_set_bits(params, RKISP2_CIF_ISP_CTRL,
+			      RKISP2_CIF_ISP_CTRL_ISP_CFG_UPD);
+
+	if (prio == RKISP2_PARAMS_CONFIG_PRIO_NONE)
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+}
+
+void rkisp2_params_isr(struct rkisp2_params *params)
+{
+	struct rkisp2_params_buffer *buf;
+
+	spin_lock(&params->buf_lock);
+	buf = list_first_entry_or_null(&params->params,
+				       struct rkisp2_params_buffer, queue);
+	if (buf)
+		list_del(&buf->queue);
+	spin_unlock(&params->buf_lock);
+
+	if (!buf)
+		return;
+
+	rkisp2_params_configure(params, buf, RKISP2_PARAMS_CONFIG_PRIO_NONE);
+}
+
+void rkisp2_params_pre_configure(struct rkisp2_params *params,
+				 enum rkisp2_fmt_raw_pat_type bayer_pat)
+{
+	struct rkisp2_params_buffer *buf;
+
+	params->raw_type = bayer_pat;
+
+	spin_lock_irq(&params->buf_lock);
+	buf = list_first_entry_or_null(&params->params,
+				       struct rkisp2_params_buffer, queue);
+	spin_unlock_irq(&params->buf_lock);
+
+	if (!buf)
+		return;
+
+	rkisp2_params_configure(params, buf, RKISP2_PARAMS_CONFIG_PRIO_PRE);
+}
+
+void rkisp2_params_post_configure(struct rkisp2_params *params)
+{
+	struct rkisp2_params_buffer *buf;
+
+	spin_lock_irq(&params->buf_lock);
+	buf = list_first_entry_or_null(&params->params,
+				       struct rkisp2_params_buffer, queue);
+	if (buf)
+		list_del(&buf->queue);
+	spin_unlock_irq(&params->buf_lock);
+
+	if (!buf)
+		return;
+
+	rkisp2_params_configure(params, buf, RKISP2_PARAMS_CONFIG_PRIO_POST);
+}
+
+/*** V4L2 ***/
+
+static int rkisp2_params_enum_fmt_meta_out(struct file *file, void *fh,
+					   struct v4l2_fmtdesc *f)
+{
+	if (f->index)
+		return -EINVAL;
+
+	if (f->mbus_code && f->mbus_code != MEDIA_BUS_FMT_METADATA_FIXED)
+		return -EINVAL;
+
+	f->pixelformat = V4L2_META_FMT_RKISP2_PARAMS;
+
+	return 0;
+}
+
+static int rkisp2_params_g_fmt_meta_out(struct file *file, void *fh,
+					struct v4l2_format *f)
+{
+
+	static const struct v4l2_meta_format mfmt = {
+		.dataformat = V4L2_META_FMT_RKISP2_PARAMS,
+		.buffersize = v4l2_isp_params_buffer_size(RKISP2_PARAMS_MAX_SIZE),
+	};
+
+	f->fmt.meta = mfmt;
+
+	return 0;
+}
+
+static int rkisp2_params_querycap(struct file *file,
+				  void *priv, struct v4l2_capability *cap)
+{
+	struct video_device *vdev = video_devdata(file);
+
+	strscpy(cap->driver, RKISP2_DRIVER_NAME, sizeof(cap->driver));
+	strscpy(cap->card, vdev->name, sizeof(cap->card));
+	strscpy(cap->bus_info, RKISP2_BUS_INFO, sizeof(cap->bus_info));
+
+	return 0;
+}
+
+/* ISP params video device IOCTLs */
+static const struct v4l2_ioctl_ops rkisp2_params_ioctl = {
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+	.vidioc_enum_fmt_meta_out = rkisp2_params_enum_fmt_meta_out,
+	.vidioc_g_fmt_meta_out = rkisp2_params_g_fmt_meta_out,
+	.vidioc_s_fmt_meta_out = rkisp2_params_g_fmt_meta_out,
+	.vidioc_try_fmt_meta_out = rkisp2_params_g_fmt_meta_out,
+	.vidioc_querycap = rkisp2_params_querycap,
+};
+
+static int rkisp2_params_vb2_queue_setup(struct vb2_queue *vq,
+					 unsigned int *num_buffers,
+					 unsigned int *num_planes,
+					 unsigned int sizes[],
+					 struct device *alloc_devs[])
+{
+	/* \todo num_buffers? */
+
+	*num_planes = 1;
+
+	sizes[0] = v4l2_isp_params_buffer_size(RKISP2_PARAMS_MAX_SIZE);
+
+	return 0;
+}
+
+static int rkisp2_params_vb2_buf_init(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct rkisp2_params_buffer *params_buf = to_rkisp2_params_buffer(vbuf);
+
+	params_buf->cfg = kvmalloc(v4l2_isp_params_buffer_size(RKISP2_PARAMS_MAX_SIZE),
+				   GFP_KERNEL);
+	if (!params_buf->cfg)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static void rkisp2_params_vb2_buf_cleanup(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct rkisp2_params_buffer *params_buf = to_rkisp2_params_buffer(vbuf);
+
+	kvfree(params_buf->cfg);
+	params_buf->cfg = NULL;
+}
+
+static void rkisp2_params_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct rkisp2_params_buffer *params_buf = to_rkisp2_params_buffer(vbuf);
+	struct vb2_queue *vq = vb->vb2_queue;
+	struct rkisp2_params *params = vq->drv_priv;
+
+	spin_lock_irq(&params->buf_lock);
+	list_add_tail(&params_buf->queue, &params->params);
+	spin_unlock_irq(&params->buf_lock);
+}
+
+static int rkisp2_params_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+	struct rkisp2_params *params = vb->vb2_queue->drv_priv;
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct rkisp2_params_buffer *params_buf = to_rkisp2_params_buffer(vbuf);
+	struct v4l2_isp_params_buffer *cfg = vb2_plane_vaddr(&vbuf->vb2_buf, 0);
+	size_t payload_size = vb2_get_plane_payload(vb, 0);
+	int ret;
+
+	ret = v4l2_isp_params_validate_buffer_size(params->rkisp2->dev, vb,
+						   v4l2_isp_params_buffer_size(RKISP2_PARAMS_MAX_SIZE));
+	if (ret)
+		return ret;
+
+	/*
+	 * Copy the parameters buffer to the internal scratch buffer to avoid
+	 * userspace modifying the buffer content while the driver processes it.
+	 */
+	memcpy(params_buf->cfg, cfg, payload_size);
+
+	return v4l2_isp_params_validate_buffer(params->rkisp2->dev, vb, cfg,
+				rkisp2_params_block_types_info,
+				ARRAY_SIZE(rkisp2_params_block_types_info));
+}
+
+static void rkisp2_params_vb2_stop_streaming(struct vb2_queue *vq)
+{
+	struct rkisp2_params *params = vq->drv_priv;
+	struct rkisp2_params_buffer *buf;
+	LIST_HEAD(tmp_list);
+
+	/*
+	 * we first move the buffers into a local list 'tmp_list'
+	 * and then we can iterate it and call vb2_buffer_done
+	 * without holding the lock
+	 */
+	spin_lock_irq(&params->buf_lock);
+	list_splice_init(&params->params, &tmp_list);
+	spin_unlock_irq(&params->buf_lock);
+
+	list_for_each_entry(buf, &tmp_list, queue)
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+}
+
+static const struct vb2_ops rkisp2_params_vb2_ops = {
+	.queue_setup = rkisp2_params_vb2_queue_setup,
+	.buf_init = rkisp2_params_vb2_buf_init,
+	.buf_cleanup = rkisp2_params_vb2_buf_cleanup,
+	.buf_queue = rkisp2_params_vb2_buf_queue,
+	.buf_prepare = rkisp2_params_vb2_buf_prepare,
+	.stop_streaming = rkisp2_params_vb2_stop_streaming,
+};
+
+static const struct v4l2_file_operations rkisp2_params_fops = {
+	.mmap = vb2_fop_mmap,
+	.unlocked_ioctl = video_ioctl2,
+	.poll = vb2_fop_poll,
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release
+};
+
+static int rkisp2_params_link_validate(struct media_link *link)
+{
+	/* \todo implement this */
+	return 0;
+}
+
+static const struct media_entity_operations rkisp2_params_media_ops = {
+	.link_validate = rkisp2_params_link_validate,
+};
+
+static int rkisp2_params_init_vb2_queue(struct vb2_queue *q,
+					struct rkisp2_params *params)
+{
+	struct rkisp2_vdev_node *node;
+
+	node = container_of(q, struct rkisp2_vdev_node, buf_queue);
+
+	q->type = V4L2_BUF_TYPE_META_OUTPUT;
+	q->io_modes = VB2_MMAP | VB2_DMABUF;
+	q->drv_priv = params;
+	q->ops = &rkisp2_params_vb2_ops;
+	q->mem_ops = &vb2_vmalloc_memops;
+	q->buf_struct_size = sizeof(struct rkisp2_params_buffer);
+	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	q->lock = &node->vlock;
+
+	return vb2_queue_init(q);
+}
+
+int rkisp2_params_register(struct rkisp2_device *rkisp2)
+{
+	struct rkisp2_params *params = &rkisp2->params;
+	struct rkisp2_vdev_node *node = &params->vnode;
+	struct video_device *vdev = &node->vdev;
+	int ret;
+
+	params->rkisp2 = rkisp2;
+	mutex_init(&node->vlock);
+	INIT_LIST_HEAD(&params->params);
+	spin_lock_init(&params->buf_lock);
+
+	strscpy(vdev->name, RKISP2_PARAMS_DEV_NAME, sizeof(vdev->name));
+
+	video_set_drvdata(vdev, params);
+	vdev->ioctl_ops = &rkisp2_params_ioctl;
+	vdev->fops = &rkisp2_params_fops;
+	vdev->release = video_device_release_empty;
+	/*
+	 * Provide a mutex to v4l2 core. It will be used
+	 * to protect all fops and v4l2 ioctls.
+	 */
+	vdev->lock = &node->vlock;
+	vdev->v4l2_dev = &rkisp2->v4l2_dev;
+	vdev->queue = &node->buf_queue;
+	vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_META_OUTPUT;
+	vdev->entity.ops = &rkisp2_params_media_ops;
+	vdev->vfl_dir = VFL_DIR_TX;
+	ret = rkisp2_params_init_vb2_queue(vdev->queue, params);
+	if (ret)
+		goto err_media;
+
+	video_set_drvdata(vdev, params);
+
+	node->pad.flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&vdev->entity, 1, &node->pad);
+	if (ret)
+		goto err_media;
+
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	if (ret) {
+		dev_err(rkisp2->dev,
+			"failed to register %s, ret=%d\n", vdev->name, ret);
+		return ret;
+	}
+
+	return 0;
+
+err_media:
+	media_entity_cleanup(&vdev->entity);
+	mutex_destroy(&node->vlock);
+	return ret;
+}
+
+void rkisp2_params_unregister(struct rkisp2_device *rkisp2)
+{
+	struct rkisp2_params *params = &rkisp2->params;
+	struct rkisp2_vdev_node *node = &params->vnode;
+	struct video_device *vdev = &node->vdev;
+
+	if (!video_is_registered(vdev))
+		return;
+
+	vb2_video_unregister_device(vdev);
+	media_entity_cleanup(&vdev->entity);
+	mutex_destroy(&node->vlock);
+}
diff --git a/drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v3x.h b/drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v3x.h
index bb79d08d206f..d27d9a48887e 100644
--- a/drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v3x.h
+++ b/drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v3x.h
@@ -881,6 +881,15 @@
 #define ISP3X_LSC_YSIZE_CD			(ISP3X_LSC_BASE + 0x000A8)
 #define ISP3X_LSC_YSIZE_EF			(ISP3X_LSC_BASE + 0x000AC)
 
+#define ISP3X_LSC_XGRAD(n)		(n < 4 ? (ISP3X_LSC_XGRAD_01 + (n) * 4) : \
+						 (ISP3X_LSC_XGRAD_89 + ((n) - 4) * 4))
+#define ISP3X_LSC_YGRAD(n)		(n < 4 ? (ISP3X_LSC_YGRAD_01 + (n) * 4) : \
+						 (ISP3X_LSC_YGRAD_89 + ((n) - 4) * 4))
+#define ISP3X_LSC_XSIZE(n)		(n < 4 ? (ISP3X_LSC_XSIZE_01 + (n) * 4) : \
+						 (ISP3X_LSC_XSIZE_89 + ((n) - 4) * 4))
+#define ISP3X_LSC_YSIZE(n)		(n < 4 ? (ISP3X_LSC_YSIZE_01 + (n) * 4) : \
+						 (ISP3X_LSC_YSIZE_89 + ((n) - 4) * 4))
+
 #define ISP3X_DEBAYER_BASE			0x00002500
 #define ISP3X_DEBAYER_CONTROL			(ISP3X_DEBAYER_BASE + 0x00000)
 #define ISP3X_DEBAYER_G_INTERP			(ISP3X_DEBAYER_BASE + 0x00004)
@@ -3235,6 +3244,12 @@
 #define ISP33_IIR_WR_ID(x)		(((x) & 0x3f) << 16)
 #define ISP33_IIR_WR_CLEAR		BIT(24)
 
+/* GAMMA_OUT */
+#define ISP3X_GAMMA_OUT_CTRL_EN			BIT(0)
+#define ISP3X_GAMMA_OUT_CTRL_MODE_EQUIDISTANT	BIT(1)
+#define ISP3X_GAMMA_OUT_CTRL_SEGMENTS_48		BIT(2)
+#define ISP3X_GAMMA_OUT_SAMPLE(a, b)		(((a) & 0xfff) | (((b) & 0xfff) << 16))
+
 /* HDRTMO */
 
 /* HDRDRC */
@@ -3264,6 +3279,7 @@
 #define ISP3X_DPCC_WORKING		BIT(30)
 
 /* CCM */
+#define ISP3X_CCM_COEFF(a, b)		((a & 0x7ff) | (b << 16))
 #define ISP3X_CCM_HIGHY_ADJ_DIS		BIT(1)
 #define ISP32_CCM_ENH_ADJ_EN		BIT(2)
 #define ISP32_CCM_ASYM_ADJ_EN		BIT(3)
@@ -3288,6 +3304,21 @@
 #define ISP3X_LSC_TABLE_ADDRESS_0	0
 #define ISP3X_LSC_TABLE_ADDRESS_153	153
 
+#define ISP3X_LSC_CTRL_EN		BIT(0)
+
+#define ISP3X_LSC_ACTIVE_TABLE		BIT(1)
+
+#define ISP3X_LSC_LUT_EN		BIT(1)
+#define ISP3X_LSC_SECTOR_16X16		BIT(2)
+#define ISP3X_LSC_PRE_RD_ST_MODE	BIT(4)
+
+#define ISP3X_LSC_TABLE_DATA(v0, v1)     \
+	(((v0) & 0x1FFF) | (((v1) & 0x1FFF) << 16))
+#define ISP3X_LSC_SECT_SIZE(v0, v1)      \
+	(((v0) & 0xFFF) | (((v1) & 0xFFF) << 16))
+#define ISP3X_LSC_GRAD_SIZE(v0, v1)      \
+	(((v0) & 0xFFF) | (((v1) & 0xFFF) << 16))
+
 #define ISP3X_LSC_LUT_EN		BIT(1)
 #define ISP3X_LSC_SECTOR_16X16		BIT(2)
 #define ISP3X_LSC_PRE_RD_ST_MODE	BIT(4)
diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
index a2b650f4ec3c..470e5b84ed15 100644
--- a/drivers/media/v4l2-core/v4l2-ioctl.c
+++ b/drivers/media/v4l2-core/v4l2-ioctl.c
@@ -1467,6 +1467,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
 	case V4L2_META_FMT_RK_ISP1_PARAMS:	descr = "Rockchip ISP1 3A Parameters"; break;
 	case V4L2_META_FMT_RK_ISP1_STAT_3A:	descr = "Rockchip ISP1 3A Statistics"; break;
 	case V4L2_META_FMT_RK_ISP1_EXT_PARAMS:	descr = "Rockchip ISP1 Ext 3A Params"; break;
+	case V4L2_META_FMT_RKISP2_PARAMS:	descr = "Rockchip ISP2 3A Parameters"; break;
 	case V4L2_META_FMT_C3ISP_PARAMS:	descr = "Amlogic C3 ISP Parameters"; break;
 	case V4L2_META_FMT_C3ISP_STATS:		descr = "Amlogic C3 ISP Statistics"; break;
 	case V4L2_META_FMT_MALI_C55_PARAMS:	descr = "ARM Mali-C55 ISP Parameters"; break;
diff --git a/include/uapi/linux/rkisp2-config.h b/include/uapi/linux/rkisp2-config.h
index ce8093db5998..0e42dbf2c9f2 100644
--- a/include/uapi/linux/rkisp2-config.h
+++ b/include/uapi/linux/rkisp2-config.h
@@ -8,6 +8,18 @@
 #ifndef _UAPI_RKISP2_CONFIG_H
 #define _UAPI_RKISP2_CONFIG_H
 
+#ifdef __KERNEL__
+#include <linux/build_bug.h>
+#endif /* __KERNEL__ */
+#include <linux/types.h>
+
+#include <linux/media/v4l2-isp.h>
+
+#define RKISP2_ISP_GAMMA_OUT_MAX_SEGMENTS   49
+
+#define RKISP2_ISP_LSC_SAMPLES_MAX		17
+#define RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX	16
+
 /**
  * enum rkisp2_isp_version - ISP variants
  *
@@ -17,4 +29,306 @@ enum rkisp2_isp_version {
 	RKISP3_V0 = 30,
 };
 
+/* See enum rkisp2_isp_goc_segments for logarithmic segment sizes */
+enum rkisp2_isp_goc_mode {
+	RKISP2_ISP_GOC_MODE_LOGARITHMIC,
+	RKISP2_ISP_GOC_MODE_EQUIDISTANT
+};
+
+/*
+ * The segments are:
+ * 1 x8, 2 x4, 4 x4, 8 x4, 16 x4, 32 x4, 64 x4, 128 x4, 256 x4, 512 x4
+ * In 48-segment mode, the last group of 512 x4 becomes 256 x8
+ */
+enum rkisp2_isp_goc_segments {
+	RKISP2_ISP_GOC_SEGMENTS_44,
+	RKISP2_ISP_GOC_SEGMENTS_48
+};
+
+enum rkisp2_isp_lsc_config {
+	RKISP2_ISP_LSC_CONFIG_8X8,
+	RKISP2_ISP_LSC_CONFIG_16X16
+};
+
+enum rkisp2_isp_set_active_table_when {
+	RKISP2_ISP_LSC_SET_ACTIVE_TABLE_AFTER,
+	RKISP2_ISP_LSC_SET_ACTIVE_TABLE_BEFORE,
+};
+
+/*---------- Parameters ------------*/
+
+/**
+ * enum rkisp2_params_block_type - RkISP1 extensible params block type
+ *
+ * @RKISP2_PARAMS_BLOCK_BLS: Black level subtraction
+ * @RKISP2_PARAMS_BLOCK_AWB_GAINS: AWB gains
+ * @RKISP2_PARAMS_BLOCK_CSM: Color conversion coefficients (in the ISP block)
+ * @RKISP2_PARAMS_BLOCK_CCM: Color correction matrix (in the CCM block)
+ * @RKISP2_PARAMS_BLOCK_GOC: Gamma out correction
+ * @RKISP2_PARAMS_BLOCK_LSC: Lens shading correction
+ * */
+enum rkisp2_params_block_type {
+	RKISP2_PARAMS_BLOCK_BLS,
+	RKISP2_PARAMS_BLOCK_AWB_GAINS,
+	RKISP2_PARAMS_BLOCK_CSM,
+	RKISP2_PARAMS_BLOCK_CCM,
+	RKISP2_PARAMS_BLOCK_GOC,
+	RKISP2_PARAMS_BLOCK_LSC,
+};
+
+/**
+ * struct rkisp2_isp_window -  measurement window.
+ *
+ * Measurements are calculated per window inside the frame.
+ * This struct represents a window for a measurement.
+ *
+ * @h_offs: the horizontal offset of the window from the left of the frame in pixels.
+ * @v_offs: the vertical offset of the window from the top of the frame in pixels.
+ * @h_size: the horizontal size of the window in pixels
+ * @v_size: the vertical size of the window in pixels.
+ */
+struct rkisp2_isp_window {
+	__u16 h_offs;
+	__u16 v_offs;
+	__u16 h_size;
+	__u16 v_size;
+};
+
+/**
+ * struct rkisp2_isp_bls_fixed_val - BLS fixed subtraction values
+ *
+ * These are signed 13-bit (-4096 to +4095).
+ *
+ * @a: Fixed black level value for Bayer channel 0
+ * @b: Fixed black level value for Bayer channel 1
+ * @c: Fixed black level value for Bayer channel 2
+ * @d: Fixed black level value for Bayer channel 3
+ */
+struct rkisp2_isp_bls_fixed_val {
+	__s16 a;
+	__s16 b;
+	__s16 c;
+	__s16 d;
+};
+
+/**
+ * struct rkisp2_isp_awb_gains - Auto white balance gain in the ISP block
+ *
+ * All fields in this struct are 16 bit, where:
+ * 0x100h = 1, unsigned integer value, range 0 to 63 with 8 bit fractional part.
+ *
+ * This leaves the upper two msb unaccounted for; it is unknown if these are
+ * unused or misdocumented.
+ *
+ * TODO investigate the upper two bits
+ *
+ * @r: gain value for red component.
+ * @gr: gain value for green component in red line.
+ * @b: gain value for blue component.
+ * @gb: gain value for green component in blue line.
+ */
+struct rkisp2_isp_awb_gains {
+	__u16 r;
+	__u16 gr;
+	__u16 b;
+	__u16 gb;
+};
+
+/**
+ * struct rkisp2_params_bls - RkISP2 params BLS config
+ *
+ * RkISP2 parameters Black Level Subtraction configuration block.
+ * Identified by :c:type:`RKISP2_PARAMS_BLOCK_BLS`.
+ *
+ * TODO Check if auto-mode and window selection is for both blocks or just for
+ * one block (it might be the same as 2.x)
+ *
+ * @header: The RkISP2 parameters block header
+ * @enable_auto: Automatic mode activated means that the measured values
+ *		 are subtracted. Otherwise the fixed subtraction
+ *		 values will be subtracted.
+ * @enabled_windows: enabled window (bit 0 for window 1, bit 1 for window 2)
+ * @bls_window1: Measurement window 1 size
+ * @bls_window2: Measurement window 2 size
+ * @bls_samples: Set amount of measured pixels for each Bayer position
+ *		 (A, B, C and D) to 2^bls_samples. (TODO needs confirmation)
+ * @bls_fixed_val: Black Level Subtraction fixed values for the BLS module at
+ * 		   the front of the pipeline
+ * @bls1_fixed_val: Black Level Subtraction fixed values for the BLS module after
+ * 		    bayer noise reduction
+ */
+struct rkisp2_params_bls {
+	struct v4l2_isp_params_block_header header;
+	__u8 enable_auto;
+	__u8 enabled_windows;
+	struct rkisp2_isp_window bls_window1;
+	struct rkisp2_isp_window bls_window2;
+	__u8 bls_samples;
+	struct rkisp2_isp_bls_fixed_val bls_fixed_val;
+	struct rkisp2_isp_bls_fixed_val bls1_fixed_val;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp2_params_awb_gains - RKISP2 params AWB gains config
+ *
+ * RkISP2 parameters auto white balance gains configuration block.
+ * Identified by :c:type:`RKISP2_PARAMS_BLOCK_AWB_GAINS`.
+ *
+ * TODO investigate what the different blocks mean
+ *
+ * Block 0 is equivalent to the awb gains block on 2.x, but blocks 1 and
+ * 2 do not exist on 2.x.
+ *
+ * @header: The RkISP2 parameters block header
+ * @gains: Gains configuration for block i
+ */
+struct rkisp2_params_awb_gains {
+	struct v4l2_isp_params_block_header header;
+	struct rkisp2_isp_awb_gains gains[3];
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp2_params_csm - Configuration used by Color Space Conversion
+ *
+ * RkISP2 parameters histogram configuration block.
+ * Identified by :c:type:`RKISP2_PARAMS_BLOCK_CSM`.
+ *
+ * @header: The RkISP2 parameters block header
+ * @coeff: color correction matrix. Values are 9-bit signed fixed-point numbers with 2 bit integer
+ *		and 7 bit fractional part, ranging from -2 (0x100) to +1.992 (0x0FF). 0 is
+ *		represented by 0x000 and a coefficient value of 1 as 0x080.
+ */
+struct rkisp2_params_csm {
+	struct v4l2_isp_params_block_header header;
+	__u16 coeff[3][3];
+};
+
+/**
+ * struct rkisp2_params_ccm - Configuration used by Color Correction Matrix
+ *
+ * RkISP2 parameters histogram configuration block.
+ * Identified by :c:type:`RKISP2_PARAMS_BLOCK_CCM`.
+ *
+ * @header: The RkISP2 parameters block header
+ * @high_y_alpha_adj_en: Enable CCM high Y alpha adjustment (TODO figure out what this does)
+ * @coeff: color correction matrix. Values are 11-bit signed fixed-point numbers with 4 bit integer
+ *		and 7 bit fractional part, ranging from -8 (0x400) to +7.992 (0x3FF). 0 is
+ *		represented by 0x000 and a coefficient value of 1 as 0x080. The
+ *		value is expanded 128 times (TODO figure out what this means).
+ * @offset: Red, Green, Blue offsets for the color correction matrix. 12-bits
+ *	    wide ranging from -4096 to 4095, but only for red; green and blue are 11-bit
+ *	    signed fixed-point like coeff, but are still 12-bits wide.
+ * @y_coeff: Red, Green, Blue coefficients for RGB2Y calculation. red and green
+ *	     are 11-bits wide and blue is 12-bits wide. The value is expanded 128 times.
+ * @alp: CCM curve y-axis point definition for ccm input pixel's luminance.
+ *       11-bit unsigned ranging from 0 to 1024. The value is expanded 128 times.
+ * @inflection_point: Inflection point of the ccm alpha interpolation curve.
+ * 		      The inflection point is 2^inflection_point. Since the maximum y-value is
+ * 		      1024, the maximum value of this field is expected to be 10 (0xa), but the
+ * 		      documentation says 4'b10.
+ */
+struct rkisp2_params_ccm {
+	struct v4l2_isp_params_block_header header;
+	__u8 high_y_alpha_adj_en;
+	__u16 coeff[3][3];
+	__u16 offset[3];
+	__u16 y_coeff[3];
+	__u16 alp[17];
+	__u8 inflection_point;
+};
+
+/**
+ * struct rkisp2_params_goc - Configuration used by Gamma Out correction
+ *
+ * RkISP2 parameters gamma out correction configuration block.
+ * Identified by :c:type:`RKISP2_PARAMS_BLOCK_GOC`.
+ *
+ * @header: The RkISP2 parameters block header
+ * @mode: goc mode (from enum rkisp2_isp_goc_mode)
+ * @segments: segments mode (from enum rkisp2_isp_goc_segments)
+ * @offset: offset value of the gamma out curve
+ * @gamma_y: gamma out curve y-axis for all color components
+ *
+ * The number of entries of @gamma_y depends on the segments mode. The entries
+ * are 12-bit unsigned.
+ */
+struct rkisp2_params_goc {
+	struct v4l2_isp_params_block_header header;
+	__u8 mode;
+	__u8 segments;
+	__u16 offset;
+	__u16 gamma_y[RKISP2_ISP_GAMMA_OUT_MAX_SEGMENTS];
+};
+
+/**
+ * struct rkisp2_params_lsc - Configuration used by Lens shading correction
+ *
+ * RkISP2 parameters lens shading correction configuration block.
+ * Identified by :c:type:`RKISP2_PARAMS_BLOCK_LSC`.
+ *
+ * The LSC module on the rkisp2 two tables: the 0th table and the 1th table.
+ * They can be programmed independently and (somewhat) simultaneously, and can be
+ * swapped by setting a single register. Hence the UAPI here is designed so
+ * that all these components can be controlled independently.
+ *
+ * In the first dimension of {r,gr,gb,b}_data_tbl we can designate which table
+ * to write the data to. write_table is then used to signal whether to write
+ * the data, and this can be controlled for both tables. active_table chooses
+ * which table to activate. set_active_table_when signals whether to set the
+ * active_table before or after programming the table. This allows
+ * optimizations such as setting a future table in one parameter buffer while
+ * swapping before setting it.
+ *
+ * This design gives us more control. For example, if we want to only program
+ * the 0th table without modifying the 1th table, we do not need to also
+ * populate the 1th table and we can use write_table to designate that we only
+ * want to program the 0th table. We can also swap tables without needing to
+ * re-populate the tables by setting active_table and unsetting write_table.
+ *
+ * {x,y}_sizes designates the grid of the LSC, and the table entries above
+ * correspond to the *vertices* of the grid. {x,y}_grads control the bilinear
+ * interpolation within the grid.
+ *
+ * @header: The RkISP2 parameters block header
+ * @r_data_tbl: Sample table red
+ * @gr_data_tbl: Sample table green (red)
+ * @gb_data_tbl: Sample table green (blue)
+ * @b_data_tbl: Sample table blue
+ * @write_table: Set to 1 to signal to write the respective table from above
+ * @active_table: Choose which of the two tables is active (0 or 1)
+ * @set_active_table_when: From rkisp2_isp_set_active_table_when; switch to the
+ *			   active table before or after programming the table
+ * @x_sizes: Sizes x
+ * @y_sizes: Sizes y
+ * @x_grads: Gradients x
+ * @y_grads: Gradients y
+ * @window_mode: From enum rkisp2_isp_lsc_config
+ */
+struct rkisp2_params_lsc {
+	struct v4l2_isp_params_block_header header;
+
+	__u16 r_data_tbl[2][RKISP2_ISP_LSC_SAMPLES_MAX][RKISP2_ISP_LSC_SAMPLES_MAX];
+	__u16 gr_data_tbl[2][RKISP2_ISP_LSC_SAMPLES_MAX][RKISP2_ISP_LSC_SAMPLES_MAX];
+	__u16 gb_data_tbl[2][RKISP2_ISP_LSC_SAMPLES_MAX][RKISP2_ISP_LSC_SAMPLES_MAX];
+	__u16 b_data_tbl[2][RKISP2_ISP_LSC_SAMPLES_MAX][RKISP2_ISP_LSC_SAMPLES_MAX];
+	__u8 write_table[2];
+	__u8 active_table;
+	__u8 set_active_table_when;
+
+	__u16 x_sizes[RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX];
+	__u16 y_sizes[RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX];
+	__u16 x_grads[RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX];
+	__u16 y_grads[RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX];
+
+	__u8 window_mode;
+};
+
+#define RKISP2_PARAMS_MAX_SIZE					\
+	(sizeof(struct rkisp2_params_bls)			+\
+	sizeof(struct rkisp2_params_awb_gains)			+\
+	sizeof(struct rkisp2_params_csm)			+\
+	sizeof(struct rkisp2_params_ccm)			+\
+	sizeof(struct rkisp2_params_goc)			+\
+	sizeof(struct rkisp2_params_lsc))
+
 #endif /* _UAPI_RKISP2_CONFIG_H */
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index eda4492e40dc..1d418a752d89 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -876,6 +876,9 @@ struct v4l2_pix_format {
 #define V4L2_META_FMT_RK_ISP1_STAT_3A	v4l2_fourcc('R', 'K', '1', 'S') /* Rockchip ISP1 3A Statistics */
 #define V4L2_META_FMT_RK_ISP1_EXT_PARAMS	v4l2_fourcc('R', 'K', '1', 'E') /* Rockchip ISP1 3a Extensible Parameters */
 
+/* Vendor specific - used for RKISP2 camera sub-system */
+#define V4L2_META_FMT_RKISP2_PARAMS	v4l2_fourcc('R', 'K', '2', 'P') /* Rockchip ISP2 Parameters */
+
 /* Vendor specific - used for C3_ISP */
 #define V4L2_META_FMT_C3ISP_PARAMS	v4l2_fourcc('C', '3', 'P', 'M') /* Amlogic C3 ISP Parameters */
 #define V4L2_META_FMT_C3ISP_STATS	v4l2_fourcc('C', '3', 'S', 'T') /* Amlogic C3 ISP Statistics */
-- 
2.47.2



^ permalink raw reply related

* [RFC PATCH 2/5] arm64: dts: rockchip: add ISP nodes to rk3588
From: Paul Elder @ 2026-04-24 17:58 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: Xu Hongfei, michael.riesch, stefan.klug, linux-media,
	linux-arm-kernel, linux-rockchip, linux-kernel, Paul Elder,
	Heiko Stuebner
In-Reply-To: <20260424175853.638202-1-paul.elder@ideasonboard.com>

From: Xu Hongfei <xuhf@rock-chips.com>

Add device tree nodes for the ISP and their iommus on the RK3588.

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

diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
index 8b98e5c3cc8b..607b03d55dfd 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
@@ -3535,6 +3535,66 @@ gpio4: gpio@fec50000 {
 			#interrupt-cells = <2>;
 		};
 	};
+
+	isp0: isp@fdcb0000 {
+		compatible = "rockchip,rk3588-isp";
+		reg = <0x0 0xfdcb0000 0x0 0x7f00>;
+		interrupts = <GIC_SPI 131 IRQ_TYPE_LEVEL_HIGH 0>,
+			     <GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH 0>,
+			     <GIC_SPI 134 IRQ_TYPE_LEVEL_HIGH 0>;
+		interrupt-names = "isp_irq", "mi_irq";
+		clocks = <&cru ACLK_ISP0>, <&cru HCLK_ISP0>,
+			 <&cru CLK_ISP0_CORE>, <&cru CLK_ISP0_CORE_MARVIN>,
+			 <&cru CLK_ISP0_CORE_VICAP>;
+		clock-names = "aclk", "hclk", "clk_core",
+			      "clk_core_marvin", "clk_core_vicap";
+		power-domains = <&power RK3588_PD_VI>;
+		iommus = <&isp0_mmu>;
+		status = "disabled";
+	};
+
+	isp0_mmu: iommu@fdcb7f00 {
+		compatible = "rockchip,rk3588-iommu", "rockchip,rk3568-iommu";
+		reg = <0x0 0xfdcb7f00 0x0 0x100>;
+		interrupts = <GIC_SPI 132 IRQ_TYPE_LEVEL_HIGH 0>;
+		interrupt-names = "isp0_mmu";
+		clocks = <&cru ACLK_ISP0>, <&cru HCLK_ISP0>;
+		clock-names = "aclk", "iface";
+		power-domains = <&power RK3588_PD_VI>;
+		#iommu-cells = <0>;
+		rockchip,disable-mmu-reset;
+		status = "disabled";
+	};
+
+	isp1: isp@fdcc0000 {
+		compatible = "rockchip,rk3588-isp";
+		reg = <0x0 0xfdcc0000 0x0 0x7f00>;
+		interrupts = <GIC_SPI 135 IRQ_TYPE_LEVEL_HIGH 0>,
+			     <GIC_SPI 137 IRQ_TYPE_LEVEL_HIGH 0>,
+			     <GIC_SPI 138 IRQ_TYPE_LEVEL_HIGH 0>;
+		interrupt-names = "isp_irq", "mi_irq";
+		clocks = <&cru ACLK_ISP1>, <&cru HCLK_ISP1>,
+			 <&cru CLK_ISP1_CORE>, <&cru CLK_ISP1_CORE_MARVIN>,
+			 <&cru CLK_ISP1_CORE_VICAP>;
+		clock-names = "aclk", "hclk", "clk_core",
+			      "clk_core_marvin", "clk_core_vicap";
+		power-domains = <&power RK3588_PD_ISP1>;
+		iommus = <&isp1_mmu>;
+		status = "disabled";
+	};
+
+	isp1_mmu: iommu@fdcc7f00 {
+		compatible = "rockchip,rk3588-iommu", "rockchip,rk3568-iommu";
+		reg = <0x0 0xfdcc7f00 0x0 0x100>;
+		interrupts = <GIC_SPI 136 IRQ_TYPE_LEVEL_HIGH 0>;
+		interrupt-names = "isp1_mmu";
+		clocks = <&cru ACLK_ISP1>, <&cru HCLK_ISP1>;
+		clock-names = "aclk", "iface";
+		power-domains = <&power RK3588_PD_ISP1>;
+		#iommu-cells = <0>;
+		rockchip,disable-mmu-reset;
+		status = "disabled";
+	};
 };
 
 #include "rk3588-base-pinctrl.dtsi"
-- 
2.47.2



^ permalink raw reply related

* [RFC PATCH 1/5] media: dt-bindings: Add rockchip rkisp2 bindings
From: Paul Elder @ 2026-04-24 17:58 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: Paul Elder, michael.riesch, xuhf, stefan.klug, linux-media,
	linux-arm-kernel, linux-rockchip, linux-kernel, Heiko Stuebner
In-Reply-To: <20260424175853.638202-1-paul.elder@ideasonboard.com>

Add documentation for the Rockchip rkisp2 bindings. This is meant to
support multiple versions of Rockchip ISPs going forward, including the
2.x series and 3.x series. The current version only adds the compatible
for the RK3588, which is a 3.0.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
---
 .../bindings/media/rockchip-isp2.yaml         | 127 ++++++++++++++++++
 1 file changed, 127 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/rockchip-isp2.yaml

diff --git a/Documentation/devicetree/bindings/media/rockchip-isp2.yaml b/Documentation/devicetree/bindings/media/rockchip-isp2.yaml
new file mode 100644
index 000000000000..f4ef089adf6e
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/rockchip-isp2.yaml
@@ -0,0 +1,127 @@
+# SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/rockchip-isp2.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Rockchip SoC Image Signal Processing unit v2
+
+maintainers:
+  - Paul Elder <paul.elder@ideasonboard.com>
+
+description: |
+  Rockchip ISP2 is the Camera interface for the Rockchip series of SoCs which
+  contains image processing, scaling, and compression functions.
+
+properties:
+  compatible:
+    enum:
+      - rockchip,rk3588-isp
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    minItems: 3
+    maxItems: 3
+
+  interrupt-names:
+    items:
+      - const: isp_irq
+      - const: mi_irq
+
+  clocks:
+    minItems: 3
+    items:
+      - description: ISP AXI clock (aclk)
+      - description: ISP AHB clock (hclk)
+      - description: ISP core clock (isp)
+      # for rk3588
+      - description: ISP core clock (marvin)
+      - description: ISP core clock (vicap)
+
+  clock-names:
+    minItems: 3
+    items:
+      - const: aclk
+      - const: hclk
+      - const: clk_core
+      # for rk3588
+      - const: clk_core_marvin
+      - const: clk_core_vicap
+
+  iommus:
+    maxItems: 1
+
+  power-domains:
+    minItems: 1
+    items:
+      - description: ISP power domain
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        description: connection point for VICAP in inline mode
+
+        properties:
+          endpoint:
+            $ref: video-interfaces.yaml#
+            unevaluatedProperties: false
+
+    required:
+      - port@0
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - clock-names
+  - power-domains
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: rockchip,rk3588-isp
+    then:
+      properties:
+        clocks:
+          minItems: 5
+        clock-names:
+          minItems: 5
+
+additionalProperties: false
+
+examples:
+  - |
+
+    #include <dt-bindings/clock/rockchip,rk3588-cru.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/power/rk3588-power.h>
+
+    parent0: parent {
+        #address-cells = <2>;
+        #size-cells = <2>;
+
+        isp0: isp@fdcb0000 {
+            compatible = "rockchip,rk3588-isp";
+            reg = <0x0 0xfdcb0000 0x0 0x7f00>;
+            interrupts = <GIC_SPI 131 IRQ_TYPE_LEVEL_HIGH 0>,
+                         <GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH 0>,
+                         <GIC_SPI 134 IRQ_TYPE_LEVEL_HIGH 0>;
+            interrupt-names = "isp_irq", "mi_irq";
+            clocks = <&cru ACLK_ISP0>, <&cru HCLK_ISP0>,
+                     <&cru CLK_ISP0_CORE>, <&cru CLK_ISP0_CORE_MARVIN>,
+                     <&cru CLK_ISP0_CORE_VICAP>;
+            clock-names = "aclk", "hclk", "clk_core",
+                        "clk_core_marvin", "clk_core_vicap";
+            power-domains = <&power RK3588_PD_VI>;
+            iommus = <&isp0_mmu>;
+        };
+    };
-- 
2.47.2



^ permalink raw reply related

* [RFC PATCH 0/5] media: rockchip: rkisp2: Add driver for ISP on Rk3588
From: Paul Elder @ 2026-04-24 17:58 UTC (permalink / raw)
  To: laurent.pinchart
  Cc: Paul Elder, michael.riesch, xuhf, stefan.klug, linux-media,
	linux-arm-kernel, linux-rockchip, linux-kernel

Hello everyone!

This patch series adds a new rkisp2 driver that aims to support all
Rockchip 2.x and 3.x series ISPs. Here we add support for the version on
the RK3588, which as far as I'm aware is a 3.0.

Patches 1 and 2 add dt bindings and dt nodes; patches 3~5 add the actual
driver.

All my discussion points/questions/progress-reports are in patch 3.

The following two series [0] [1] will be helpful for testing the code,
as they enable the VICAP module to work on the RK3588. I've tested this
on the Orange Pi CM5 Base with an imx219. I have a branch here [2] for
convenience. A libcamera branch will be coming soon too!

[0] https://lore.kernel.org/all/20260216-rkcif-fixes-v2-0-ee40931fe0ff@collabora.com/
[1] https://lore.kernel.org/all/20250430-rk3588-vicap-v3-0-e38e428868cc@collabora.com/
[2] https://git.ideasonboard.com/epaul/linux/src/branch/epaul/v7.0/rk3588/rkisp2/upstream

Paul Elder (3):
  media: dt-bindings: Add rockchip rkisp2 bindings
  media: rkisp2: Add parameters output video node
  media: rkisp2: Add statistics capture video node

Xu Hongfei (2):
  arm64: dts: rockchip: add ISP nodes to rk3588
  media: rockchip: rkisp2: Add rkisp2 driver

 .../bindings/media/rockchip-isp2.yaml         |  127 +
 arch/arm64/boot/dts/rockchip/rk3588-base.dtsi |   60 +
 drivers/media/platform/rockchip/Kconfig       |    1 +
 drivers/media/platform/rockchip/Makefile      |    1 +
 .../media/platform/rockchip/rkisp2/Kconfig    |   19 +
 .../media/platform/rockchip/rkisp2/Makefile   |   13 +
 .../platform/rockchip/rkisp2/rkisp2-capture.c | 1979 ++++++++++
 .../platform/rockchip/rkisp2/rkisp2-common.c  |  180 +
 .../platform/rockchip/rkisp2/rkisp2-common.h  |  565 +++
 .../platform/rockchip/rkisp2/rkisp2-debug.c   |  236 ++
 .../platform/rockchip/rkisp2/rkisp2-dev.c     |  376 ++
 .../platform/rockchip/rkisp2/rkisp2-dmarx.c   |  687 ++++
 .../platform/rockchip/rkisp2/rkisp2-isp.c     |  917 +++++
 .../platform/rockchip/rkisp2/rkisp2-params.c  | 1013 +++++
 .../rockchip/rkisp2/rkisp2-regs-v2x.h         | 2625 +++++++++++++
 .../rockchip/rkisp2/rkisp2-regs-v3x.h         | 3427 +++++++++++++++++
 .../platform/rockchip/rkisp2/rkisp2-regs.h    | 1736 +++++++++
 .../platform/rockchip/rkisp2/rkisp2-stats.c   |  482 +++
 drivers/media/v4l2-core/v4l2-ioctl.c          |    2 +
 include/uapi/linux/rkisp1-config.h            |    1 +
 include/uapi/linux/rkisp2-config.h            |  565 +++
 include/uapi/linux/videodev2.h                |    4 +
 22 files changed, 15016 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/rockchip-isp2.yaml
 create mode 100644 drivers/media/platform/rockchip/rkisp2/Kconfig
 create mode 100644 drivers/media/platform/rockchip/rkisp2/Makefile
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-capture.c
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-common.c
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-common.h
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-debug.c
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-dev.c
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-dmarx.c
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-isp.c
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-params.c
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v2x.h
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-regs-v3x.h
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-regs.h
 create mode 100644 drivers/media/platform/rockchip/rkisp2/rkisp2-stats.c
 create mode 100644 include/uapi/linux/rkisp2-config.h

-- 
2.47.2



^ permalink raw reply

* Re: [PATCH 1/2] dt-bindings: remoteproc: xlnx: add auto boot feature
From: Shah, Tanmay @ 2026-04-24 17:52 UTC (permalink / raw)
  To: Krzysztof Kozlowski, tanmay.shah
  Cc: andersson, mathieu.poirier, robh, krzk+dt, conor+dt, michal.simek,
	ben.levinsky, linux-remoteproc, devicetree, linux-arm-kernel,
	linux-kernel
In-Reply-To: <67f442f7-377d-46f3-82bc-86053e34c277@kernel.org>



On 4/24/2026 11:53 AM, Krzysztof Kozlowski wrote:
> On 23/04/2026 19:59, Shah, Tanmay wrote:
>> Ack, I will rename it to xlnx,auto-boot.
>>
>>>>
>>>>>> +        type: boolean
>>>>>> +        description: remote core is either already running or ready to boot
>>>>>
>>>>> And why is this property of a board?
>>>>>
>>>>
>>>> Not sure what indicates it is? The property is under remoteproc child
>>>> device that is SOC level property. Remote core is on same SOC wher linux
>>>> core is running.
>>>
>>> So it is implied by SoC compatible? Please provide some arguments why it
>>> cannot be implied by the SoC compatible. I gave you one way out, but if
>>> you disagree then no problem.
>>>
>>
>> So on some SoC, the bootloader supports loading and starting of the
>> remote processor. But it is totally user's choice. User can choose to
>> load & start one core of a cluster via bootloader and leave another core
>> powered-off.
>> That is why it is not possible to decide based on SoC compatible.
> 
> OK. The problem is that "user" is a bit vague and usually user choice
> goes to user-space.
> 
> The property will be set or unset for ALL of given boards. So all of the
> DTS->DTB. That's why it should be clear why all such boards should
> behave like you described. If this is truly user, as in user-space,
> choice, then DT is not the way.
> 

Okay 'user' may not be the right choice of word. I should say 'hardware
configuration'. On same SoC, some cores can be configured to boot
automatically before Linux boots, and some won't. So if device-tree is
about hardware configuration, then we need a way to show which core is
configured to boot before linux. This configuration is board agnostic.
So I think auto-boot in device-tree makes sense.

The only advantage on this platform is, it has a way to detect if the
core is running or not runtime and don't have to rely on device-tree.

> 
>>
>> If we don't want to make it a device-tree property then I can implement
>> in a different way. New way will detect if the remote is running or not
>> via EMMI/SCMI call to the firmware, and take a decision based on that.
>> If this new way works, then I don't think we need auto-boot property at all.
>>
>> Let me know your thoughts.
> 
> This works for me and solves my questions from DT point of view, but I
> cannot judge whether this makes sense for you.
> 

I say I will keep it open ended for now. I will avoid introducing
auto-boot in the device-tree for now, and send a patch without it. In
future if for some other reason this property is needed, will send new
patch later.

Thanks,
Tanmay

> 
> Best regards,
> Krzysztof



^ 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