Devicetree
 help / color / mirror / Atom feed
* [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE
@ 2026-07-03 15:51 ` Bryan O'Donoghue
  2026-07-03 15:51   ` [PATCH 1/7] arm64: dts: qcom: x1e80100: Add ICP/BPS/IPE nodes Bryan O'Donoghue
                     ` (7 more replies)
  0 siblings, 8 replies; 10+ messages in thread
From: Bryan O'Donoghue @ 2026-07-03 15:51 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-arm-msm, devicetree, linux-kernel, linux-media,
	Bryan O'Donoghue

This series is a very loose RFC for a block of code I've been developing to
enable the firmware based camera path available on most recent Qualcomm
platforms.

The Image Control Processor (ICP) provides a Host Firmware Interface (HFI)
which implements a session based system providing all of the most advanced
Camera algorithms on the Qualcomm stack.

Since it is firmware based with a session the ICP can achieve things like 

IPE:
- Motion Compensated Temporal Filtering - MCTF/temporal denoise
- Image Correction Adjustment - ICA warp/geometric correction
- Advanced noise reduction - ANR - multi-pass noise reduction in a single
  frame
- Electronic Image Stablisation - EIS
- Upscaling
  Not present in the inline hardware
- Local Tone Mapping / LTM - done on current frame using current stats

BPS:
- Multi Frame Noise Reduction / MFNR
- Bracketed HDR frames

All of the same functionality as with Inline Image Front End (IFE) is
available in the ICP version with the key differences that inline processes
sensor data directly whereas the offline processes frames submitted to it
via HFI.

One thing I'd like to reason about is if the offline engine would be a
better fit in driver/accel. This RFC solve the engineering problem of
actually getting the ICP to boot and exchanging some data with it.

A putative structure of bot the BPS and IPE appearing as /dev/videoX nodes
in user-space is sketched out and the stastics and parameters the ICP/HFI
expects are included in the drop for reference.

As is obvious YAML is missing, uAPI documentation is missing.

Speaking/listening to several people at the media summit in Nice this year
got me to thinking v4l2 m2m might be a another solution for an offline
engine like this one, with the inline engines fitting into v4l with the
media-graph and the ability to route different sensor/PHY combinations to
different CSI decoders.

This version of the code uses simple-mfd to probe the bus but that is _not_
my intention for a v1 and beyond. There is already code in-flight for
CSIPHY to effectively make sub-nodes probe themselves so please ignore that
part.

Link: https://git.kernel.org/pub/scm/linux/kernel/git/bod/linux.git/log/?h=qcom-laptops-v6.18-rc4-camss-icp-bps-ipe-icp-boots%2Bstats-b4

Here's an example of the ICP log right now on my reference machine:
camss -e csi
[    0.000000] OF: reserved mem: initialized node camera_icp_mem, compatible id shared-dma-pool
[    0.000000] OF: reserved mem: 0x0000000bef000000..0x0000000bffffffff (278528 KiB) map reusable camera_icp_mem
[    0.000000] OF: reserved mem: 0x0000000081f20000..0x0000000081f2ffff (64 KiB) nomap non-reusable usb-ucsi-shared@81f20000
[    1.443825] platform ac01000.icp: Adding to iommu group 5
[    6.665683] camss-icp ac01000.icp: assigned reserved memory node camera_icp_mem
[    6.673517] camss-icp ac01000.icp: clk=ahb
[    6.687251] camss-icp ac01000.icp: clk=core
[    6.704545] camss-icp ac01000.icp: clk=debug_xo
[    6.716389] camss-icp ac01000.icp: clk=gcc_hf_axi
[    6.727918] camss-icp ac01000.icp: clk=gcc_sf_axi
[    6.727919] camss-icp ac01000.icp: clk=cpas_ahb
[    6.727920] camss-icp ac01000.icp: clk=core_ahb
[    6.727921] camss-icp ac01000.icp: clk=cpas_fast_ahb
[    6.742587] camss-icp ac01000.icp: clk=camnoc_axi_rt
[    6.742588] camss-icp ac01000.icp: clk=camnoc_axi_nrt
[    6.742589] camss-icp ac01000.icp: clk=bps_ahb
[    6.742591] camss-icp ac01000.icp: clk=bps_fast_ahb
[    6.754909] camss-icp ac01000.icp: clk=bps
[    6.754911] camss-icp ac01000.icp: clk=cpas_bps
[    6.754912] camss-icp ac01000.icp: clk=ipe_ahb
[    6.754913] camss-icp ac01000.icp: clk=ipe_nps_fast_ahb
[    6.767222] camss-icp ac01000.icp: clk=ipe_pps_fast_ahb
[    6.781681] camss-icp ac01000.icp: clk=ipe_nps
[    6.803558] camss-icp ac01000.icp: clk=ipe_pps
[    6.813041] camss-icp ac01000.icp: clk=cpas_ipe
[    6.827852] camss-icp ac01000.icp: Voting for BW now ahb
[    6.837128] camss-icp ac01000.icp: Voting for BW now hf_0
[    6.871958] camss-icp ac01000.icp: Voting for BW now sf_0
[    6.983317] camss-icp ac01000.icp: Voting for BW now sf_icp
[    6.989219] camss-icp ac01000.icp: HW version: 0x20000000
[    6.994830] camss-icp ac01000.icp: FW memory: phys=0x0x000000008e100000 size=8388608
[    7.012769] camss-icp ac01000.icp: Firmware loaded: qcom/x1e80100/CAMERA_ICP.mdt (7340032 bytes)
[    7.012770] camss-icp ac01000.icp: HFI struct sizes: q_hdr=956 q_tbl_hdr=24 (expect 956, 24)
[    7.012962] camss-icp ac01000.icp: Allocated: vaddr=ffff800085201000 dma_addr=0xfff00000 size=0x100000
[    7.013650] camss-icp ac01000.icp: Allocated: vaddr=ffff800087801000 dma_addr=0xff800000 size=0x700000
[    7.013803] camss-icp ac01000.icp: Allocated: vaddr=ffff800085601000 dma_addr=0xff700000 size=0x100000
[    7.013804] camss-icp ac01000.icp: HFI memory layout:
[    7.013805] camss-icp ac01000.icp:   SHMEM:      dma=0xfff00000 size=0x100000
[    7.013806] camss-icp ac01000.icp:   FwUncached: dma=0xff800000 size=0x700000
[    7.013808] camss-icp ac01000.icp:   QDSS:       dma=0xff700000 size=0x100000
[    7.013810] camss-icp ac01000.icp: QTBL initialized: ver=0xffffffff size=0xb4c
[    7.013811] camss-icp ac01000.icp: Queue headers in QTBL:
[    7.013813] camss-icp ac01000.icp:   Q[0]: status=1 start_addr=0xffb00000 type=0 q_size=262144
[    7.013815] camss-icp ac01000.icp:   Q[1]: status=1 start_addr=0xffc00000 type=1 q_size=262144
[    7.013817] camss-icp ac01000.icp:   Q[2]: status=1 start_addr=0xffd00000 type=2 q_size=262144
[    7.086701] camss-icp ac01000.icp: Firmware ready! version=0x01000100
[    7.098579] camss-icp ac01000.icp: CIRQ after FW init: MASK=0x0 STATUS=0x0
[    7.098581] camss-icp ac01000.icp: GP registers after FW init:
[    7.232986] camss-icp ac01000.icp:   GP0 (unused):        0x00000000
[    7.239586] camss-icp ac01000.icp:   GP1 (FW_VERSION):    0x01000100
[    7.246249] camss-icp ac01000.icp:   GP2 (INIT_REQ):      0x00000001
[    7.252839] camss-icp ac01000.icp:   GP3 (INIT_RESP):     0x00000001
[    7.259434] camss-icp ac01000.icp:   GP4 (SHMEM_PTR):     0xfff00000 (we wrote: 0xfff00000)
[    7.268067] camss-icp ac01000.icp:   GP5 (SHMEM_SIZE):    0x00100000 (we wrote: 0x00100000)
[    7.276691] camss-icp ac01000.icp:   GP6 (QTBL_PTR):      0xffa00000 (we wrote: 0xffa00000)
[    7.285319] camss-icp ac01000.icp:   GP7 (SECHEAP_PTR):   0xff900000 (we wrote: 0xff900000)
[    7.293949] camss-icp ac01000.icp:   GP8 (SECHEAP_SIZE):  0x00100000 (we wrote: 0x00100000)
[    7.302710] camss-icp ac01000.icp:   GP9 (STATUS):        0x023200c3
[    7.309294] camss-icp ac01000.icp:   GP10 (SFR_PTR):      0xffe00000 (we wrote: 0xffe00000)
[    7.317920] camss-icp ac01000.icp:   GP11 (QDSS_IOVA):    0xff700000 (we wrote: 0xff700000)
[    7.326559] camss-icp ac01000.icp:   GP12 (QDSS_SIZE):    0x00100000
[    7.333141] camss-icp ac01000.icp:   GP17 (FWUNCACHED):   0xff800000 (we wrote: 0xff800000)
[    7.341778] camss-icp ac01000.icp:   GP18 (FWUNC_SIZE):   0x00700000 (we wrote: 0x00700000)
[    7.350447] camss-icp ac01000.icp: Starting HFI core init (SYS_INIT command)
[    7.357741] camss-icp ac01000.icp: CMD_Q before: read=0 write=0
[    7.363919] camss-icp ac01000.icp: MSG_Q before: read=0 write=0
[    7.376337] camss-icp ac01000.icp: hdr->type 0x00010001 size 0x00000008 tx:00 00 00 00 00 00 00 00
[    7.385590] camss-icp ac01000.icp: CMD_Q after write: read=0 write=2
[    7.392168] camss-icp ac01000.icp: Raising HOST2ICPINT (CIRQ STATUS before: 0x0)
[    7.399925] camss-icp ac01000.icp: CIRQ STATUS after HOST2ICPINT: 0x0
[    7.430285] camss-icp ac01000.icp: MSG_Q has data (polled): read=0 write=14
[    7.437487] camss-icp ac01000.icp: CMD_Q final: read=2 write=2 (FW consumed: YES)
[    7.445226] camss-icp ac01000.icp: MSG_Q final: read=0 write=14 (FW responded: YES)
[    7.453146] camss-icp ac01000.icp: hdr->type 0x00020001 size 0x00000038 rx:00 00 00 00 01 00 00 00 04 00 00 00 c3 00 32 02 05 09 06 00 00 03 01 01 00 00 00 20 00 00 01 20 02 00 00 20 02 00 00 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[    7.475216] camss-icp ac01000.icp: Parsed: size=56 type=0x20001 error=0x0 prop_num=0x1
[    7.483406] camss-icp ac01000.icp: Props 04 00 00 00
[    7.483409] camss-icp ac01000.icp: hdr->type 0x00020003 size 0x00000104 rx:08 00 00 00 e9 00 00 00 00 00 00 00 eb ca 70 01 43 49 43 50 5f 46 57 5f 45 20 3a 20 48 46 49 20 20 3a 51 43 5f 49 4d 41 47 45 5f 56 45 52 53 49 4f 4e 5f 53 54 52 49 4e 47 3d 43 49 43 50 2e 46
[    7.483412] camss-icp ac01000.icp: ICP FW [24169195]: CICP_FW_E : HFI  :QC_IMAGE_VERSION_STRING=CICP.FW.5.0-00020,OEM_IMAGE_VERSION_STRING=CRM,BUILD_TIME: Feb  8 2023 03:05:23,CACHE_ENABLED at icphostinterface.c:683 QC_IMAGE_VERSION_STRING=CICP.FW.5.0-00020 OEM_IMAGE_VERSION_STRING=CRM
[    7.483414] camss-icp ac01000.icp: hdr->type 0x00020003 size 0x000000ec rx:08 00 00 00 d1 00 00 00 00 00 00 00 ba 71 71 01 43 49 43 50 5f 46 57 5f 45 20 3a 20 48 46 49 20 20 3a 45 4c 46 20 76 61 72 69 61 6e 74 3a 20 43 41 43 48 45 2d 45 4e 41 42 4c 45 44 3a 54 34 38
[    7.483416] camss-icp ac01000.icp: ICP FW [24211898]: CICP_FW_E : HFI  :ELF variant: CACHE-ENABLED:T480:API_V2:USE_CDM_1_1:T680:TCM_ENABLED: , API version: 0x23200c3 at icphostinterface.c:684 QC_IMAGE_VERSION_STRING=CICP.FW.5.0-00020 OEM_IMAGE_VERSION_STRING=CRM
[    7.483419] camss-icp ac01000.icp: CMD_Q before: read=2 write=2
[    7.483420] camss-icp ac01000.icp: MSG_Q before: read=14 write=14
[    7.483421] camss-icp ac01000.icp: hdr->type 0x00010005 size 0x00000010 tx:be ba fe ca ef be ad de 00 00 00 00 00 00 00 00
[    7.483423] camss-icp ac01000.icp: CMD_Q after write: read=2 write=6
[    7.483425] camss-icp ac01000.icp: Raising HOST2ICPINT (CIRQ STATUS before: 0x0)
[    7.483529] camss-icp ac01000.icp: CIRQ STATUS after HOST2ICPINT: 0x0
[    7.650377] camss-icp ac01000.icp: MSG_Q has data (polled): read=14 write=18
[    7.657677] camss-icp ac01000.icp: CMD_Q final: read=6 write=6 (FW consumed: YES)
[    7.657679] camss-icp ac01000.icp: MSG_Q final: read=14 write=18 (FW responded: YES)
[    7.657680] camss-icp ac01000.icp: hdr->type 0x00020006 size 0x00000010 rx:be ba fe ca ef be ad de 00 f5 b0 76 c9 78 e5 26
[    7.657683] camss-icp ac01000.icp: Ping 0xdeadbeefcafebabe Pong 0xdeadbeefcafebabe
[    7.685850] camss-bps ac2c000.bps: BPS registered as /dev/video18
[    7.702906] camss-icp ac01000.icp: CMD_Q before: read=6 write=6
[    7.711157] camss-icp ac01000.icp: MSG_Q before: read=18 write=18
[    7.717472] camss-icp ac01000.icp: hdr->type 0x01010008 size 0x0000001c tx:01 00 00 00 00 80 4e 03 08 00 ff ff 00 00 00 00 00 00 00 00 00 80 ff ff 00 00 00 00
[    7.732077] camss-icp ac01000.icp: CMD_Q after write: read=6 write=13
[    7.738751] camss-icp ac01000.icp: Raising HOST2ICPINT (CIRQ STATUS before: 0x0)
[    7.746535] camss-icp ac01000.icp: CIRQ STATUS after HOST2ICPINT: 0x0
[    7.770466] camss-icp ac01000.icp: MSG_Q has data (polled): read=18 write=26
[    7.777770] camss-icp ac01000.icp: CMD_Q final: read=13 write=13 (FW consumed: YES)
[    7.785691] camss-icp ac01000.icp: MSG_Q final: read=18 write=26 (FW responded: YES)
[    7.793701] camss-icp ac01000.icp: hdr->type 0x01020008 size 0x00000020 rx:00 00 00 00 98 19 29 00 00 80 4e 03 08 00 ff ff 00 00 00 00 00 00 00 00 00 d5 12 df c1 60 13 01
[    7.809366] camss-icp ac01000.icp: fw_handle = 0x00291998
[    7.815006] camss-icp ac01000.icp: CMD_Q before: read=13 write=13
[    7.815008] camss-icp ac01000.icp: MSG_Q before: read=26 write=26
[    7.815009] camss-icp ac01000.icp: hdr->type 0x0101000a size 0x00000028 tx:04 00 00 00 98 19 29 00 00 80 4e 03 08 00 ff ff 00 00 00 00 00 00 00 00 01 00 00 00 98 19 29 00 00 00 00 00 00 00 00 00
[    7.815011] camss-icp ac01000.icp: CMD_Q after write: read=13 write=23
[    7.815013] camss-icp ac01000.icp: Raising HOST2ICPINT (CIRQ STATUS before: 0x0)
[    7.815116] camss-icp ac01000.icp: CIRQ STATUS after HOST2ICPINT: 0x0
[    7.882581] camss-icp ac01000.icp: MSG_Q has data (polled): read=26 write=33
[    7.891973] camss-icp ac01000.icp: CMD_Q final: read=23 write=23 (FW consumed: YES)
[    7.899895] camss-icp ac01000.icp: MSG_Q final: read=26 write=33 (FW responded: YES)
[    7.907896] camss-icp ac01000.icp: hdr->type 0x00020008 size 0x0000001c rx:ff ff ff ff 03 00 00 00 cc 00 00 00 28 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[    7.922501] camss-icp ac01000.icp: ack.hdr.type != HFI_MSG_IPEBPS_ASYNC_DIRECT_ACK
[    7.930419] camss-bps ac2c000.bps: failed to destory handle -71

Signed-off-by: Bryan O'Donoghue <bod@kernel.org>
---
Bryan O'Donoghue (7):
      arm64: dts: qcom: x1e80100: Add ICP/BPS/IPE nodes
      media: qcom: camss: Launch ICP from CAMSS
      media: qcom: camss: qcom-icp: Add minimal ICP driver with HFI infrastrucutre
      media: qcom: camss: qcom-icp: bps: Add initial v4l2 m2m BPS driver
      media: qcom: camss: qcom-icp: ipe: Add initial v4l2 m2m IPE driver
      media: qcom: camss: Switch on ICP and BPS as make options
      media: uapi: qcom-camss-stats-params

 arch/arm64/boot/dts/qcom/hamoa.dtsi                |  250 ++-
 drivers/media/platform/qcom/camss/Kconfig          |   24 +
 drivers/media/platform/qcom/camss/Makefile         |    8 +
 drivers/media/platform/qcom/camss/camss-bps.c      |  684 +++++++
 drivers/media/platform/qcom/camss/camss-bps.h      |   74 +
 drivers/media/platform/qcom/camss/camss-icp-hfi.h  |  534 ++++++
 drivers/media/platform/qcom/camss/camss-icp.c      |  623 ++++++
 drivers/media/platform/qcom/camss/camss-icp.h      |   58 +
 drivers/media/platform/qcom/camss/camss-ipe.c      |  558 ++++++
 drivers/media/platform/qcom/camss/camss-ipe.h      |   69 +
 drivers/media/platform/qcom/camss/camss.c          |   29 +
 .../linux/media/qcom/camss/camss-stats-params.h    | 2022 ++++++++++++++++++++
 12 files changed, 4926 insertions(+), 7 deletions(-)
---
base-commit: ae28dda0ce21b86f8aa1c4456ede819d46eea536
change-id: 20260703-qcom-laptops-v6-18-rc4-camss-icp-bps-ipe-icp-boots-stats-b4-b252a7912d4d

Best regards,
--  
Bryan O'Donoghue <bod@kernel.org>


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

* [PATCH 1/7] arm64: dts: qcom: x1e80100: Add ICP/BPS/IPE nodes
  2026-07-03 15:51 ` [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE Bryan O'Donoghue
@ 2026-07-03 15:51   ` Bryan O'Donoghue
  2026-07-03 15:51   ` [PATCH 2/7] media: qcom: camss: Launch ICP from CAMSS Bryan O'Donoghue
                     ` (6 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Bryan O'Donoghue @ 2026-07-03 15:51 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-arm-msm, devicetree, linux-kernel, linux-media,
	Bryan O'Donoghue

This is a very rough outline of adding the ICP, BPS and IPE to Hamoa. It
does so assuming the relevant devices are sub-nodes of the existing
binding.

Yaml for this binding has not been made yet.

Signed-off-by: Bryan O'Donoghue <bod@kernel.org>
---
 arch/arm64/boot/dts/qcom/hamoa.dtsi | 250 +++++++++++++++++++++++++++++++++++-
 1 file changed, 243 insertions(+), 7 deletions(-)

diff --git a/arch/arm64/boot/dts/qcom/hamoa.dtsi b/arch/arm64/boot/dts/qcom/hamoa.dtsi
index 5c7f86005e824..40ab1c649dc6e 100644
--- a/arch/arm64/boot/dts/qcom/hamoa.dtsi
+++ b/arch/arm64/boot/dts/qcom/hamoa.dtsi
@@ -645,7 +645,7 @@ cvp_mem: cvp@8da00000 {
 			no-map;
 		};
 
-		camera_mem: camera@8e100000 {
+		camera_fw_mem: camera@8e100000 {
 			reg = <0x0 0x8e100000 0x0 0x800000>;
 			no-map;
 		};
@@ -706,6 +706,13 @@ smem_mem: smem@ffe00000 {
 			hwlocks = <&tcsr_mutex 3>;
 			no-map;
 		};
+
+		camera_icp_mem: camera_icp_mem {
+			compatible = "shared-dma-pool";
+			reusable;
+			size = <0x0 0x11000000>;
+			alignment = <0x0 0x00100000>;
+		};
 	};
 
 	qup_opp_table_100mhz: opp-table-qup100mhz {
@@ -5495,7 +5502,10 @@ cci1_i2c1: i2c-bus@1 {
 		};
 
 		camss: isp@acb6000 {
-			compatible = "qcom,x1e80100-camss";
+			compatible = "qcom,x1e80100-camss", "simple-mfd";
+			#address-cells = <2>;
+			#size-cells = <2>;
+			ranges;
 
 			reg = <0 0x0acb6000 0 0x1000>,
 			      <0 0x0acb7000 0 0x2000>,
@@ -5602,11 +5612,11 @@ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>,
 					     "sf_mnoc",
 					     "sf_icp_mnoc";
 
-			iommus = <&apps_smmu 0x800 0x60>,
-				 <&apps_smmu 0x860 0x60>,
-				 <&apps_smmu 0x1860 0x60>,
-				 <&apps_smmu 0x18e0 0x00>,
-				 <&apps_smmu 0x19a0 0x20>;
+			iommus = <&apps_smmu 0x800 0x60>, //IFE0/1 IFE_LITE 0/1 non-protected stream read - S1 IFE HLOS
+				 <&apps_smmu 0x820 0x60>, //IFE0/1 IFE_LITE 0/1 non-protected stream write - S1 IFE HLOS
+				 <&apps_smmu 0x840 0x60>, //SFE0 non-protected read - S1 IFE HLOS
+				 <&apps_smmu 0x860 0x60>, //SFE0 non-protected write - S1 IFE HLOS
+				 <&apps_smmu 0x18a0 0x0>; //CDM IFE non-protected stream - S1 IFE HLOS
 
 			phys = <&csiphy0 PHY_TYPE_DPHY>, <&csiphy1 PHY_TYPE_DPHY>,
 			       <&csiphy2 PHY_TYPE_DPHY>, <&csiphy4 PHY_TYPE_DPHY>;
@@ -5662,6 +5672,232 @@ camss_csiphy4_inep0: endpoint@0 {
 					};
 				};
 			};
+
+			icp: icp@ac01000 {
+				compatible = "qcom,x1e80100-camss-icp";
+
+				reg = <0 0xac01000 0 0x400>,
+				      <0 0xac01800 0 0x400>,
+				      <0 0xac04000 0 0x1000>;
+				reg-names = "csr", "cirq", "wd";
+
+				interrupts = <GIC_SPI 463 IRQ_TYPE_EDGE_RISING>;
+
+				/*
+				 * ICP clocks plus BPS/IPE clocks.
+				 * ICP firmware expects BPS/IPE to be clocked before boot.
+				 */
+				clocks = <&camcc CAM_CC_ICP_AHB_CLK>,
+					 <&camcc CAM_CC_ICP_CLK>,
+					 <&camcc CAM_CC_QDSS_DEBUG_XO_CLK>,
+					 <&gcc GCC_CAMERA_HF_AXI_CLK>,
+					 <&gcc GCC_CAMERA_SF_AXI_CLK>,
+					 <&camcc CAM_CC_CPAS_AHB_CLK>,
+					 <&camcc CAM_CC_CPAS_FAST_AHB_CLK>,
+					 <&camcc CAM_CC_CORE_AHB_CLK>,
+					 <&camcc CAM_CC_CAMNOC_AXI_RT_CLK>,
+					 <&camcc CAM_CC_CAMNOC_AXI_NRT_CLK>,
+					 /* BPS clocks */
+					 <&camcc CAM_CC_BPS_AHB_CLK>,
+					 <&camcc CAM_CC_BPS_FAST_AHB_CLK>,
+					 <&camcc CAM_CC_BPS_CLK>,
+					 <&camcc CAM_CC_CPAS_BPS_CLK>,
+					 /* IPE clocks */
+					 <&camcc CAM_CC_IPE_NPS_AHB_CLK>,
+					 <&camcc CAM_CC_IPE_NPS_FAST_AHB_CLK>,
+					 <&camcc CAM_CC_IPE_PPS_FAST_AHB_CLK>,
+					 <&camcc CAM_CC_IPE_NPS_CLK>,
+					 <&camcc CAM_CC_IPE_PPS_CLK>,
+					 <&camcc CAM_CC_CPAS_IPE_NPS_CLK>;
+
+				clock-names = "ahb", "core", "debug_xo",
+					      "gcc_hf_axi", "gcc_sf_axi",
+					      "cpas_ahb", "cpas_fast_ahb", "core_ahb",
+					      "camnoc_axi_rt", "camnoc_axi_nrt",
+					      "bps_ahb", "bps_fast_ahb", "bps", "cpas_bps",
+					      "ipe_ahb", "ipe_nps_fast_ahb", "ipe_pps_fast_ahb",
+					      "ipe_nps", "ipe_pps", "cpas_ipe";
+
+				/* Set operational clock rates to enable PLLs */
+				assigned-clocks = <&camcc CAM_CC_BPS_CLK_SRC>,
+						  <&camcc CAM_CC_IPE_NPS_CLK_SRC>,
+						  <&camcc CAM_CC_ICP_CLK_SRC>;
+				assigned-clock-rates = <480000000>,   /* BPS: 480 MHz */
+						       <480000000>,   /* IPE: 480 MHz */
+						       <480000000>;   /* ICP: 480 MHz */
+
+				/*
+				 * Power domains: TITAN_TOP plus BPS and IPE GDSCs.
+				 * ICP firmware expects BPS/IPE powered before boot.
+				 */
+				power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>,
+						<&camcc CAM_CC_BPS_GDSC>,
+						<&camcc CAM_CC_IPE_0_GDSC>;
+				power-domain-names = "top", "bps", "ipe";
+
+				resets = <&camcc CAM_CC_ICP_BCR>,
+					 <&camcc CAM_CC_BPS_BCR>,
+					 <&camcc CAM_CC_IPE_0_BCR>;
+				reset-names = "icp", "bps", "ipe";
+
+				/*
+				 * ICP SMMU contexts
+				 * Multiple stream IDs for processor and DMA
+				 */
+				iommus = <&apps_smmu 0x1900 0x0>;	// S1 ICP_IPE_BPS_CDM CPU shared stream
+
+				interconnect-names = "ahb",
+						     "hf_0",
+						     "sf_0",
+						     "sf_icp";
+				interconnects = <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS
+						 &config_noc SLAVE_CAMERA_CFG QCOM_ICC_TAG_ALWAYS>,
+						<&mmss_noc MASTER_CAMNOC_HF QCOM_ICC_TAG_ALWAYS
+						 &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>,
+						<&mmss_noc MASTER_CAMNOC_SF QCOM_ICC_TAG_ALWAYS
+						 &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>,
+						<&mmss_noc MASTER_CAMNOC_ICP QCOM_ICC_TAG_ALWAYS
+						 &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>;
+
+				memory-region = <&camera_fw_mem>, <&camera_icp_mem>;
+				firmware-name = "qcom/x1e80100/CAMERA_ICP";
+
+				operating-points-v2 = <&icp_opp_table>;
+
+				icp_opp_table: opp-table {
+					compatible = "operating-points-v2";
+
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					opp-400000000 {
+						opp-hz = /bits/ 64 <400000000>;
+						required-opps = <&rpmhpd_opp_low_svs>;
+					};
+
+					opp-480000000 {
+						opp-hz = /bits/ 64 <480000000>;
+						required-opps = <&rpmhpd_opp_svs>;
+					};
+
+					opp-600000000 {
+						opp-hz = /bits/ 64 <600000000>;
+						required-opps = <&rpmhpd_opp_svs_l1>;
+					};
+				};
+			};
+
+			ipe: ipe@ac42000 {
+				compatible = "qcom,x1e80100-camss-ipe";
+
+				reg = <0 0xac42000 0 0x16000>;
+
+				qcom,icp = <&icp>;
+
+				clocks = <&camcc CAM_CC_IPE_NPS_AHB_CLK>,
+					 <&camcc CAM_CC_IPE_NPS_FAST_AHB_CLK>,
+					 <&camcc CAM_CC_IPE_PPS_FAST_AHB_CLK>,
+					 <&camcc CAM_CC_IPE_NPS_CLK>,
+					 <&camcc CAM_CC_IPE_PPS_CLK>,
+					 <&camcc CAM_CC_CPAS_IPE_NPS_CLK>;
+				clock-names = "ahb", "nps_fast_ahb", "pps_fast_ahb",
+					      "nps", "pps", "cpas";
+
+				power-domains = <&camcc CAM_CC_IPE_0_GDSC>;
+
+				iommus = <&apps_smmu 0x1980 0x20>,	// S1_ICP_IPE_BPS_CDM non-protected CMD IPE0 read
+					 <&apps_smmu 0x1820 0x60>,	// S1_ICP_IPE_BPS_CDM non-protected IPE0 write
+					 <&apps_smmu 0x1800 0x60>;	// S1_ICP_IPE_BPS_CDM non-protected IPE0 read
+				dma-coherent;
+
+				interconnects = <&mmss_noc MASTER_CAMNOC_SF 0
+						 &mc_virt SLAVE_EBI1 0>;
+				interconnect-names = "mem";
+
+				ubwc-fetch-cfg = <0x7083>;
+				ubwc-write-cfg = <0x1620f>;
+
+				operating-points-v2 = <&ipe_opp_table>;
+
+				ipe_opp_table: opp-table {
+					compatible = "operating-points-v2";
+
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					opp-364000000 {
+						opp-hz = /bits/ 64 <364000000>;
+						required-opps = <&rpmhpd_opp_low_svs>;
+					};
+
+					opp-500000000 {
+						opp-hz = /bits/ 64 <500000000>;
+						required-opps = <&rpmhpd_opp_svs>;
+					};
+
+					opp-600000000 {
+						opp-hz = /bits/ 64 <600000000>;
+						required-opps = <&rpmhpd_opp_svs_l1>;
+					};
+
+					opp-700000000 {
+						opp-hz = /bits/ 64 <700000000>;
+						required-opps = <&rpmhpd_opp_nom>;
+					};
+				};
+			};
+
+			bps: bps@ac2c000 {
+				compatible = "qcom,x1e80100-camss-bps";
+
+				reg = <0 0xac2c000 0 0x8000>;
+
+				qcom,icp = <&icp>;
+
+				clocks = <&camcc CAM_CC_BPS_AHB_CLK>,
+					 <&camcc CAM_CC_BPS_FAST_AHB_CLK>,
+					 <&camcc CAM_CC_BPS_CLK>,
+					 <&camcc CAM_CC_CPAS_BPS_CLK>;
+				clock-names = "ahb", "fast_ahb", "core", "cpas";
+
+				power-domains = <&camcc CAM_CC_BPS_GDSC>;
+
+				iommus = <&apps_smmu 0x1840 0x60>,	// S1_ICP_IPE_BPS_CDM non-protected BPS0 read
+					 <&apps_smmu 0x1860 0x60>,	// S1_ICP_IPE_BPS_CDM non-protected BPS0 write
+					 <&apps_smmu 0x19a0 0x20>;	// S1_ICP_IPE_BPS_CDM non-protected BPS0 CMD read
+				dma-coherent;
+
+				interconnects = <&mmss_noc MASTER_CAMNOC_SF 0
+						 &mc_virt SLAVE_EBI1 0>;
+				interconnect-names = "mem";
+
+				ubwc-fetch-cfg = <0x7083>;
+				ubwc-write-cfg = <0x1620f>;
+
+				operating-points-v2 = <&bps_opp_table>;
+
+				bps_opp_table: opp-table {
+					compatible = "operating-points-v2";
+
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					opp-200000000 {
+						opp-hz = /bits/ 64 <200000000>;
+						required-opps = <&rpmhpd_opp_low_svs>;
+					};
+
+					opp-400000000 {
+						opp-hz = /bits/ 64 <400000000>;
+						required-opps = <&rpmhpd_opp_svs>;
+					};
+
+					opp-600000000 {
+						opp-hz = /bits/ 64 <600000000>;
+						required-opps = <&rpmhpd_opp_nom>;
+					};
+				};
+			};
 		};
 
 		csiphy_opp_table: opp-table-csiphy {

-- 
2.54.0


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

* [PATCH 2/7] media: qcom: camss: Launch ICP from CAMSS
  2026-07-03 15:51 ` [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE Bryan O'Donoghue
  2026-07-03 15:51   ` [PATCH 1/7] arm64: dts: qcom: x1e80100: Add ICP/BPS/IPE nodes Bryan O'Donoghue
@ 2026-07-03 15:51   ` Bryan O'Donoghue
  2026-07-03 15:51   ` [PATCH 3/7] media: qcom: camss: qcom-icp: Add minimal ICP driver with HFI infrastrucutre Bryan O'Donoghue
                     ` (5 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Bryan O'Donoghue @ 2026-07-03 15:51 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-arm-msm, devicetree, linux-kernel, linux-media,
	Bryan O'Donoghue

Launch ICP from CAMSS.

Signed-off-by: Bryan O'Donoghue <bod@kernel.org>
---
 drivers/media/platform/qcom/camss/camss.c | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c
index 499e349aa4929..eafa8a1eaaa36 100644
--- a/drivers/media/platform/qcom/camss/camss.c
+++ b/drivers/media/platform/qcom/camss/camss.c
@@ -16,6 +16,7 @@
 #include <linux/of.h>
 #include <linux/of_device.h>
 #include <linux/of_graph.h>
+#include <linux/of_platform.h>
 #include <linux/pm_runtime.h>
 #include <linux/pm_domain.h>
 #include <linux/slab.h>
@@ -4962,6 +4963,15 @@ static void camss_genpd_cleanup(struct camss *camss)
 	dev_pm_domain_detach(camss->genpd, true);
 }
 
+/*
+ * Match table for child nodes that camss.c registers as platform devices.
+ * This includes existing nodes (csiphy, csid, vfe) plus the new ICP node.
+ */
+static const struct of_device_id camss_child_match[] = {
+	{ .compatible = "qcom,x1e80100-camss-icp" },	/* NEW: ICP node */
+	{ }
+};
+
 /*
  * camss_probe - Probe CAMSS platform device
  * @pdev: Pointer to CAMSS platform device
@@ -5043,6 +5053,23 @@ static int camss_probe(struct platform_device *pdev)
 
 	pm_runtime_enable(dev);
 
+	/*
+	 * Register child nodes as platform devices.
+	 *
+	 * This includes:
+	 * - Existing: csiphy, csid, vfe
+	 * - New: icp
+	 *
+	 * Note: IPE and BPS are NOT registered here. They are siblings
+	 * of ICP in the device tree, but are registered by the ICP
+	 * driver since ICP needs to probe first to provide HFI.
+	 */
+	ret = of_platform_populate(dev->of_node, camss_child_match, NULL, dev);
+	if (ret) {
+		dev_err(dev, "Failed to populate child devices: %d\n", ret);
+		goto err_of_platform_depopulate;
+	}
+
 	ret = camss_parse_ports(camss);
 	if (ret < 0)
 		goto err_v4l2_device_unregister;
@@ -5079,6 +5106,8 @@ static int camss_probe(struct platform_device *pdev)
 	v4l2_device_unregister(&camss->v4l2_dev);
 	v4l2_async_nf_cleanup(&camss->notifier);
 	pm_runtime_disable(dev);
+err_of_platform_depopulate:
+	of_platform_depopulate(&pdev->dev);
 err_media_device_cleanup:
 	media_device_cleanup(&camss->media_dev);
 err_genpd_cleanup:

-- 
2.54.0


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

* [PATCH 3/7] media: qcom: camss: qcom-icp: Add minimal ICP driver with HFI infrastrucutre
  2026-07-03 15:51 ` [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE Bryan O'Donoghue
  2026-07-03 15:51   ` [PATCH 1/7] arm64: dts: qcom: x1e80100: Add ICP/BPS/IPE nodes Bryan O'Donoghue
  2026-07-03 15:51   ` [PATCH 2/7] media: qcom: camss: Launch ICP from CAMSS Bryan O'Donoghue
@ 2026-07-03 15:51   ` Bryan O'Donoghue
  2026-07-03 15:51   ` [PATCH 4/7] media: qcom: camss: qcom-icp: bps: Add initial v4l2 m2m BPS driver Bryan O'Donoghue
                     ` (4 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Bryan O'Donoghue @ 2026-07-03 15:51 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-arm-msm, devicetree, linux-kernel, linux-media,
	Bryan O'Donoghue

Signed-off-by: Bryan O'Donoghue <bod@kernel.org>
---
 drivers/media/platform/qcom/camss/camss-icp-hfi.h | 534 +++++++++++++++++++
 drivers/media/platform/qcom/camss/camss-icp.c     | 623 ++++++++++++++++++++++
 drivers/media/platform/qcom/camss/camss-icp.h     |  58 ++
 3 files changed, 1215 insertions(+)

diff --git a/drivers/media/platform/qcom/camss/camss-icp-hfi.h b/drivers/media/platform/qcom/camss/camss-icp-hfi.h
new file mode 100644
index 0000000000000..a63a0395c0f17
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-icp-hfi.h
@@ -0,0 +1,534 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Qualcomm CAMSS ICP HFI Protocol Definitions
+ *
+ * Copyright (c) 2025 Linaro Ltd.
+ */
+
+#ifndef __CAMSS_ICP_HFI_H__
+#define __CAMSS_ICP_HFI_H__
+
+#include <linux/types.h>
+
+#define HFI_QTBL_VERSION			0xFFFFFFFF
+
+/* ICP firmware boot command/response */
+#define ICP_INIT_REQUEST_RESET			0x0
+#define ICP_INIT_REQUEST_SET			0x01
+
+#define ICP_INIT_RESP_RESET			0x00
+#define ICP_INIT_RESP_SUCCESS			0x01
+#define	ICP_INIT_RESP_FAILED			0x02
+
+/* Command / Response groups */
+#define HFI_CMD_GRP_ICP				0
+#define HFI_CMD_GRP_IPE_BPS			BIT(24)
+#define HFI_CMD_GRP_CDM				BIT(25)
+#define HFI_CMD_GRP_DBG				(BIT(24) | BIT(25))
+
+/* Command / Response offsets */
+#define HFI_CMD_BASE				BIT(16)
+#define HFI_RSP_BASE				BIT(17)
+
+/* System Commands */
+#define HFI_CMD_SYS_INIT			0x10001
+#define HFI_CMD_SYS_PC_PREP			0x10002
+#define HFI_CMD_SYS_SET_PROPERTY		0x10003
+#define HFI_CMD_SYS_GET_PROPERTY		0x10004
+#define HFI_CMD_SYS_PING			0x10005
+#define HFI_CMD_SYS_RESET			0x10006
+
+/* ICP Firmware result codes */
+#define CAMERAICP_OK				0x00
+#define CAMERAICP_EFAILED			0x01
+#define CAMERAICP_ENOMEMORY			0x02
+#define CAMERAICP_EBADSTATE			0x03
+#define CAMERAICP_EBADPARM			0x04
+#define CAMERAICP_EBADITEM			0x05
+#define CAMERAICP_EINVALIDFORMAT		0x06
+#define CAMERAICP_EUNSUPPORTED			0x07
+#define CAMERAICP_EOUTOFBOUND			0x08
+#define CAMERAICP_ETIMEDOUT			0x09
+#define CAMERAICP_EABORTED			0x0a
+#define CAMERAICP_EHWVIOLATION			0x0b
+#define CAMERAICP_ECDMERROR			0x0c
+
+/* System Messages */
+#define HFI_MSG_ICP_COMMON_START		0x20000
+#define HFI_MSG_SYS_INIT_DONE			0x20001
+#define HFI_MSG_SYS_PC_PREP_DONE		0x20002
+#define HFI_MSG_SYS_DEBUG			0x20003
+#define HFI_MSG_SYS_IDLE			0x20004
+#define HFI_MSG_SYS_PROPERTY_INFO		0x20005
+#define HFI_MSG_SYS_PING_ACK			0x20006
+#define HFI_MSG_SYS_RESET_ACK			0x20007
+#define HFI_MSG_EVENT_NOTIFY			0x20008
+
+/* IPE/BPS Commands */
+#define HFI_CMD_IPEBPS_CREATE_HANDLE		0x1010008
+#define HFI_CMD_IPEBPS_ASYNC_COMMAND_DIRECT	0x101000a
+#define HFI_CMD_IPEBPS_ASYNC_COMMAND_INDIRECT	0x101000e
+
+/* IPE/BPS Messages */
+#define HFI_MSG_IPEBPS_CREATE_HANDLE_ACK	0x1020008
+#define HFI_MSG_IPEBPS_ASYNC_DIRECT_ACK		0x102000a
+#define HFI_MSG_IPEBPS_ASYNC_INDIRECT_ACK	0x102000e
+
+/* Opcodes */
+#define HFI_IPEBPS_CMD_OPCODE_BPS_CONFIG_IO	0x1
+#define HFI_IPEBPS_CMD_OPCODE_BPS_FRAME_PROCESS	0x2
+#define HFI_IPEBPS_CMD_OPCODE_BPS_ABORT		0x3
+#define HFI_IPEBPS_CMD_OPCODE_BPS_DESTROY	0x4
+
+#define HFI_IPEBPS_CMD_OPCODE_IPE_CONFIG_IO	0x5
+#define HFI_IPEBPS_CMD_OPCODE_IPE_FRAME_PROCESS	0x6
+#define HFI_IPEBPS_CMD_OPCODE_IPE_ABORT		0x7
+#define HFI_IPEBPS_CMD_OPCODE_IPE_DESTROY	0x8
+
+#define HFI_IPEBPS_CMD_OPCODE_BPS_WAIT_FOR_IPE	0x9
+#define HFI_IPEBPS_CMD_OPCODE_BPS_WAIT_FOR_BPS	0xa
+#define HFI_IPEBPS_CMD_OPCODE_IPE_WAIT_FOR_BPS	0xb
+#define HFI_IPEBPS_CMD_OPCODE_IPE_WAIT_FOR_IPE	0xc
+
+#define HFI_IPEBPS_CMD_OPCODE_MEM_MAP		0xe
+#define HFI_IPEBPS_CMD_OPCODE_MEM_UNMAP		0xf
+
+/* Device Types */
+#define HFI_DEV_TYPE_BPS			1
+#define HFI_DEV_TYPE_IPE_RT			2
+#define HFI_DEV_TYPE_IPE			3
+#define HFI_DEV_TYPE_IPE_SEMI_RT		4
+#define HFI_DEV_TYPE_BPS_RT			5
+#define HFI_DEV_TYPE_BPS_SEMI_RT		6
+
+/* Events */
+#define HFI_EVENT_SYS_ERROR			0x01
+#define HFI_EVENT_ICP_ERROR			0x02
+#define HFI_EVENT_IPE_BPS_ERROR			0x03
+#define HFI_EVENT_CDM_ERROR			0x04
+#define HFI_EVENT_DBG_ERROR			0x05
+
+/* Property Types */
+#define HFI_PROP_SYS_DEBUG_CFG			0x01
+#define HFI_PROP_SYS_UBWC_CFG			0x02
+#define HFI_PROP_SYS_IMAGE_VER			0x03
+#define HFI_PROP_SYS_SUPPORTED			0x04
+#define HFI_PROP_SYS_IPEBPS_PC			0x05
+
+/*
+ * Debug levels
+ */
+#define HFI_DEBUG_MSG_LOW			BIT(0)
+#define HFI_DEBUG_MSG_MEDIUM			BIT(1)
+#define HFI_DEBUG_MSG_HIGH			BIT(2)
+#define HFI_DEBUG_MSG_ERROR			BIT(3)
+#define HFI_DEBUG_MSG_FATAL			BIT(4)
+#define HFI_DEBUG_MSG_PERF			BIT(5)
+
+/*
+ * Debug output modes
+ */
+#define HFI_DEBUG_MODE_QUEUE			0x00000001
+#define HFI_DEBUG_MODE_QDSS			0x00000002
+
+/*
+ * Handle types for CREATE_HANDLE
+ */
+#define HFI_HANDLE_TYPE_BPS			1
+#define HFI_HANDLE_TYPE_IPE			2
+
+/*
+ * Queue indexes
+ */
+enum {
+	HFI_Q_CMD_TYPE = 0,
+	HFI_Q_MSG_TYPE,
+	HFI_Q_DBG_TYPE,
+	HFI_Q_MAX,
+};
+
+/* Memory Sizes */
+#define HFI_QTBL_SIZE				SZ_1M
+#define HFI_CMD_Q_SIZE				SZ_1M
+#define HFI_MSG_Q_SIZE				SZ_1M
+#define HFI_DBG_Q_SIZE				SZ_1M
+#define HFI_SFR_LOG_SIZE			SZ_4K
+
+#define HFI_CMD_Q_DATA_SIZE			SZ_4K
+#define HFI_MSG_Q_DATA_SIZE			SZ_4K
+#define HFI_DBG_Q_DATA_SIZE			102400
+
+#define HFI_SHMEM_SIZE				SZ_1M
+#define HFI_FWUNCACHED_SIZE			(7 * SZ_1M)
+#define HFI_QDSS_SIZE				SZ_1M
+#define HFI_QTBL_SIZE				SZ_1M
+#define HFI_Q_SIZE				SZ_1M
+#define HFI_SFR_SIZE				SZ_8K
+#define HFI_SECHEAP_SIZE			SZ_1M
+
+/*
+ * General Purpose registers
+ *
+ * GP registers start at CSR base + 0x20. CAMX defines them as:
+ *   GEN_PURPOSE_REG(n) = n * 4, relative to (CSR + 0x20)
+ *
+ * So GP0 = CSR+0x20, GP1 = CSR+0x24, GP2 = CSR+0x28, GP3 = CSR+0x2C, etc.
+ *
+ * Offsets below are absolute from CSR base for direct use with csr_base.
+ */
+#define HFI_REG_FW_VERSION			0x24  /* GP1 - firmware writes version */
+#define HFI_REG_HOST_ICP_INIT_REQ		0x28  /* GP2 - host signals init request */
+#define HFI_REG_ICP_HOST_INIT_RESP		0x2C  /* GP3 - firmware signals ready */
+#define HFI_REG_SHARED_MEM_PTR			0x30  /* GP4 - shared memory IOVA */
+#define HFI_REG_SHARED_MEM_SIZE			0x34  /* GP5 - shared memory size */
+#define HFI_REG_QTBL_PTR			0x38  /* GP6 - q table IOVA */
+#define HFI_REG_SECONDARY_HEAP_PTR		0x3C  /* GP7 - secondary heap IOVA */
+#define HFI_REG_SECONDARY_HEAP_SIZE		0x40  /* GP8 - secondary heap size */
+#define HFI_REG_RESERVED			0x44  /* GP9 - reserved/status */
+#define HFI_REG_SFR_PTR				0x48  /* GP10 - SFR buffer IOVA */
+#define HFI_REG_QDSS_IOVA			0x4C  /* GP11 - QDSS buffer IOVA */
+#define HFI_REG_QDSS_IOVA_SIZE			0x50  /* GP12 - QDSS buffer size */
+#define HFI_REG_IO_REGION_1_IOVA		0x54  /* GP13 - IO region 1 IOVA */
+#define HFI_REG_IO_REGION_1_SIZE		0x58  /* GP14 - IO region 1 size */
+#define HFI_REG_IO_REGION_2_IOVA		0x5C  /* GP15 - IO region 2 IOVA */
+#define HFI_REG_IO_REGION_2_SIZE		0x60  /* GP16 - IO region 2 size */
+#define HFI_REG_FWUNCACHED_IOVA			0x64  /* GP17 - FW uncached region IOVA */
+#define HFI_REG_FWUNCACHED_SIZE			0x68  /* GP18 - FW uncached region size */
+
+/* HFI constants */
+#define HFI_QUEUE_TABLE_VERSION			0xFFFFFFFF
+
+#define HFI_BYTE_WORD_SHIFT			0x02
+
+#define HFI_MAX_PROPS				16
+
+struct hfi_resources {
+	u32 shmem_size;
+	u32 qdss_size;
+	u32 fwuncached_size;
+	u32 secheap_size;
+	u32 q_tbl_size;
+	u32 qdata_size[HFI_Q_MAX];
+	u32 sfr_size;
+};
+
+struct icp_hfi_mem_region {
+	void *vaddr;
+	dma_addr_t dma_addr;
+	size_t size;
+};
+
+struct icp_hfi_mem {
+	/* These get their own allocations */
+	struct icp_hfi_mem_region shmem;
+	struct icp_hfi_mem_region qdss;
+	struct icp_hfi_mem_region fwuncached;
+
+	/* Pointers into the fwuncached region */
+	struct icp_hfi_mem_region secheap;
+	struct icp_hfi_mem_region q_tbl;
+	struct icp_hfi_mem_region q_data[HFI_Q_MAX];
+	struct icp_hfi_mem_region sfr;
+};
+
+struct icp_hfi_ops {
+	void (*raise_irq)(void *priv);
+};
+
+struct icp_hfi {
+	struct device *dev;
+	struct icp_hfi_mem hfi_mem;
+	const struct hfi_resources *res;
+
+	const struct icp_hfi_ops *ops;
+	void *priv;
+
+	/*
+	 * Queue Table Pointer
+	 * Points to hfi_q_tbl_hdr at hfi_mem.q_tbl.vaddr
+	 * Contains header + 4 x struct hfi_q_header
+	 */
+	struct hfi_q_tbl_hdr *q_tbl;
+
+	/*
+	* Synchronization
+	*/
+	struct mutex cmd_lock;
+	struct completion cmd_complete;
+
+	/*
+	 * State
+	 */
+	bool ready;         /* HFI fully initialized */
+	u32 fw_version;     /* From GP1 register after boot */
+	u32 api_version;    /* From INIT_DONE response */
+
+	/* Properties */
+	u32 prop_num;
+	u32 properties[HFI_MAX_PROPS];
+
+	/* Debug */
+	struct hfi_sfr *sfr;
+};
+
+/*
+ * HFI Queue Header - CAMX compatible
+ * Each field is padded to 64 bytes (16 u32s) for cache line alignment.
+ * The firmware expects this exact layout.
+ */
+struct hfi_q_hdr {
+	u32 dummy0[15];
+	u32 status;
+	u32 dummy1[15];
+	u32 start_addr;
+	u32 dummy2[15];
+	u32 type;
+	u32 dummy3[15];
+	u32 q_size;
+	u32 dummy4[15];
+	u32 pkt_size;
+	u32 dummy5[15];
+	u32 pkt_drop_cnt;
+	u32 dummy6[15];
+	u32 rx_wm;
+	u32 dummy7[15];
+	u32 tx_wm;
+	u32 dummy8[15];
+	u32 rx_req;
+	u32 dummy9[15];
+	u32 tx_req;
+	u32 dummy10[15];
+	u32 rx_irq_status;
+	u32 dummy11[15];
+	u32 tx_irq_status;
+	u32 dummy12[15];
+	u32 read_idx;
+	u32 dummy13[15];
+	u32 write_idx;
+	u32 dummy14[15];
+} __packed;
+
+struct hfi_q_tbl_hdr {
+	u32 version;
+	u32 size;
+	u32 q_hdr0_offset;
+	u32 q_hdr_size;
+	u32 num_queues;
+	u32 num_active_queues;
+	struct hfi_q_hdr q_hdr[];
+} __packed;
+
+struct hfi_pkt_hdr {
+	u32 size;
+	u32 type;
+} __packed;
+
+struct hfi_cmd_sys_init {
+	struct hfi_pkt_hdr hdr;
+} __packed;
+
+struct hfi_msg_init_done {
+	struct hfi_pkt_hdr hdr;
+	u32 error;
+	u32 prop_num;
+	u32 prop_data[];
+} __packed;
+
+struct hfi_cmd_ping {
+	struct hfi_pkt_hdr hdr;
+	u64 user_data;
+} __packed;
+
+struct hfi_msg_ping_ack {
+	struct hfi_pkt_hdr hdr;
+	u64 user_data;
+} __packed;
+
+struct hfi_msg_event {
+	struct hfi_pkt_hdr hdr;
+	u32 session_id;
+	u32 event_id;
+	u32 data1;
+	u32 data2;
+} __packed;
+
+struct hfi_cmd_ubwc_cfg {
+	struct hfi_pkt_hdr hdr;
+	u32 num_params;
+	u32 prop_type;
+	u32 ipe_fetch;
+	u32 ipe_write;
+	u32 bps_fetch;
+	u32 bps_write;
+} __packed;
+
+struct hfi_cmd_create_handle {
+	struct hfi_pkt_hdr hdr;
+	u32 handle_type;
+	u64 user_data0;
+	u64 user_data1;
+} __packed;
+
+struct hfi_msg_create_handle_ack {
+	struct hfi_pkt_hdr hdr;
+	u32 error;
+	u32 fw_handle;
+	u64 user_data0;
+	u64 user_data1;
+} __packed;
+
+struct hfi_cmd_async {
+	struct hfi_pkt_hdr hdr;
+	u32 opcode;
+	u32 fw_handle;
+	u64 user_data0;
+	u64 user_data1;
+	u32 num_handles;
+	u32 handle[1];
+	u32 payload[];
+} __packed;
+
+#define HFI_MSG_ASYNC_MAX_MSG 32
+struct hfi_msg_async_ack {
+	struct hfi_pkt_hdr hdr;
+	u32 opcode;
+	u64 user_data0;
+	u64 user_data1;
+	u32 error;
+	u32 msg[HFI_MSG_ASYNC_MAX_MSG];
+} __packed;
+
+struct hfi_cmd_set_property {
+	struct hfi_pkt_hdr hdr;
+	u32 num_prop;
+	u32 prop_data[];
+} __packed;
+
+struct hfi_msg_debug {
+	struct hfi_pkt_hdr hdr;
+	u32 msg_type;
+	u32 msg_size;
+	u32 timestamp_hi;
+	u32 timestamp_lo;
+	u8  msg_data[];
+} __packed;
+
+struct hfi_debug_cfg {
+	u32 debug_config;
+	u32 debug_mode;
+} __packed;
+
+struct hfi_msg_debug_level {
+	struct hfi_cmd_set_property hdr;
+	u32 prop_id;
+	struct hfi_debug_cfg cfg;
+} __packed;
+
+struct hfi_sfr {
+	u32 size;
+	char msg[HFI_SFR_LOG_SIZE];
+};
+
+static inline u32 hfi_pkt_size(void *pkt)
+{
+	struct hfi_pkt_hdr *pkt_hdr = pkt;
+	return pkt_hdr->size;
+}
+
+static inline u32 hfi_pkt_type(void *pkt)
+{
+	struct hfi_pkt_hdr *pkt_hdr = pkt;
+	return pkt_hdr->type;
+}
+
+static inline bool hfi_queue_empty(struct hfi_q_hdr *q)
+{
+	return READ_ONCE(q->read_idx) == READ_ONCE(q->write_idx);
+}
+
+static inline u32 hfi_queue_free(struct hfi_q_hdr *q)
+{
+	u32 ri = READ_ONCE(q->read_idx);
+	u32 wi = READ_ONCE(q->write_idx);
+	u32 used = (wi >= ri) ? (wi - ri) : (q->q_size - ri + wi);
+
+	return q->q_size - used - 1;
+}
+
+/* Queue management */
+int icp_hfi_init_queues(struct icp_hfi *hfi);
+void icp_hfi_deinit_queues(struct icp_hfi *hfi);
+
+/* ISR support - called from threaded IRQ handler */
+void icp_hfi_flush_debug_queue(struct icp_hfi *hfi);
+bool icp_hfi_process_msg_queue(struct icp_hfi *hfi);
+
+/* Core operations */
+int icp_hfi_core_init(struct icp_hfi *hfi);
+void icp_hfi_dump_sfr(struct icp_hfi *hfi);
+
+/**
+ * icp_set_ubwc_config - Set UBWC configuration for device
+ * @icp: ICP device
+ * @dev_type: HFI_DEV_TYPE_IPE or HFI_DEV_TYPE_BPS
+ * @cfg: UBWC configuration
+ *
+ * Should be called during IPE/BPS probe, before any contexts
+ * are created. ICP will send config to firmware on boot.
+ */
+int icp_hfi_set_ubwc_config(struct icp_device *icp, u32 dev_type,
+			    struct icp_ubwc_cfg *cfg);
+
+/**
+ * icp_ctx_create - Create processing context
+ * @icp: ICP device
+ * @dev_type: HFI_DEV_TYPE_IPE or HFI_DEV_TYPE_BPS
+ * @callback: Completion callback (called from workqueue)
+ * @priv: Private data for callback
+ *
+ * Returns context or ERR_PTR on failure.
+ */
+int icp_hfi_create_handle(struct icp_hfi *hfi, u32 handle_type,
+			  u64 ctx, u32 *fw_handle, u32 timeout_ms);
+
+/**
+ * icp_ctx_destroy - Destroy processing context
+ * @ctx: Context to destroy
+ */
+int icp_hfi_destroy_handle(struct icp_hfi *hfi, u32 handle_type,
+			   u64 ctx, u32 fw_handle, u32 timeout_ms);
+#if 0
+/**
+ * icp_ctx_config_io - Configure IO buffers for context
+ * @ctx: Processing context
+ * @cfg_iova: IOVA of configuration buffer
+ * @cfg_size: Size of configuration buffer
+ */
+int icp_ctx_config_io(struct icp_context *ctx, dma_addr_t cfg_iova, u32 cfg_size);
+
+/**
+ * icp_ctx_submit_frame - Submit frame for processing
+ * @ctx: Processing context
+ * @req: Frame request descriptor
+ *
+ * Asynchronous - completion via callback.
+ */
+int icp_ctx_submit_frame(struct icp_context *ctx, struct icp_frame_request *req);
+
+/**
+ * icp_ctx_abort - Abort pending operations
+ * @ctx: Processing context
+ */
+int icp_ctx_abort(struct icp_context *ctx);
+
+/**
+ * icp_ctx_wait - Wait for operation completion
+ * @ctx: Processing context
+ * @timeout_ms: Timeout in milliseconds
+ *
+ * Returns 0 on success, -ETIMEDOUT on timeout.
+ */
+int icp_ctx_wait(struct icp_context *ctx, unsigned long timeout_ms);
+#endif
+
+#endif /* __CAMSS_ICP_HFI_H__ */
diff --git a/drivers/media/platform/qcom/camss/camss-icp.c b/drivers/media/platform/qcom/camss/camss-icp.c
new file mode 100644
index 0000000000000..1617dea15a5a7
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-icp.c
@@ -0,0 +1,623 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Qualcomm ICP (Image Control Processor) driver for X1E80100
+ *
+ */
+#define DEBUG
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/interconnect.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iommu.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/soc/qcom/mdt_loader.h>
+
+#include "camss-icp.h"
+#include "camss-icp-hfi.h"
+
+/* CSR register offsets */
+#define ICP_CSR_HW_VERSION		0x00
+#define ICP_CSR_TCM_SIZE		0x08
+#define ICP_CSR_DBG_STATUS		0x44
+#define ICP_CSR_DBG_CTRL		0x48
+
+/* CIRQ register offsets */
+#define CIRQ_OB_MASK			0x00
+#define CIRQ_OB_STATUS			0x04
+#define CIRQ_OB_CLEAR			0x08
+#define CIRQ_HOST2ICPINT		0x124
+
+/* CIRQ bits */
+#define CIRQ_ICP2HOSTINT		BIT(0)
+#define CIRQ_WDT_BITE_WS0		BIT(6)
+
+#define ICP_CLK_MAX			32
+
+#define POLLING_SLEEP_US		1000
+#define POLLING_TIMEOUT_US		20000
+
+/* HFI polling - match CAMX: 100us interval, 2 second timeout */
+#define HFI_POLL_DELAY_US		100
+#define HFI_POLL_TIMEOUT_US		2000000
+
+/* ICC paths */
+#define HFI_MAX_ICC_PATHS		4
+struct camss_icp_resources {
+	int pas_id;
+	const char ** const clk_names;
+	int clk_num;
+	const char ** const noc_names;
+	int noc_num;
+	struct hfi_resources hfi_res;
+};
+
+struct camss_icp {
+	struct device *dev;
+	void __iomem *csr_base;
+	void __iomem *cirq_base;
+	int irq;
+
+	struct dev_pm_domain_list *pd_list;
+
+	struct clk_bulk_data clks[ICP_CLK_MAX];
+	struct icc_path *icc_path[HFI_MAX_ICC_PATHS];
+
+	struct icp_hfi hfi;
+
+	phys_addr_t fw_phys;
+	size_t fw_size;
+
+	const struct camss_icp_resources *icp_res;
+};
+
+struct icp_hfi *icp_hfi_get(struct device *dev)
+{
+	struct camss_icp *icp = dev_get_drvdata(dev);
+
+	if (!icp)
+		return ERR_PTR(-ENODEV);
+
+	get_device(icp->dev);
+	return &icp->hfi;
+}
+EXPORT_SYMBOL(icp_hfi_get);
+
+void icp_hfi_put(struct icp_hfi *phfi)
+{
+	struct camss_icp *icp = container_of(phfi, struct camss_icp, hfi);
+
+	put_device(icp->dev);
+}
+EXPORT_SYMBOL(icp_hfi_put);
+
+/*
+ * Raise interrupt to firmware - called by HFI layer after CMD_Q write
+ */
+static void icp_raise_irq(void *priv)
+{
+	struct camss_icp *icp = priv;
+
+	dev_dbg(icp->dev, "Raising HOST2ICPINT (CIRQ STATUS before: 0x%x)\n",
+		readl(icp->cirq_base + CIRQ_OB_STATUS));
+
+	writel(1, icp->cirq_base + CIRQ_HOST2ICPINT);
+
+	/* Small delay then check if anything changed */
+	udelay(100);
+	dev_dbg(icp->dev, "CIRQ STATUS after HOST2ICPINT: 0x%x\n",
+		readl(icp->cirq_base + CIRQ_OB_STATUS));
+}
+
+/* Load firmware */
+static int icp_load_firmware(struct camss_icp *icp)
+{
+	const struct camss_icp_resources *icp_res = icp->icp_res;
+	const struct firmware *fw;
+	struct device_node *node;
+	struct resource res;
+	const char *fw_name;
+	char fw_path[64];
+	void *vaddr;
+	ssize_t fw_size;
+	int ret;
+
+	ret = of_property_read_string(icp->dev->of_node, "firmware-name", &fw_name);
+	if (ret)
+		return ret;
+
+	snprintf(fw_path, sizeof(fw_path), "%s.mdt", fw_name);
+
+	node = of_parse_phandle(icp->dev->of_node, "memory-region", 0);
+	if (!node)
+		return -ENODEV;
+
+	ret = of_address_to_resource(node, 0, &res);
+	of_node_put(node);
+	if (ret)
+		return ret;
+
+	dev_dbg(icp->dev, "FW memory: phys=0x%pa size=%llu\n",
+		&res.start, resource_size(&res));
+
+	ret = firmware_request_nowarn(&fw, fw_path, icp->dev);
+	if (ret)
+		return ret;
+
+	fw_size = qcom_mdt_get_size(fw);
+	if (fw_size < 0 || (size_t)fw_size > resource_size(&res)) {
+		release_firmware(fw);
+		return -EINVAL;
+	}
+
+	vaddr = ioremap_wc(res.start, resource_size(&res));
+	if (!vaddr) {
+		release_firmware(fw);
+		return -ENOMEM;
+	}
+
+	ret = qcom_mdt_load(icp->dev, fw, fw_path, icp_res->pas_id, vaddr,
+			    res.start, resource_size(&res), NULL);
+	iounmap(vaddr);
+	release_firmware(fw);
+
+	if (ret == 0) {
+		dev_dbg(icp->dev, "Firmware loaded: %s (%zd bytes)\n", fw_path, fw_size);
+		icp->fw_phys = res.start;
+		icp->fw_size = fw_size;
+	}
+
+	return ret;
+}
+
+static irqreturn_t camss_icp_isr_thread(int irq, void *data)
+{
+	struct camss_icp *icp = data;
+	u32 status;
+
+	/* Re-read status for logging */
+	status = readl(icp->cirq_base + CIRQ_OB_STATUS);
+
+	dev_dbg(icp->dev, "IRQ thread: status=0x%x\n", status);
+
+	if (status & CIRQ_WDT_BITE_WS0) {
+		dev_err(icp->dev, "ICP watchdog bite!\n");
+		icp_hfi_dump_sfr(&icp->hfi);
+	}
+
+	/* Process message queue - signals waiters if data present */
+	icp_hfi_process_msg_queue(&icp->hfi);
+
+	/* Flush debug queue - async debug messages */
+	icp_hfi_flush_debug_queue(&icp->hfi);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t camss_icp_isr(int irq, void *data)
+{
+	struct camss_icp *icp = data;
+	u32 status;
+
+	dev_dbg(icp->dev, "IRQ ISR\n");
+
+	status = readl(icp->cirq_base + CIRQ_OB_STATUS);
+	if (!status)
+		return IRQ_NONE;
+
+	writel(status, icp->cirq_base + CIRQ_OB_CLEAR);
+
+	return IRQ_WAKE_THREAD;
+}
+
+/* Dump GP registers for debugging */
+void icp_dump_gp_regs(struct camss_icp *icp, const char *label)
+{
+	u32 regs[20];
+	int i;
+
+	dev_info(icp->dev, "=== %s ===\n", label);
+	dev_info(icp->dev, "Raw CSR offsets 0x20-0x6C (GP registers):\n");
+
+	for (i = 0; i < 20; i++)
+		regs[i] = readl(icp->csr_base + 0x20 + i * 4);
+
+	for (i = 0; i < 20; i += 4)
+		dev_info(icp->dev, "  [0x%02x]: %08x %08x %08x %08x\n",
+			 0x20 + i * 4, regs[i], regs[i+1], regs[i+2], regs[i+3]);
+}
+
+void icp_hfi_latch_regs(struct camss_icp *icp)
+{
+	struct icp_hfi *hfi = &icp->hfi;
+	struct device *dev = hfi->dev;
+
+	/* Shared memory */
+	writel(hfi->hfi_mem.shmem.dma_addr, icp->csr_base + HFI_REG_SHARED_MEM_PTR);
+	writel(hfi->hfi_mem.shmem.size, icp->csr_base + HFI_REG_SHARED_MEM_SIZE);
+
+	/* QTBL - header describing HFI queues */
+	writel(hfi->hfi_mem.q_tbl.dma_addr, icp->csr_base + HFI_REG_QTBL_PTR);
+
+	/* QDSS */
+	writel(hfi->hfi_mem.qdss.dma_addr, icp->csr_base + HFI_REG_QDSS_IOVA);
+	writel(HFI_QDSS_SIZE, icp->csr_base + HFI_REG_QDSS_IOVA_SIZE);
+
+	/* FW Uncached */
+	writel(hfi->hfi_mem.fwuncached.dma_addr, icp->csr_base + HFI_REG_FWUNCACHED_IOVA);
+	writel(HFI_FWUNCACHED_SIZE, icp->csr_base + HFI_REG_FWUNCACHED_SIZE);
+
+	/* Sec heap */
+	writel(hfi->hfi_mem.secheap.dma_addr, icp->csr_base + HFI_REG_SECONDARY_HEAP_PTR);
+	writel(HFI_SECHEAP_SIZE, icp->csr_base + HFI_REG_SECONDARY_HEAP_SIZE);
+
+	/* SFR buffer */
+	writel(hfi->hfi_mem.sfr.dma_addr, icp->csr_base + HFI_REG_SFR_PTR);
+
+	/* IO regions - use standard CAMX values TODO: fix this */
+	writel(0x10c00000, icp->csr_base + HFI_REG_IO_REGION_1_IOVA);
+	writel(0xcf400000, icp->csr_base + HFI_REG_IO_REGION_1_SIZE);
+	writel(0xe0800000, icp->csr_base + HFI_REG_IO_REGION_2_IOVA);
+	writel(0x1e700000, icp->csr_base + HFI_REG_IO_REGION_2_SIZE);
+
+}
+
+/* HFI operations */
+static const struct icp_hfi_ops hfi_ops = {
+	.raise_irq = icp_raise_irq,
+};
+
+static int icp_boot(struct camss_icp *icp)
+{
+	const struct camss_icp_resources *icp_res = icp->icp_res;
+	u32 hw_version, data;
+	int i, ret;
+
+	/* Power on all domains via runtime PM */
+	ret = pm_runtime_resume_and_get(icp->dev);
+	if (ret) {
+		dev_err(icp->dev, "Failed to power on: %d\n", ret);
+		return ret;
+	}
+
+	/* Enable clocks */
+	ret = clk_bulk_prepare_enable(icp_res->clk_num, icp->clks);
+	if (ret) {
+		dev_err(icp->dev, "Failed to enable clocks: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < icp_res->noc_num; i++) {
+		if (icp->icc_path[i]) {
+			dev_dbg(icp->dev, "Voting for BW now %s\n", icp_res->noc_names[i]);
+			ret = icc_set_bw(icp->icc_path[i], 100000000, 1000000000);
+			if (ret) {
+				dev_err(icp->dev, "Voting for %s failed\n", icp_res->noc_names[i]);
+				return ret;
+			}
+		}
+	}
+
+	/* Verify HW version */
+	hw_version = readl(icp->csr_base + ICP_CSR_HW_VERSION);
+	dev_info(icp->dev, "HW version: 0x%08x\n", hw_version);
+
+	if (hw_version == 0 || hw_version == 0xffffffff) {
+		dev_err(icp->dev, "Invalid HW version\n");
+		ret = -EIO;
+		goto err_clk;
+	}
+
+	/* Configure interrupts */
+	writel(0x7f, icp->cirq_base + CIRQ_OB_CLEAR);
+	writel(CIRQ_ICP2HOSTINT | CIRQ_WDT_BITE_WS0, icp->cirq_base + CIRQ_OB_MASK);
+
+	/* Load firmware */
+	ret = icp_load_firmware(icp);
+	if (ret)
+		goto err_clk;
+
+	/* Allocate HFI queues */
+	ret = icp_hfi_init_queues(&icp->hfi);
+	if (ret)
+		goto err_clk;
+
+	/* Start firmware via TrustZone */
+	ret = qcom_scm_pas_auth_and_reset(icp_res->pas_id);
+	if (ret) {
+		dev_err(icp->dev, "TZ auth_and_reset failed: %d\n", ret);
+		goto err_hfi;
+	}
+
+	/* Wait for firmware to boot */
+	usleep_range(5000, 51000);
+
+	/* Program HFI pointers after bootup */
+	icp_hfi_latch_regs(icp);
+
+	/*
+	 * Boot handshake with ICP firmware.
+	 *
+	 * After TZ starts the ICP, firmware waits for host to program
+	 * GP registers with memory addresses and signal readiness.
+	 *
+	 * Register assignments:
+	 *   GP1 (0x24) = FW_VERSION        - firmware writes its version
+	 *   GP2 (0x28) = HOST_ICP_INIT_REQ - host writes 1 ("addresses ready")
+	 *   GP3 (0x2C) = ICP_HOST_INIT_RESP - firmware writes 1 ("ready")
+	 *   GP4-GP18   = memory addresses   - host programs with IOVAs
+	 *
+	 * Sequence:
+	 *   1. Program GP4-GP18 with memory addresses  [done above]
+	 *   2. Write GP2 = 1 (HOST_ICP_INIT_REQUEST)
+	 *   3. Poll GP3 until ICP_INIT_RESP_SUCCESS (1)
+	 *   4. Read GP1 for firmware version
+	 *
+	 */
+
+	/* Signal firmware */
+	writel(ICP_INIT_REQUEST_SET, icp->csr_base + HFI_REG_HOST_ICP_INIT_REQ);
+
+	wmb();
+
+	/* Wait for firmware to signal ready via GP3 */
+	ret = readl_poll_timeout(icp->csr_base + HFI_REG_ICP_HOST_INIT_RESP,
+				 data, data == ICP_INIT_RESP_SUCCESS,
+				 HFI_POLL_DELAY_US, HFI_POLL_TIMEOUT_US);
+
+	if (ret < 0) {
+		dev_err(icp->dev, "Firmware ready timeout (GP3=0x%08x)\n",
+			readl(icp->csr_base + HFI_REG_ICP_HOST_INIT_RESP));
+		dev_err(icp->dev, "  GP1 (FW_VERSION)=0x%08x GP2 (INIT_REQ)=0x%08x\n",
+			readl(icp->csr_base + HFI_REG_FW_VERSION),
+			readl(icp->csr_base + HFI_REG_HOST_ICP_INIT_REQ));
+		icp_dump_gp_regs(icp, "FW READY TIMEOUT");
+		icp_hfi_dump_sfr(&icp->hfi);
+		ret = -ETIMEDOUT;
+		goto err_hfi;
+	}
+
+	/* Read firmware version from GP1 */
+	icp->hfi.fw_version = readl(icp->csr_base + HFI_REG_FW_VERSION);
+	dev_dbg(icp->dev, "Firmware ready! version=0x%08x\n", icp->hfi.fw_version);
+
+	/* Dump CIRQ state after FW init */
+	dev_info(icp->dev, "CIRQ after FW init: MASK=0x%x STATUS=0x%x\n",
+		 readl(icp->cirq_base + CIRQ_OB_MASK),
+		 readl(icp->cirq_base + CIRQ_OB_STATUS));
+
+	/* Dump GP registers to verify what firmware sees */
+	dev_info(icp->dev, "GP registers after FW init:\n");
+	dev_info(icp->dev, "  GP0 (unused):        0x%08x\n", readl(icp->csr_base + 0x20));
+	dev_info(icp->dev, "  GP1 (FW_VERSION):    0x%08x\n", readl(icp->csr_base + HFI_REG_FW_VERSION));
+	dev_info(icp->dev, "  GP2 (INIT_REQ):      0x%08x\n", readl(icp->csr_base + HFI_REG_HOST_ICP_INIT_REQ));
+	dev_info(icp->dev, "  GP3 (INIT_RESP):     0x%08x\n", readl(icp->csr_base + HFI_REG_ICP_HOST_INIT_RESP));
+	dev_info(icp->dev, "  GP4 (SHMEM_PTR):     0x%08x (we wrote: 0x%08x)\n",
+		 readl(icp->csr_base + HFI_REG_SHARED_MEM_PTR), (u32)icp->hfi.hfi_mem.shmem.dma_addr);
+	dev_info(icp->dev, "  GP5 (SHMEM_SIZE):    0x%08x (we wrote: 0x%08x)\n",
+		 readl(icp->csr_base + HFI_REG_SHARED_MEM_SIZE), (u32)icp->hfi.hfi_mem.shmem.size);
+	dev_info(icp->dev, "  GP6 (QTBL_PTR):      0x%08x (we wrote: 0x%08x)\n",
+		 readl(icp->csr_base + HFI_REG_QTBL_PTR), (u32)icp->hfi.hfi_mem.q_tbl.dma_addr);
+	dev_info(icp->dev, "  GP7 (SECHEAP_PTR):   0x%08x (we wrote: 0x%08x)\n",
+		 readl(icp->csr_base + HFI_REG_SECONDARY_HEAP_PTR), (u32)icp->hfi.hfi_mem.secheap.dma_addr);
+	dev_info(icp->dev, "  GP8 (SECHEAP_SIZE):  0x%08x (we wrote: 0x%08x)\n",
+		 readl(icp->csr_base + HFI_REG_SECONDARY_HEAP_SIZE), HFI_SECHEAP_SIZE);
+	dev_info(icp->dev, "  GP9 (STATUS):        0x%08x\n", readl(icp->csr_base + HFI_REG_RESERVED));
+	dev_info(icp->dev, "  GP10 (SFR_PTR):      0x%08x (we wrote: 0x%08x)\n",
+		 readl(icp->csr_base + HFI_REG_SFR_PTR), (u32)icp->hfi.hfi_mem.sfr.dma_addr);
+	dev_info(icp->dev, "  GP11 (QDSS_IOVA):    0x%08x (we wrote: 0x%08x)\n",
+		 readl(icp->csr_base + HFI_REG_QDSS_IOVA), (u32)icp->hfi.hfi_mem.qdss.dma_addr);
+	dev_info(icp->dev, "  GP12 (QDSS_SIZE):    0x%08x\n", readl(icp->csr_base + HFI_REG_QDSS_IOVA_SIZE));
+	dev_info(icp->dev, "  GP17 (FWUNCACHED):   0x%08x (we wrote: 0x%08x)\n",
+		 readl(icp->csr_base + HFI_REG_FWUNCACHED_IOVA), (u32)icp->hfi.hfi_mem.fwuncached.dma_addr);
+	dev_info(icp->dev, "  GP18 (FWUNC_SIZE):   0x%08x (we wrote: 0x%08x)\n",
+		 readl(icp->csr_base + HFI_REG_FWUNCACHED_SIZE), HFI_FWUNCACHED_SIZE);
+
+	return 0;
+err_hfi:
+	icp_hfi_deinit_queues(&icp->hfi);
+err_clk:
+	for (i = 0; i < icp_res->noc_num; i++) {
+		if (icp->icc_path[i])
+			icc_set_bw(icp->icc_path[i], 0, 0);
+	}
+	clk_bulk_disable_unprepare(icp_res->clk_num, icp->clks);
+	return ret;
+}
+
+static int camss_icp_probe(struct platform_device *pdev)
+{
+	struct dev_pm_domain_attach_data pd_data = { .pd_flags = PD_FLAG_DEV_LINK_ON };
+	const struct camss_icp_resources *icp_res;
+	struct camss_icp *icp;
+	int ret, i;
+
+	icp_res = of_device_get_match_data(&pdev->dev);
+	if (!icp_res)
+		return -EINVAL;
+
+	icp = devm_kzalloc(&pdev->dev, sizeof(*icp), GFP_KERNEL);
+	if (!icp)
+		return -ENOMEM;
+
+	icp->dev = &pdev->dev;
+	icp->icp_res = icp_res;
+	icp->hfi.res = &icp_res->hfi_res;
+	icp->hfi.ops = &hfi_ops;
+	icp->hfi.dev = icp->dev;
+	icp->hfi.priv = icp;
+
+	platform_set_drvdata(pdev, icp);
+
+	icp->csr_base = devm_platform_ioremap_resource_byname(pdev, "csr");
+	if (IS_ERR(icp->csr_base))
+		return PTR_ERR(icp->csr_base);
+
+	icp->cirq_base = devm_platform_ioremap_resource_byname(pdev, "cirq");
+	if (IS_ERR(icp->cirq_base))
+		return PTR_ERR(icp->cirq_base);
+
+	icp->irq = platform_get_irq(pdev, 0);
+	if (icp->irq < 0)
+		return icp->irq;
+
+	/* Reserved memory for HFI */
+	ret = of_reserved_mem_device_init_by_idx(&pdev->dev, pdev->dev.of_node, 1);
+	if (ret && ret != -ENODEV) {
+		dev_err(&pdev->dev, "Failed to init reserved memory: %d\n", ret);
+		return ret;
+	}
+
+	ret = dev_pm_domain_attach_list(&pdev->dev, &pd_data, &icp->pd_list);
+	if (ret < 0 && ret != -EEXIST) {
+		dev_err(&pdev->dev, "Failed to attach power domains: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < icp_res->clk_num; i++) {
+		dev_info(&pdev->dev, "clk=%s\n", icp_res->clk_names[i]);
+		icp->clks[i].id = icp_res->clk_names[i];
+	}
+
+	ret = devm_clk_bulk_get(&pdev->dev, icp_res->clk_num, icp->clks);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to get clocks: %d\n", ret);
+		goto err_pd;
+	}
+
+	for (i = 0; i < icp_res->noc_num; i++) {
+		icp->icc_path[i] = devm_of_icc_get(&pdev->dev, icp_res->noc_names[i]);
+		if (IS_ERR(icp->icc_path[i])) {
+			if (PTR_ERR(icp->icc_path[i]) != -ENODATA) {
+				ret = PTR_ERR(icp->icc_path[i]);
+				goto err_pd;
+			}
+			icp->icc_path[i] = NULL;
+		}
+	};
+
+	ret = devm_request_threaded_irq(&pdev->dev, icp->irq, camss_icp_isr,
+					camss_icp_isr_thread,
+					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+					"camss-icp", icp);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret);
+		goto err_pd;
+	}
+
+	pm_runtime_enable(&pdev->dev);
+
+	ret = icp_boot(icp);
+	if (ret)
+		goto err_pd;
+
+	ret = icp_hfi_core_init(&icp->hfi);
+	if (ret)
+		goto err_pd;
+
+	return 0;
+
+err_pd:
+	if (icp->pd_list)
+		dev_pm_domain_detach_list(icp->pd_list);
+
+	return ret;
+}
+
+static void camss_icp_remove(struct platform_device *pdev)
+{
+	struct camss_icp *icp = platform_get_drvdata(pdev);
+	const struct camss_icp_resources *icp_res;
+	int i;
+
+	icp_res = icp->icp_res;
+	qcom_scm_pas_shutdown(icp_res->pas_id);
+
+	icp_hfi_deinit_queues(&icp->hfi);
+
+	for (i = 0; i < icp_res->noc_num; i++) {
+		if (icp->icc_path[i])
+			icc_set_bw(icp->icc_path[i], 0, 0);
+	}
+	clk_bulk_disable_unprepare(icp_res->clk_num, icp->clks);
+
+	if (icp->pd_list)
+		dev_pm_domain_detach_list(icp->pd_list);
+
+}
+
+static const char * const x1e80100_clk_names [] = {
+	"ahb", "core", "debug_xo",
+	"gcc_hf_axi", "gcc_sf_axi",
+	"cpas_ahb", "core_ahb", "cpas_fast_ahb",
+	"camnoc_axi_rt", "camnoc_axi_nrt",
+	"bps_ahb", "bps_fast_ahb", "bps", "cpas_bps",
+	"ipe_ahb", "ipe_nps_fast_ahb", "ipe_pps_fast_ahb",
+	"ipe_nps", "ipe_pps", "cpas_ipe",
+};
+
+static const char * const x1e80100_noc_names [] = {
+	"ahb",
+	"hf_0",
+	"sf_0",
+	"sf_icp"
+};
+
+struct camss_icp_resources x1e80100_icp_res = {
+	.pas_id = 33,
+	.clk_names = x1e80100_clk_names,
+	.clk_num = ARRAY_SIZE(x1e80100_clk_names),
+	.noc_names = x1e80100_noc_names,
+	.noc_num = ARRAY_SIZE(x1e80100_noc_names),
+	.hfi_res = {
+		.shmem_size = SZ_1M,	 // change to 0x0FC00000 per downstream ?
+		.qdss_size = SZ_1M,
+		.fwuncached_size = 7 * SZ_1M,
+		/*
+		 * Carve sub-regions from FwUncached:
+		 * Over-allocate as CamX does for now.
+		 *   +0x000000: SecHeap (1MB)
+		 *   +0x100000: QTBL (1MB)
+		 *   +0x200000: CMD_Q (1MB)
+		 *   +0x300000: MSG_Q (1MB)
+		 *   +0x400000: DBG_Q (1MB)
+		 *   +0x500000: SFR (1MB alloc 4KB used)
+		 */
+		.secheap_size = SZ_1M,
+		.q_tbl_size = SZ_1M,
+		.qdata_size[HFI_Q_CMD_TYPE] = SZ_1M,
+		.qdata_size[HFI_Q_MSG_TYPE] = SZ_1M,
+		.qdata_size[HFI_Q_DBG_TYPE] = SZ_1M,
+		.sfr_size = SZ_1M,
+	},
+};
+
+static const struct of_device_id camss_icp_of_match[] = {
+	{ .compatible = "qcom,x1e80100-camss-icp", .data = &x1e80100_icp_res},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, camss_icp_of_match);
+
+static struct platform_driver camss_icp_driver = {
+	.probe = camss_icp_probe,
+	.remove = camss_icp_remove,
+	.driver = {
+		.name = "camss-icp",
+		.of_match_table = camss_icp_of_match,
+	},
+};
+
+module_platform_driver(camss_icp_driver);
+
+MODULE_DESCRIPTION("Qualcomm ICP driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/qcom/camss/camss-icp.h b/drivers/media/platform/qcom/camss/camss-icp.h
new file mode 100644
index 0000000000000..68039df4a28b4
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-icp.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ICP provides HFI interface used by IPE and BPS V4L2 m2m drivers.
+ *
+ * Copyright (c) 2026 Bryan O'Donoghue.
+ */
+#ifndef __CAMSS_ICP_H__
+#define __CAMSS_ICP_H__
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/types.h>
+
+#include "camss-icp-hfi.h"
+
+struct icp_device;
+
+/*
+ * ICP Context
+ *
+ * Represents a processing session with firmware.
+ * Created per-stream in IPE/BPS V4L2 driver.
+ */
+struct icp_context {
+	u32 id;
+	u32 dev_type;
+	u32 fw_handle;
+	struct completion done;
+	int result;
+	void *priv;
+	void (*callback)(struct icp_context *ctx, int status);
+};
+
+/*
+ * UBWC Configuration
+ */
+struct icp_ubwc_cfg {
+	u32 fetch_cfg;
+	u32 write_cfg;
+};
+
+/*
+ * Frame Processing Descriptor
+ */
+struct icp_frame_request {
+	dma_addr_t input_iova;
+	u32 input_size;
+	dma_addr_t output_iova;
+	u32 output_size;
+	dma_addr_t cmdbufs_iova;
+	u32 cmdbufs_size;
+	void *priv;
+};
+
+struct icp_hfi *icp_hfi_get(struct device *dev);
+void icp_hfi_put(struct icp_hfi *hfi);
+
+#endif /* __CAMSS_ICP_H__ */

-- 
2.54.0


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

* [PATCH 4/7] media: qcom: camss: qcom-icp: bps: Add initial v4l2 m2m BPS driver
  2026-07-03 15:51 ` [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE Bryan O'Donoghue
                     ` (2 preceding siblings ...)
  2026-07-03 15:51   ` [PATCH 3/7] media: qcom: camss: qcom-icp: Add minimal ICP driver with HFI infrastrucutre Bryan O'Donoghue
@ 2026-07-03 15:51   ` Bryan O'Donoghue
  2026-07-03 15:51   ` [PATCH 5/7] media: qcom: camss: qcom-icp: ipe: Add initial v4l2 m2m IPE driver Bryan O'Donoghue
                     ` (3 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Bryan O'Donoghue @ 2026-07-03 15:51 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-arm-msm, devicetree, linux-kernel, linux-media,
	Bryan O'Donoghue

Signed-off-by: Bryan O'Donoghue <bod@kernel.org>
---
 drivers/media/platform/qcom/camss/camss-bps.c | 684 ++++++++++++++++++++++++++
 drivers/media/platform/qcom/camss/camss-bps.h |  74 +++
 2 files changed, 758 insertions(+)

diff --git a/drivers/media/platform/qcom/camss/camss-bps.c b/drivers/media/platform/qcom/camss/camss-bps.c
new file mode 100644
index 0000000000000..6d447a6353a70
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-bps.c
@@ -0,0 +1,684 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Qualcomm CAMSS BPS (Bayer Processing Segment) V4L2 M2M Driver
+ *
+ * Responsibilities:
+ * - Manage BPS power and clocks (independent of ICP)
+ * - Implement V4L2 mem2mem interface for demosaicing
+ * - Use ICP for HFI communication with firmware
+ *
+ * Copyright (c) 2026 Bryan O'Donoghue.
+ */
+
+#include <linux/clk.h>
+#include <linux/interconnect.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "camss-bps-cmd.h"
+#include "camss-bps-iq.h"
+#include "camss-icp-hfi.h"
+#include "camss-icp-hfi-frame.h"
+#include "camss-icp.h"
+
+#define BPS_NAME		"qcom-camss-bps"
+#define BPS_CLK_MAX		4
+
+struct bps_device {
+	struct device *dev;
+	void __iomem *base;
+
+	/* BPS's own clocks */
+	struct clk_bulk_data clocks[BPS_CLK_MAX];
+	int num_clocks;
+
+	/* BPS's own interconnect */
+	struct icc_path *icc_mem;
+
+	/* BPS's own power state */
+	bool powered;
+
+	/* UBWC config */
+	struct icp_ubwc_cfg ubwc;
+
+	/* ICP reference */
+	struct device *icp_dev;
+	struct icp_hfi *hfi;
+
+	/* V4L2 */
+	struct v4l2_device v4l2_dev;
+	struct video_device vdev;
+	struct v4l2_m2m_dev *m2m_dev;
+	struct mutex lock;
+};
+
+struct bps_ctx {
+	struct v4l2_fh fh;
+	struct bps_device *bps;
+
+	/* Format info */
+	u32 width;
+	u32 height;
+	u32 src_fmt;
+	u32 dst_fmt;
+	u32 bayer_pattern;
+
+	/* Command buffers (DMA memory for HFI) */
+	struct bps_cmd_bufs *cmd_bufs;
+
+	/* IQ configuration */
+	struct bps_iq_config iq;
+
+	/* Frame counter */
+	u32 frame_id;
+
+	/* Firmware returned handle */
+	u32 fw_handle;
+};
+
+/* ============================================================
+ * BPS Power Management (independent of ICP)
+ * ============================================================ */
+
+static int bps_power_on(struct bps_device *bps)
+{
+	int ret;
+
+	if (bps->powered)
+		return 0;
+
+	ret = pm_runtime_resume_and_get(bps->dev);
+	if (ret)
+		return ret;
+
+	ret = icc_set_bw(bps->icc_mem, 0, MBps_to_icc(8000));
+	if (ret)
+		goto err_rpm;
+
+	/* Set clock rate */
+	clk_set_rate(bps->clocks[2].clk, 600000000);  /* core */
+
+	ret = clk_bulk_prepare_enable(bps->num_clocks, bps->clocks);
+	if (ret)
+		goto err_icc;
+
+	bps->powered = true;
+	dev_dbg(bps->dev, "BPS powered on\n");
+	return 0;
+
+err_icc:
+	icc_set_bw(bps->icc_mem, 0, 0);
+err_rpm:
+	pm_runtime_put(bps->dev);
+	return ret;
+}
+
+static void bps_power_off(struct bps_device *bps)
+{
+	if (!bps->powered)
+		return;
+
+	clk_bulk_disable_unprepare(bps->num_clocks, bps->clocks);
+	icc_set_bw(bps->icc_mem, 0, 0);
+	pm_runtime_put(bps->dev);
+
+	bps->powered = false;
+	dev_dbg(bps->dev, "BPS powered off\n");
+}
+
+/* ============================================================
+ * V4L2 Operations
+ * ============================================================ */
+
+static int bps_querycap(struct file *file, void *priv,
+			struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, BPS_NAME, sizeof(cap->driver));
+	strscpy(cap->card, "Qualcomm BPS", sizeof(cap->card));
+
+	return 0;
+}
+
+static int bps_enum_fmt_src(struct file *file, void *priv,
+			    struct v4l2_fmtdesc *f)
+{
+	/* Input: Raw Bayer formats */
+	static const u32 fmts[] = {
+		V4L2_PIX_FMT_SRGGB10,
+		V4L2_PIX_FMT_SGRBG10,
+		V4L2_PIX_FMT_SGBRG10,
+		V4L2_PIX_FMT_SBGGR10,
+	};
+
+	if (f->index >= ARRAY_SIZE(fmts))
+		return -EINVAL;
+
+	f->pixelformat = fmts[f->index];
+	return 0;
+}
+
+static int bps_enum_fmt_dst(struct file *file, void *priv,
+			    struct v4l2_fmtdesc *f)
+{
+	/* Output: NV12 (demosaiced) */
+	if (f->index > 0)
+		return -EINVAL;
+
+	f->pixelformat = V4L2_PIX_FMT_NV12;
+	return 0;
+}
+
+static int bps_g_fmt_src(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct bps_ctx *ctx = container_of(priv, struct bps_ctx, fh);
+
+	f->fmt.pix_mp.width = ctx->width ?: 4096;
+	f->fmt.pix_mp.height = ctx->height ?: 3072;
+	f->fmt.pix_mp.pixelformat = ctx->src_fmt ?: V4L2_PIX_FMT_SRGGB10;
+	f->fmt.pix_mp.num_planes = 1;
+	f->fmt.pix_mp.plane_fmt[0].bytesperline = f->fmt.pix_mp.width * 2;
+	f->fmt.pix_mp.plane_fmt[0].sizeimage =
+		f->fmt.pix_mp.plane_fmt[0].bytesperline * f->fmt.pix_mp.height;
+
+	return 0;
+}
+
+static int bps_g_fmt_dst(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct bps_ctx *ctx = container_of(priv, struct bps_ctx, fh);
+
+	f->fmt.pix_mp.width = ctx->width ?: 4096;
+	f->fmt.pix_mp.height = ctx->height ?: 3072;
+	f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;
+	f->fmt.pix_mp.num_planes = 2;
+	f->fmt.pix_mp.plane_fmt[0].bytesperline = f->fmt.pix_mp.width;
+	f->fmt.pix_mp.plane_fmt[0].sizeimage =
+		f->fmt.pix_mp.width * f->fmt.pix_mp.height;
+	f->fmt.pix_mp.plane_fmt[1].bytesperline = f->fmt.pix_mp.width;
+	f->fmt.pix_mp.plane_fmt[1].sizeimage =
+		f->fmt.pix_mp.width * f->fmt.pix_mp.height / 2;
+
+	return 0;
+}
+
+static int bps_s_fmt_src(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct bps_ctx *ctx = container_of(priv, struct bps_ctx, fh);
+
+	ctx->width = f->fmt.pix_mp.width;
+	ctx->height = f->fmt.pix_mp.height;
+	ctx->src_fmt = f->fmt.pix_mp.pixelformat;
+
+	return bps_g_fmt_src(file, priv, f);
+}
+
+static int bps_s_fmt_dst(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct bps_ctx *ctx = container_of(priv, struct bps_ctx, fh);
+
+	ctx->width = f->fmt.pix_mp.width;
+	ctx->height = f->fmt.pix_mp.height;
+
+	return bps_g_fmt_dst(file, priv, f);
+}
+
+static const struct v4l2_ioctl_ops bps_ioctl_ops = {
+	.vidioc_querycap		= bps_querycap,
+	.vidioc_enum_fmt_vid_out	= bps_enum_fmt_src,
+	.vidioc_enum_fmt_vid_cap	= bps_enum_fmt_dst,
+	.vidioc_g_fmt_vid_out_mplane	= bps_g_fmt_src,
+	.vidioc_g_fmt_vid_cap_mplane	= bps_g_fmt_dst,
+	.vidioc_s_fmt_vid_out_mplane	= bps_s_fmt_src,
+	.vidioc_s_fmt_vid_cap_mplane	= bps_s_fmt_dst,
+	.vidioc_try_fmt_vid_out_mplane	= bps_g_fmt_src,
+	.vidioc_try_fmt_vid_cap_mplane	= bps_g_fmt_dst,
+
+	.vidioc_reqbufs			= v4l2_m2m_ioctl_reqbufs,
+	.vidioc_querybuf		= v4l2_m2m_ioctl_querybuf,
+	.vidioc_qbuf			= v4l2_m2m_ioctl_qbuf,
+	.vidioc_dqbuf			= v4l2_m2m_ioctl_dqbuf,
+	.vidioc_prepare_buf		= v4l2_m2m_ioctl_prepare_buf,
+	.vidioc_create_bufs		= v4l2_m2m_ioctl_create_bufs,
+	.vidioc_expbuf			= v4l2_m2m_ioctl_expbuf,
+
+	.vidioc_streamon		= v4l2_m2m_ioctl_streamon,
+	.vidioc_streamoff		= v4l2_m2m_ioctl_streamoff,
+};
+
+/* ============================================================
+ * V4L2 M2M Callbacks
+ * ============================================================ */
+#if 0
+static void bps_job_abort(void *priv)
+{
+	struct bps_ctx *ctx = priv;
+
+	if (ctx->icp_ctx)
+		icp_ctx_abort(ctx->icp_ctx);
+}
+#endif
+static void bps_frame_done(struct icp_context *icp_ctx, int status)
+{
+	struct bps_ctx *ctx = icp_ctx->priv;
+	struct vb2_v4l2_buffer *src, *dst;
+
+	src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+	dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+
+	if (status) {
+		v4l2_m2m_buf_done(src, VB2_BUF_STATE_ERROR);
+		v4l2_m2m_buf_done(dst, VB2_BUF_STATE_ERROR);
+	} else {
+		v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE);
+		v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE);
+	}
+
+	v4l2_m2m_job_finish(ctx->bps->m2m_dev, ctx->fh.m2m_ctx);
+}
+
+static void bps_device_run(void *priv)
+{
+	struct bps_ctx *ctx = priv;
+	struct vb2_v4l2_buffer *src, *dst;
+	struct icp_frame_request req;
+	dma_addr_t input_iova, output_iova;
+	u32 input_size, output_size;
+	u32 input_stride, output_stride;
+	int ret;
+
+	src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+	dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+
+	input_iova = vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0);
+	input_size = vb2_plane_size(&src->vb2_buf, 0);
+	input_stride = ctx->width * 2;	/* 10-bit packed = ~2 bytes/pixel */
+
+	output_iova = vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0);
+	output_size = vb2_plane_size(&dst->vb2_buf, 0);
+	output_stride = ctx->width;	/* NV12 Y plane stride */
+
+	/* Build frame command with IQ settings */
+	ret = bps_build_frame_cmd(ctx->cmd_bufs,
+				  ctx->frame_id++,
+				  ctx->width, ctx->height,
+				  ctx->bayer_pattern,
+				  input_iova, input_stride, input_size,
+				  output_iova, output_stride, output_size,
+				  &ctx->iq);
+	if (ret) {
+//		bps_frame_done(ctx->icp_ctx, -EIO);
+		return;
+	}
+
+	/* Submit to ICP */
+	req.input_iova = input_iova;
+	req.input_size = input_size;
+	req.output_iova = output_iova;
+	req.output_size = output_size;
+	req.cmdbufs_iova = bps_get_cmd_iova(ctx->cmd_bufs);
+	req.cmdbufs_size = bps_get_cmd_size(ctx->cmd_bufs);
+	req.priv = ctx;
+#if 0
+	if (icp_ctx_submit_frame(ctx->icp_ctx, &req))
+		bps_frame_done(ctx->icp_ctx, -EIO);
+#endif
+}
+
+static const struct v4l2_m2m_ops bps_m2m_ops = {
+	.device_run = bps_device_run,
+#if 0
+	.job_abort = bps_job_abort,
+#endif
+};
+
+/* ============================================================
+ * V4L2 Queue Operations
+ * ============================================================ */
+
+static int bps_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
+			   unsigned int *nplanes, unsigned int sizes[],
+			   struct device *alloc_devs[])
+{
+	struct bps_ctx *ctx = vb2_get_drv_priv(vq);
+
+	if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
+		/* Raw input */
+		if (*nplanes) {
+			if (*nplanes != 1)
+				return -EINVAL;
+			return 0;
+		}
+		*nplanes = 1;
+		sizes[0] = ctx->width * ctx->height * 2;
+	} else {
+		/* NV12 output */
+		if (*nplanes) {
+			if (*nplanes != 2)
+				return -EINVAL;
+			return 0;
+		}
+		*nplanes = 2;
+		sizes[0] = ctx->width * ctx->height;
+		sizes[1] = ctx->width * ctx->height / 2;
+	}
+
+	return 0;
+}
+
+static int bps_buf_prepare(struct vb2_buffer *vb)
+{
+	return 0;
+}
+
+static void bps_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct bps_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+	v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+}
+
+static int bps_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct bps_ctx *ctx = vb2_get_drv_priv(vq);
+	struct bps_device *bps = ctx->bps;
+	int ret;
+
+	/* Allocate command buffers */
+	ctx->cmd_bufs = bps_cmd_bufs_alloc(bps->dev);
+	if (!ctx->cmd_bufs)
+		return -ENOMEM;
+
+	/* Initialize default IQ (passthrough demosaic) */
+	bps_iq_init_passthrough(&ctx->iq);
+	ctx->frame_id = 0;
+
+	return 0;
+}
+
+static void bps_stop_streaming(struct vb2_queue *vq)
+{
+	struct bps_ctx *ctx = vb2_get_drv_priv(vq);
+	struct bps_device *bps = ctx->bps;
+	struct vb2_v4l2_buffer *vbuf;
+
+	/* Return all buffers */
+	while ((vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
+		v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+	while ((vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx)))
+		v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+
+	/* Free command buffers */
+	if (ctx->cmd_bufs) {
+		bps_cmd_bufs_free(bps->dev, ctx->cmd_bufs);
+		ctx->cmd_bufs = NULL;
+	}
+}
+
+static const struct vb2_ops bps_vb2_ops = {
+	.queue_setup = bps_queue_setup,
+	.buf_prepare = bps_buf_prepare,
+	.buf_queue = bps_buf_queue,
+	.start_streaming = bps_start_streaming,
+	.stop_streaming = bps_stop_streaming,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+};
+
+static int bps_queue_init(void *priv, struct vb2_queue *src_vq,
+			  struct vb2_queue *dst_vq)
+{
+	struct bps_ctx *ctx = priv;
+	int ret;
+
+	src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+	src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	src_vq->drv_priv = ctx;
+	src_vq->buf_struct_size = sizeof(struct vb2_v4l2_buffer);
+	src_vq->ops = &bps_vb2_ops;
+	src_vq->mem_ops = &vb2_dma_contig_memops;
+	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	src_vq->lock = &ctx->bps->lock;
+	src_vq->dev = ctx->bps->dev;
+
+	ret = vb2_queue_init(src_vq);
+	if (ret)
+		return ret;
+
+	dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+	dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	dst_vq->drv_priv = ctx;
+	dst_vq->buf_struct_size = sizeof(struct vb2_v4l2_buffer);
+	dst_vq->ops = &bps_vb2_ops;
+	dst_vq->mem_ops = &vb2_dma_contig_memops;
+	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	dst_vq->lock = &ctx->bps->lock;
+	dst_vq->dev = ctx->bps->dev;
+
+	return vb2_queue_init(dst_vq);
+}
+
+/* ============================================================
+ * File Operations
+ * ============================================================ */
+
+static int bps_open(struct file *file)
+{
+	struct bps_device *bps = video_drvdata(file);
+	struct bps_ctx *ctx;
+	int ret;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	ret = bps_power_on(bps);
+	if (ret)
+		return ret;
+
+	ret = icp_hfi_create_handle(bps->hfi, HFI_DEV_TYPE_BPS, (u64)ctx, &ctx->fw_handle, 100);
+	if (ret) {
+		dev_err(bps->dev, "icp_hfi_create_handle fail %d\n", ret);
+		goto err_power;
+	}
+
+	ctx->bps = bps;
+	ctx->width = 4096;
+	ctx->height = 3072;
+	ctx->src_fmt = V4L2_PIX_FMT_SRGGB10;
+	ctx->bayer_pattern = HFI_BAYER_RGGB;  /* Default, updated by s_fmt */
+
+	v4l2_fh_init(&ctx->fh, &bps->vdev);
+	ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(bps->m2m_dev, ctx, bps_queue_init);
+	if (IS_ERR(ctx->fh.m2m_ctx)) {
+		int ret = PTR_ERR(ctx->fh.m2m_ctx);
+		v4l2_fh_exit(&ctx->fh);
+		kfree(ctx);
+		return ret;
+	}
+
+	file->private_data = &ctx->fh;
+	v4l2_fh_add(&ctx->fh, file);
+
+	return 0;
+
+err_power:
+	bps_power_off(bps);
+	return ret;
+}
+
+static int bps_release(struct file *file)
+{
+	struct bps_ctx *ctx = container_of(file->private_data, struct bps_ctx, fh);
+	struct bps_device *bps = video_drvdata(file);
+	int ret;
+
+	ret = icp_hfi_destroy_handle(bps->hfi, HFI_DEV_TYPE_BPS, (u64)ctx, ctx->fw_handle, 100);
+	if (ret) {
+		dev_err(bps->dev, "failed to destory handle %d\n", ret);
+	}
+
+	bps_power_off(bps);
+
+	v4l2_fh_del(&ctx->fh, file);
+	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+	v4l2_fh_exit(&ctx->fh);
+	kfree(ctx);
+
+	return 0;
+}
+
+static const struct v4l2_file_operations bps_fops = {
+	.owner = THIS_MODULE,
+	.open = bps_open,
+	.release = bps_release,
+	.poll = v4l2_m2m_fop_poll,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap = v4l2_m2m_fop_mmap,
+};
+
+/* ============================================================
+ * Platform Driver
+ * ============================================================ */
+
+static int camss_bps_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct bps_device *bps;
+	int ret;
+
+	bps = devm_kzalloc(dev, sizeof(*bps), GFP_KERNEL);
+	if (!bps)
+		return -ENOMEM;
+
+	bps->dev = dev;
+	platform_set_drvdata(pdev, bps);
+	mutex_init(&bps->lock);
+
+	/* Map registers */
+	bps->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(bps->base))
+		return PTR_ERR(bps->base);
+
+	/* Get clocks */
+	bps->clocks[0].id = "ahb";
+	bps->clocks[1].id = "fast_ahb";
+	bps->clocks[2].id = "core";
+	bps->clocks[3].id = "cpas";
+	bps->num_clocks = BPS_CLK_MAX;
+
+	ret = devm_clk_bulk_get(dev, bps->num_clocks, bps->clocks);
+	if (ret)
+		return ret;
+
+	/* Get interconnect */
+	bps->icc_mem = devm_of_icc_get(dev, "mem");
+	if (IS_ERR(bps->icc_mem))
+		return PTR_ERR(bps->icc_mem);
+
+	/* Get UBWC config */
+	of_property_read_u32(dev->of_node, "ubwc-fetch-cfg", &bps->ubwc.fetch_cfg);
+	of_property_read_u32(dev->of_node, "ubwc-write-cfg", &bps->ubwc.write_cfg);
+
+	/* Get ICP reference */
+	struct device_node *icp_np;
+	struct platform_device *icp_pdev;
+
+	icp_np = of_parse_phandle(dev->of_node, "qcom,icp", 0);
+	if (!icp_np) {
+		dev_err(dev, "missing qcom,icp phandle\n");
+		return -ENODEV;
+	}
+
+	icp_pdev = of_find_device_by_node(icp_np);
+	of_node_put(icp_np);
+
+	bps->hfi = icp_hfi_get(&icp_pdev->dev);
+	if (IS_ERR(bps->hfi))
+		return PTR_ERR(bps->hfi);
+
+	if (!icp_pdev)
+		return -EPROBE_DEFER;
+
+	bps->icp_dev = &icp_pdev->dev;
+
+	/* Register V4L2 device */
+	ret = v4l2_device_register(dev, &bps->v4l2_dev);
+	if (ret)
+		goto err_icp;
+
+	/* Create M2M device */
+	bps->m2m_dev = v4l2_m2m_init(&bps_m2m_ops);
+	if (IS_ERR(bps->m2m_dev)) {
+		ret = PTR_ERR(bps->m2m_dev);
+		goto err_v4l2;
+	}
+
+	/* Register video device */
+	bps->vdev.fops = &bps_fops;
+	bps->vdev.ioctl_ops = &bps_ioctl_ops;
+	bps->vdev.release = video_device_release_empty;
+	bps->vdev.v4l2_dev = &bps->v4l2_dev;
+	bps->vdev.vfl_dir = VFL_DIR_M2M;
+	bps->vdev.lock = &bps->lock;
+	bps->vdev.device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
+	strscpy(bps->vdev.name, "qcom-bps", sizeof(bps->vdev.name));
+	video_set_drvdata(&bps->vdev, bps);
+
+	ret = video_register_device(&bps->vdev, VFL_TYPE_VIDEO, -1);
+	if (ret)
+		goto err_m2m;
+
+	pm_runtime_enable(dev);
+
+	dev_info(dev, "BPS registered as /dev/video%d\n", bps->vdev.num);
+
+	return 0;
+
+err_m2m:
+	v4l2_m2m_release(bps->m2m_dev);
+err_v4l2:
+	v4l2_device_unregister(&bps->v4l2_dev);
+err_icp:
+	put_device(bps->icp_dev);
+	return ret;
+}
+
+static void camss_bps_remove(struct platform_device *pdev)
+{
+	struct bps_device *bps = platform_get_drvdata(pdev);
+
+	pm_runtime_disable(bps->dev);
+	video_unregister_device(&bps->vdev);
+	v4l2_m2m_release(bps->m2m_dev);
+	v4l2_device_unregister(&bps->v4l2_dev);
+	put_device(bps->icp_dev);
+}
+
+static const struct of_device_id camss_bps_dt_match[] = {
+	{ .compatible = "qcom,x1e80100-camss-bps" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, camss_bps_dt_match);
+
+static struct platform_driver camss_bps_driver = {
+	.probe = camss_bps_probe,
+	.remove = camss_bps_remove,
+	.driver = {
+		.name = "camss-bps",
+		.of_match_table = camss_bps_dt_match,
+	},
+};
+
+module_platform_driver(camss_bps_driver);
+
+MODULE_DESCRIPTION("Qualcomm CAMSS BPS V4L2 M2M driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/qcom/camss/camss-bps.h b/drivers/media/platform/qcom/camss/camss-bps.h
new file mode 100644
index 0000000000000..dec558b721491
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-bps.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Qualcomm CAMSS BPS (Bayer Processing Segment) Driver
+ *
+ * The BPS performs Bayer demosaicing and initial image processing,
+ * controlled by ICP firmware.
+ *
+ * Processing pipeline:
+ * - Pedestal correction
+ * - Linearisation
+ * - Black level correction
+ * - Bad pixel correction
+ * - Demosaicing (Bayer to RGB/YUV)
+ * - Colour correction
+ * - Gamma correction
+ *
+ * Copyright (c) 2026 Bryan O'Donoghue.
+ */
+
+#ifndef __CAMSS_BPS_H__
+#define __CAMSS_BPS_H__
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/interconnect.h>
+#include <linux/types.h>
+
+/* Forward declaration */
+struct camss_icp;
+
+/*
+ * BPS Clock Indices
+ */
+enum bps_clk_id {
+	BPS_CLK_AHB,
+	BPS_CLK_FAST_AHB,
+	BPS_CLK_CORE,
+	BPS_CLK_CPAS,
+	BPS_CLK_MAX,
+};
+
+/*
+ * BPS Device
+ */
+struct camss_bps {
+	struct device *dev;
+	struct camss_icp *icp;
+
+	/* Register base (informational - ICP firmware programs registers) */
+	void __iomem *base;
+
+	/* Clocks */
+	struct clk *clocks[BPS_CLK_MAX];
+
+	/* Power domain (BPS_GDSC) */
+	struct device *pd;
+	struct device_link *pd_link;
+
+	/* Interconnect */
+	struct icc_path *icc_mem;
+
+	/* State */
+	bool powered;
+	u32 clock_rate;
+};
+
+/*
+ * API Functions
+ */
+int camss_bps_power_on(struct camss_bps *bps);
+void camss_bps_power_off(struct camss_bps *bps);
+int camss_bps_set_clock(struct camss_bps *bps, u32 rate);
+
+#endif /* __CAMSS_BPS_H__ */

-- 
2.54.0


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

* [PATCH 5/7] media: qcom: camss: qcom-icp: ipe: Add initial v4l2 m2m IPE driver
  2026-07-03 15:51 ` [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE Bryan O'Donoghue
                     ` (3 preceding siblings ...)
  2026-07-03 15:51   ` [PATCH 4/7] media: qcom: camss: qcom-icp: bps: Add initial v4l2 m2m BPS driver Bryan O'Donoghue
@ 2026-07-03 15:51   ` Bryan O'Donoghue
  2026-07-03 15:51   ` [PATCH 6/7] media: qcom: camss: Switch on ICP and BPS as make options Bryan O'Donoghue
                     ` (2 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Bryan O'Donoghue @ 2026-07-03 15:51 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-arm-msm, devicetree, linux-kernel, linux-media,
	Bryan O'Donoghue

Signed-off-by: Bryan O'Donoghue <bod@kernel.org>
---
 drivers/media/platform/qcom/camss/camss-ipe.c | 558 ++++++++++++++++++++++++++
 drivers/media/platform/qcom/camss/camss-ipe.h |  69 ++++
 2 files changed, 627 insertions(+)

diff --git a/drivers/media/platform/qcom/camss/camss-ipe.c b/drivers/media/platform/qcom/camss/camss-ipe.c
new file mode 100644
index 0000000000000..c579c2b5904e3
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-ipe.c
@@ -0,0 +1,558 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Qualcomm CAMSS IPE (Image Processing Engine) V4L2 M2M Driver
+ *
+ * Responsibilities:
+ * - Manage IPE power and clocks (independent of ICP)
+ * - Implement V4L2 mem2mem interface
+ * - Use ICP for HFI communication with firmware
+ *
+ * Copyright (c) 2026 Bryan O'Donoghue.
+ */
+
+#include <linux/clk.h>
+#include <linux/interconnect.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "camss-icp.h"
+
+#define IPE_NAME		"qcom-camss-ipe"
+#define IPE_CLK_MAX		6
+
+struct ipe_device {
+	struct device *dev;
+	void __iomem *base;
+
+	/* IPE's own clocks */
+	struct clk_bulk_data clocks[IPE_CLK_MAX];
+	int num_clocks;
+
+	/* IPE's own interconnect */
+	struct icc_path *icc_mem;
+
+	/* IPE's own power state */
+	bool powered;
+
+	/* UBWC config */
+	struct icp_ubwc_cfg ubwc;
+
+	/* ICP reference */
+	struct icp_device *icp;
+
+	/* V4L2 */
+	struct v4l2_device v4l2_dev;
+	struct video_device vdev;
+	struct v4l2_m2m_dev *m2m_dev;
+	struct mutex lock;
+};
+
+struct ipe_ctx {
+	struct v4l2_fh fh;
+	struct ipe_device *ipe;
+	struct icp_context *icp_ctx;
+
+	/* Format info */
+	u32 width;
+	u32 height;
+	u32 src_fmt;
+	u32 dst_fmt;
+};
+
+/* ============================================================
+ * IPE Power Management (independent of ICP)
+ * ============================================================ */
+
+static int ipe_power_on(struct ipe_device *ipe)
+{
+	int ret;
+
+	if (ipe->powered)
+		return 0;
+
+	ret = pm_runtime_resume_and_get(ipe->dev);
+	if (ret)
+		return ret;
+
+	ret = icc_set_bw(ipe->icc_mem, 0, MBps_to_icc(8000));
+	if (ret)
+		goto err_rpm;
+
+	/* Set clock rates */
+	clk_set_rate(ipe->clocks[3].clk, 700000000);  /* nps */
+	clk_set_rate(ipe->clocks[4].clk, 700000000);  /* pps */
+
+	ret = clk_bulk_prepare_enable(ipe->num_clocks, ipe->clocks);
+	if (ret)
+		goto err_icc;
+
+	ipe->powered = true;
+	dev_dbg(ipe->dev, "IPE powered on\n");
+	return 0;
+
+err_icc:
+	icc_set_bw(ipe->icc_mem, 0, 0);
+err_rpm:
+	pm_runtime_put(ipe->dev);
+	return ret;
+}
+
+static void ipe_power_off(struct ipe_device *ipe)
+{
+	if (!ipe->powered)
+		return;
+
+	clk_bulk_disable_unprepare(ipe->num_clocks, ipe->clocks);
+	icc_set_bw(ipe->icc_mem, 0, 0);
+	pm_runtime_put(ipe->dev);
+
+	ipe->powered = false;
+	dev_dbg(ipe->dev, "IPE powered off\n");
+}
+
+/* ============================================================
+ * V4L2 Operations
+ * ============================================================ */
+
+static int ipe_querycap(struct file *file, void *priv,
+			struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, IPE_NAME, sizeof(cap->driver));
+	strscpy(cap->card, "Qualcomm IPE", sizeof(cap->card));
+
+	return 0;
+}
+
+static int ipe_enum_fmt(struct file *file, void *priv,
+			struct v4l2_fmtdesc *f)
+{
+	/* Support NV12 for now */
+	if (f->index > 0)
+		return -EINVAL;
+
+	f->pixelformat = V4L2_PIX_FMT_NV12;
+	return 0;
+}
+
+static int ipe_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct ipe_ctx *ctx = container_of(priv, struct ipe_ctx, fh);
+
+	f->fmt.pix_mp.width = ctx->width ?: 1920;
+	f->fmt.pix_mp.height = ctx->height ?: 1080;
+	f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;
+	f->fmt.pix_mp.num_planes = 2;
+	f->fmt.pix_mp.plane_fmt[0].bytesperline = f->fmt.pix_mp.width;
+	f->fmt.pix_mp.plane_fmt[0].sizeimage = f->fmt.pix_mp.width * f->fmt.pix_mp.height;
+	f->fmt.pix_mp.plane_fmt[1].bytesperline = f->fmt.pix_mp.width;
+	f->fmt.pix_mp.plane_fmt[1].sizeimage = f->fmt.pix_mp.width * f->fmt.pix_mp.height / 2;
+
+	return 0;
+}
+
+static int ipe_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct ipe_ctx *ctx = container_of(priv, struct ipe_ctx, fh);
+
+	ctx->width = f->fmt.pix_mp.width;
+	ctx->height = f->fmt.pix_mp.height;
+
+	return ipe_g_fmt(file, priv, f);
+}
+
+static const struct v4l2_ioctl_ops ipe_ioctl_ops = {
+	.vidioc_querycap		= ipe_querycap,
+	.vidioc_enum_fmt_vid_cap	= ipe_enum_fmt,
+	.vidioc_enum_fmt_vid_out	= ipe_enum_fmt,
+	.vidioc_g_fmt_vid_cap_mplane	= ipe_g_fmt,
+	.vidioc_g_fmt_vid_out_mplane	= ipe_g_fmt,
+	.vidioc_s_fmt_vid_cap_mplane	= ipe_s_fmt,
+	.vidioc_s_fmt_vid_out_mplane	= ipe_s_fmt,
+	.vidioc_try_fmt_vid_cap_mplane	= ipe_g_fmt,
+	.vidioc_try_fmt_vid_out_mplane	= ipe_g_fmt,
+
+	.vidioc_reqbufs			= v4l2_m2m_ioctl_reqbufs,
+	.vidioc_querybuf		= v4l2_m2m_ioctl_querybuf,
+	.vidioc_qbuf			= v4l2_m2m_ioctl_qbuf,
+	.vidioc_dqbuf			= v4l2_m2m_ioctl_dqbuf,
+	.vidioc_prepare_buf		= v4l2_m2m_ioctl_prepare_buf,
+	.vidioc_create_bufs		= v4l2_m2m_ioctl_create_bufs,
+	.vidioc_expbuf			= v4l2_m2m_ioctl_expbuf,
+
+	.vidioc_streamon		= v4l2_m2m_ioctl_streamon,
+	.vidioc_streamoff		= v4l2_m2m_ioctl_streamoff,
+};
+
+/* ============================================================
+ * V4L2 M2M Callbacks
+ * ============================================================ */
+
+static void ipe_job_abort(void *priv)
+{
+	struct ipe_ctx *ctx = priv;
+
+	if (ctx->icp_ctx)
+		icp_ctx_abort(ctx->icp_ctx);
+}
+
+static void ipe_frame_done(struct icp_context *icp_ctx, int status)
+{
+	struct ipe_ctx *ctx = icp_ctx->priv;
+	struct vb2_v4l2_buffer *src, *dst;
+
+	src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+	dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+
+	if (status) {
+		v4l2_m2m_buf_done(src, VB2_BUF_STATE_ERROR);
+		v4l2_m2m_buf_done(dst, VB2_BUF_STATE_ERROR);
+	} else {
+		v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE);
+		v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE);
+	}
+
+	v4l2_m2m_job_finish(ctx->ipe->m2m_dev, ctx->fh.m2m_ctx);
+}
+
+static void ipe_device_run(void *priv)
+{
+	struct ipe_ctx *ctx = priv;
+	struct vb2_v4l2_buffer *src, *dst;
+	struct icp_frame_request req;
+
+	src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+	dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+
+	req.input_iova = vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0);
+	req.input_size = vb2_plane_size(&src->vb2_buf, 0);
+	req.output_iova = vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0);
+	req.output_size = vb2_plane_size(&dst->vb2_buf, 0);
+	req.cmdbufs_iova = 0;  /* Would be set up properly */
+	req.cmdbufs_size = 0;
+	req.priv = ctx;
+
+	if (icp_ctx_submit_frame(ctx->icp_ctx, &req))
+		ipe_frame_done(ctx->icp_ctx, -EIO);
+}
+
+static const struct v4l2_m2m_ops ipe_m2m_ops = {
+	.device_run = ipe_device_run,
+	.job_abort = ipe_job_abort,
+};
+
+/* ============================================================
+ * V4L2 Queue Operations
+ * ============================================================ */
+
+static int ipe_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
+			   unsigned int *nplanes, unsigned int sizes[],
+			   struct device *alloc_devs[])
+{
+	struct ipe_ctx *ctx = vb2_get_drv_priv(vq);
+	unsigned int size = ctx->width * ctx->height * 3 / 2;
+
+	if (*nplanes) {
+		if (*nplanes != 2)
+			return -EINVAL;
+		return 0;
+	}
+
+	*nplanes = 2;
+	sizes[0] = ctx->width * ctx->height;
+	sizes[1] = size - sizes[0];
+
+	return 0;
+}
+
+static int ipe_buf_prepare(struct vb2_buffer *vb)
+{
+	return 0;
+}
+
+static void ipe_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct ipe_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+	v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+}
+
+static int ipe_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct ipe_ctx *ctx = vb2_get_drv_priv(vq);
+	struct ipe_device *ipe = ctx->ipe;
+	int ret;
+
+	/* Power on IPE (IPE manages its own power) */
+	ret = ipe_power_on(ipe);
+	if (ret)
+		return ret;
+
+	/*
+	 * Create ICP context - ICP boots itself automatically
+	 * on first context creation
+	 */
+	ctx->icp_ctx = icp_ctx_create(ipe->icp, HFI_DEV_TYPE_IPE,
+				      ipe_frame_done, ctx);
+	if (IS_ERR(ctx->icp_ctx)) {
+		ret = PTR_ERR(ctx->icp_ctx);
+		ctx->icp_ctx = NULL;
+		ipe_power_off(ipe);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void ipe_stop_streaming(struct vb2_queue *vq)
+{
+	struct ipe_ctx *ctx = vb2_get_drv_priv(vq);
+	struct ipe_device *ipe = ctx->ipe;
+	struct vb2_v4l2_buffer *vbuf;
+
+	/* Return all buffers */
+	while ((vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
+		v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+	while ((vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx)))
+		v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+
+	/*
+	 * Destroy ICP context - ICP shuts itself down automatically
+	 * when last context is destroyed
+	 */
+	if (ctx->icp_ctx) {
+		icp_ctx_destroy(ctx->icp_ctx);
+		ctx->icp_ctx = NULL;
+	}
+
+	ipe_power_off(ipe);
+}
+
+static const struct vb2_ops ipe_vb2_ops = {
+	.queue_setup = ipe_queue_setup,
+	.buf_prepare = ipe_buf_prepare,
+	.buf_queue = ipe_buf_queue,
+	.start_streaming = ipe_start_streaming,
+	.stop_streaming = ipe_stop_streaming,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+};
+
+static int ipe_queue_init(void *priv, struct vb2_queue *src_vq,
+			  struct vb2_queue *dst_vq)
+{
+	struct ipe_ctx *ctx = priv;
+	int ret;
+
+	src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+	src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	src_vq->drv_priv = ctx;
+	src_vq->buf_struct_size = sizeof(struct vb2_v4l2_buffer);
+	src_vq->ops = &ipe_vb2_ops;
+	src_vq->mem_ops = &vb2_dma_contig_memops;
+	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	src_vq->lock = &ctx->ipe->lock;
+	src_vq->dev = ctx->ipe->dev;
+
+	ret = vb2_queue_init(src_vq);
+	if (ret)
+		return ret;
+
+	dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+	dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	dst_vq->drv_priv = ctx;
+	dst_vq->buf_struct_size = sizeof(struct vb2_v4l2_buffer);
+	dst_vq->ops = &ipe_vb2_ops;
+	dst_vq->mem_ops = &vb2_dma_contig_memops;
+	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	dst_vq->lock = &ctx->ipe->lock;
+	dst_vq->dev = ctx->ipe->dev;
+
+	return vb2_queue_init(dst_vq);
+}
+
+/* ============================================================
+ * File Operations
+ * ============================================================ */
+
+static int ipe_open(struct file *file)
+{
+	struct ipe_device *ipe = video_drvdata(file);
+	struct ipe_ctx *ctx;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	ctx->ipe = ipe;
+	ctx->width = 1920;
+	ctx->height = 1080;
+
+	v4l2_fh_init(&ctx->fh, &ipe->vdev);
+	ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(ipe->m2m_dev, ctx, ipe_queue_init);
+	if (IS_ERR(ctx->fh.m2m_ctx)) {
+		int ret = PTR_ERR(ctx->fh.m2m_ctx);
+		v4l2_fh_exit(&ctx->fh);
+		kfree(ctx);
+		return ret;
+	}
+
+	file->private_data = &ctx->fh;
+	v4l2_fh_add(&ctx->fh, file);
+
+	return 0;
+}
+
+static int ipe_release(struct file *file)
+{
+	struct ipe_ctx *ctx = container_of(file->private_data, struct ipe_ctx, fh);
+
+	v4l2_fh_del(&ctx->fh, file);
+	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+	v4l2_fh_exit(&ctx->fh);
+	kfree(ctx);
+
+	return 0;
+}
+
+static const struct v4l2_file_operations ipe_fops = {
+	.owner = THIS_MODULE,
+	.open = ipe_open,
+	.release = ipe_release,
+	.poll = v4l2_m2m_fop_poll,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap = v4l2_m2m_fop_mmap,
+};
+
+/* ============================================================
+ * Platform Driver
+ * ============================================================ */
+
+static int camss_ipe_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ipe_device *ipe;
+	int ret;
+
+	ipe = devm_kzalloc(dev, sizeof(*ipe), GFP_KERNEL);
+	if (!ipe)
+		return -ENOMEM;
+
+	ipe->dev = dev;
+	platform_set_drvdata(pdev, ipe);
+	mutex_init(&ipe->lock);
+
+	/* Map registers */
+	ipe->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(ipe->base))
+		return PTR_ERR(ipe->base);
+
+	/* Get clocks */
+	ipe->clocks[0].id = "ahb";
+	ipe->clocks[1].id = "nps_fast_ahb";
+	ipe->clocks[2].id = "pps_fast_ahb";
+	ipe->clocks[3].id = "nps";
+	ipe->clocks[4].id = "pps";
+	ipe->clocks[5].id = "cpas";
+	ipe->num_clocks = IPE_CLK_MAX;
+
+	ret = devm_clk_bulk_get(dev, ipe->num_clocks, ipe->clocks);
+	if (ret)
+		return ret;
+
+	/* Get interconnect */
+	ipe->icc_mem = devm_of_icc_get(dev, "mem");
+	if (IS_ERR(ipe->icc_mem))
+		return PTR_ERR(ipe->icc_mem);
+
+	/* Get UBWC config */
+	of_property_read_u32(dev->of_node, "ubwc-fetch-cfg", &ipe->ubwc.fetch_cfg);
+	of_property_read_u32(dev->of_node, "ubwc-write-cfg", &ipe->ubwc.write_cfg);
+
+	/* Get ICP reference */
+	ipe->icp = icp_get(dev);
+	if (IS_ERR(ipe->icp))
+		return PTR_ERR(ipe->icp);
+
+	/* Set UBWC config in ICP */
+	icp_set_ubwc_config(ipe->icp, HFI_DEV_TYPE_IPE, &ipe->ubwc);
+
+	/* Register V4L2 device */
+	ret = v4l2_device_register(dev, &ipe->v4l2_dev);
+	if (ret)
+		goto err_icp;
+
+	/* Create M2M device */
+	ipe->m2m_dev = v4l2_m2m_init(&ipe_m2m_ops);
+	if (IS_ERR(ipe->m2m_dev)) {
+		ret = PTR_ERR(ipe->m2m_dev);
+		goto err_v4l2;
+	}
+
+	/* Register video device */
+	ipe->vdev.fops = &ipe_fops;
+	ipe->vdev.ioctl_ops = &ipe_ioctl_ops;
+	ipe->vdev.release = video_device_release_empty;
+	ipe->vdev.v4l2_dev = &ipe->v4l2_dev;
+	ipe->vdev.vfl_dir = VFL_DIR_M2M;
+	ipe->vdev.lock = &ipe->lock;
+	ipe->vdev.device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
+	strscpy(ipe->vdev.name, "qcom-ipe", sizeof(ipe->vdev.name));
+	video_set_drvdata(&ipe->vdev, ipe);
+
+	ret = video_register_device(&ipe->vdev, VFL_TYPE_VIDEO, -1);
+	if (ret)
+		goto err_m2m;
+
+	pm_runtime_enable(dev);
+
+	dev_info(dev, "IPE registered as /dev/video%d\n", ipe->vdev.num);
+
+	return 0;
+
+err_m2m:
+	v4l2_m2m_release(ipe->m2m_dev);
+err_v4l2:
+	v4l2_device_unregister(&ipe->v4l2_dev);
+err_icp:
+	icp_put(ipe->icp);
+	return ret;
+}
+
+static void camss_ipe_remove(struct platform_device *pdev)
+{
+	struct ipe_device *ipe = platform_get_drvdata(pdev);
+
+	pm_runtime_disable(ipe->dev);
+	video_unregister_device(&ipe->vdev);
+	v4l2_m2m_release(ipe->m2m_dev);
+	v4l2_device_unregister(&ipe->v4l2_dev);
+	icp_put(ipe->icp);
+}
+
+static const struct of_device_id camss_ipe_dt_match[] = {
+	{ .compatible = "qcom,x1e80100-camss-ipe" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, camss_ipe_dt_match);
+
+static struct platform_driver camss_ipe_driver = {
+	.probe = camss_ipe_probe,
+	.remove = camss_ipe_remove,
+	.driver = {
+		.name = "camss-ipe",
+		.of_match_table = camss_ipe_dt_match,
+	},
+};
+
+module_platform_driver(camss_ipe_driver);
+
+MODULE_DESCRIPTION("Qualcomm CAMSS IPE V4L2 M2M driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/qcom/camss/camss-ipe.h b/drivers/media/platform/qcom/camss/camss-ipe.h
new file mode 100644
index 0000000000000..3f6d43a269d2d
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-ipe.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Qualcomm CAMSS IPE (Image Processing Engine) Driver
+ *
+ * The IPE performs image post-processing controlled by ICP firmware.
+ * It consists of two sub-blocks:
+ * - NPS (Noise Processing Segment): Noise reduction, sharpening
+ * - PPS (Post Processing Segment): Colour correction, scaling
+ *
+ * Copyright (c) 2026 Bryan O'Donoghue.
+ */
+
+#ifndef __CAMSS_IPE_H__
+#define __CAMSS_IPE_H__
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/interconnect.h>
+#include <linux/types.h>
+
+/* Forward declaration */
+struct camss_icp;
+
+/*
+ * IPE Clock Indices
+ */
+enum ipe_clk_id {
+	IPE_CLK_NPS_AHB,
+	IPE_CLK_NPS_FAST_AHB,
+	IPE_CLK_PPS_FAST_AHB,
+	IPE_CLK_NPS,
+	IPE_CLK_PPS,
+	IPE_CLK_CPAS_IPE_NPS,
+	IPE_CLK_MAX,
+};
+
+/*
+ * IPE Device
+ */
+struct camss_ipe {
+	struct device *dev;
+	struct camss_icp *icp;
+
+	/* Register base (informational - ICP firmware programs registers) */
+	void __iomem *base;
+
+	/* Clocks */
+	struct clk *clocks[IPE_CLK_MAX];
+
+	/* Power domain (IPE_0_GDSC) */
+	struct device *pd;
+	struct device_link *pd_link;
+
+	/* Interconnect */
+	struct icc_path *icc_mem;
+
+	/* State */
+	bool powered;
+	u32 clock_rate;
+};
+
+/*
+ * API Functions
+ */
+int camss_ipe_power_on(struct camss_ipe *ipe);
+void camss_ipe_power_off(struct camss_ipe *ipe);
+int camss_ipe_set_clock(struct camss_ipe *ipe, u32 rate);
+
+#endif /* __CAMSS_IPE_H__ */

-- 
2.54.0


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

* [PATCH 6/7] media: qcom: camss: Switch on ICP and BPS as make options
  2026-07-03 15:51 ` [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE Bryan O'Donoghue
                     ` (4 preceding siblings ...)
  2026-07-03 15:51   ` [PATCH 5/7] media: qcom: camss: qcom-icp: ipe: Add initial v4l2 m2m IPE driver Bryan O'Donoghue
@ 2026-07-03 15:51   ` Bryan O'Donoghue
  2026-07-03 17:09     ` Julian Braha
  2026-07-03 15:51   ` [PATCH 7/7] media: uapi: qcom-camss-stats-params Bryan O'Donoghue
  2026-07-03 16:06   ` [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE Bryan O'Donoghue
  7 siblings, 1 reply; 10+ messages in thread
From: Bryan O'Donoghue @ 2026-07-03 15:51 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-arm-msm, devicetree, linux-kernel, linux-media,
	Bryan O'Donoghue

Signed-off-by: Bryan O'Donoghue <bod@kernel.org>
---
 drivers/media/platform/qcom/camss/Kconfig  | 24 ++++++++++++++++++++++++
 drivers/media/platform/qcom/camss/Makefile |  8 ++++++++
 2 files changed, 32 insertions(+)

diff --git a/drivers/media/platform/qcom/camss/Kconfig b/drivers/media/platform/qcom/camss/Kconfig
index 1edc5e5a1829e..d62d5f6b383f8 100644
--- a/drivers/media/platform/qcom/camss/Kconfig
+++ b/drivers/media/platform/qcom/camss/Kconfig
@@ -8,3 +8,27 @@ config VIDEO_QCOM_CAMSS
 	select VIDEOBUF2_DMA_SG
 	select V4L2_FWNODE
 	select PHY_QCOM_MIPI_CSI2
+
+config VIDEO_QCOM_CAMSS_HARD_ISP
+	tristate "Qualcomm CAMSS Hardware ISP (ICP/IPE/BPS)"
+	depends on VIDEO_QCOM_CAMSS
+	depends on QCOM_MDT_LOADER
+	depends on QCOM_SCM
+	depends on VIDEO_DEV
+	select VIDEOBUF2_DMA_CONTIG
+	select V4L2_MEM2MEM_DEV
+	help
+	  Enable support for the hardware ISP found in Qualcomm camera
+	  subsystems on SoCs like X1E80100. This includes:
+
+	  - ICP (Image Control Processor): Tensilica LX7 that runs
+	    camera firmware and provides HFI interface
+	  - IPE (Image Processing Engine): V4L2 m2m device for noise
+	    reduction, sharpening, and scaling
+	  - BPS (Bayer Processing Segment): V4L2 m2m device for
+	    demosaicing raw Bayer data
+
+	  If unsure, say N.
+
+	  To compile this as modules, choose M here. The modules will
+	  be called camss-icp, camss-ipe, and camss-bps, respecitvely.
diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
index 5e349b4915130..940af23da5675 100644
--- a/drivers/media/platform/qcom/camss/Makefile
+++ b/drivers/media/platform/qcom/camss/Makefile
@@ -29,3 +29,11 @@ qcom-camss-objs += \
 		camss-format.o \
 
 obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
+
+qcom-camss-icp-objs += camss-icp.o \
+		       camss-icp-hfi.o
+obj-$(CONFIG_VIDEO_QCOM_CAMSS_HARD_ISP) += qcom-camss-icp.o
+
+qcom-camss-bps-objs += camss-bps.o \
+		       camss-bps-cmd.o
+obj-$(CONFIG_VIDEO_QCOM_CAMSS_HARD_ISP) += qcom-camss-bps.o

-- 
2.54.0


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

* [PATCH 7/7] media: uapi: qcom-camss-stats-params
  2026-07-03 15:51 ` [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE Bryan O'Donoghue
                     ` (5 preceding siblings ...)
  2026-07-03 15:51   ` [PATCH 6/7] media: qcom: camss: Switch on ICP and BPS as make options Bryan O'Donoghue
@ 2026-07-03 15:51   ` Bryan O'Donoghue
  2026-07-03 16:06   ` [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE Bryan O'Donoghue
  7 siblings, 0 replies; 10+ messages in thread
From: Bryan O'Donoghue @ 2026-07-03 15:51 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Robert Foss, Todor Tomov, Bryan O'Donoghue,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-arm-msm, devicetree, linux-kernel, linux-media,
	Bryan O'Donoghue

This header is an RFC on the topic of making a common stats and params
definition for Qualcomm CAMSS data.

We have three different types of camera hardware that can produce and
consume this data. A simplified view of the consumers and produces is:

Class A:
Inline - This is the IFE block which can do stats, params and colour
	 conversion straight into DRAM in one sweep. This inline engine can
	 operate on CSI2 sensor data directly producing YUV into DRAM in
	 one shot.

Offline firmware - Usually offline means a co-processor firmware called the
	  ICP controls two core blocks the Bayer Processing Segment (BPS)
	  and/or the Image Processing Engine (IPE).
	  A kernel/userspace ABI called Host Firmware Interface (HFI)
	  provides session and resource management as well as implements
	  some of the more advanced features that an inline engine can't
	  directly do - temporal denoise as an example.
	  The offline engines are by their nature memory-to-memory and
	  must either be fed sensor data from an IFE or from another
	  data-source.

Class B:
Offline no firmware - Some IOT systems have a stripped down version of the
		      IFE called the Thin Front End (TFE) which can produce
		      basic Bayer stats but can't do colour conversion.
		      On these systems the only way to produce YUV is to
		      feed raw Bayer data into the OPE via memory-to-memory
		      methods.

Interestingly the HFI provides a representation of params and stats that we
can use again for the inline IFE case, the offline OPE case and the offline
ICP/BPS/IPE case. Since the HFI blocks on a given SoC will re-use the same
silicon IP as the IFE blocks on that silicon it is not only logical but
required to reuse insofar as possible HFI's definition of the data. Anyway
there are some parallel works in progress addressing this topic so I've
taken the time here, with the assistance of an LLM to begin to address the
significant work this will entail.

Sidequest note:
Offline no firmware - One could in theory control the BPS and IPE directly
		      from the kernel but then all of the "secret sauce"
		      the ICP provides - like temporal denoise must be
		      handled manually.

Signed-off-by: Bryan O'Donoghue <bod@kernel.org>
---
 .../linux/media/qcom/camss/camss-stats-params.h    | 2022 ++++++++++++++++++++
 1 file changed, 2022 insertions(+)

diff --git a/include/uapi/linux/media/qcom/camss/camss-stats-params.h b/include/uapi/linux/media/qcom/camss/camss-stats-params.h
new file mode 100644
index 0000000000000..5af1dce88c109
--- /dev/null
+++ b/include/uapi/linux/media/qcom/camss/camss-stats-params.h
@@ -0,0 +1,2022 @@
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
+/*
+ * Qualcomm CAMSS ISP statistics and parameters UAPI.
+ *
+ * Uses the generic V4L2 extensible ISP parameters buffer format
+ * defined in <uapi/linux/media/v4l2-isp.h>.
+ *
+ * This header defines the userspace ABI for the Qualcomm CAMSS
+ * subsystem in two directions:
+ *
+ *   - Parameters (userspace -> kernel): IQ tuning blocks and
+ *     stats-engine configuration, carried as extensible per-block
+ *     entries in a V4L2 params buffer. See
+ *     &enum camss_params_block_type below.
+ *
+ *   - Statistics output (kernel -> userspace): hardware-written
+ *     memory layouts for each stats engine, surfaced via V4L2
+ *     meta-capture nodes. See the "Stats outputs" section.
+ *
+ * CAMSS has two distinct conceptual modes of operation Inline and Offline.
+ *
+ * Inline:
+ *
+ *	Inline processing mode operates on data that has been or is in the
+ *	process of being delivered by a sensor into DRAM.
+ *	This is the fastest and most efficient method of delivering YUV
+ *	data to DRAM. The Inline Front Ends (IFEs) contain Raw Data
+ *	Interfaces (RDIs) and Pixel Paths (PIX). The PIX path is capable of
+ *	generating bayer statistics, applying given parameters, generating
+ *	YUV output, as well as various different types of downscaling.
+ *
+ * Offline:
+ *
+ *	Offline processing engines do not operate on sensor data they are
+ *	memory-to-memory devices. A firmware running on a co-processor
+ *	called the Image Control Processor (ICP) implements a message
+ *	passing system called the Host Firmware Interface (HFI). This HFI
+ *	provides an abstraction over two further blocks the Bayer
+ *	Processing Segment (BPS) and Image Processing Engine (IPE).
+ *	The ICP/BPS/IPE nexus provides a session context which in turn
+ *	allows the ICP firmware to implement things like temporal
+ *	denoising.
+ *
+ *	A second type of offline processing is the Thin Front End (TFE)
+ *	paired with the Offline Processing Engine (OPE). In this case the
+ *	TFE can produce minimal bayer stats with raw Bayer data into
+ *	memory. The OPE operates as a memory-to-memory device without an
+ *	ICP. The OPE can be viewed as a reduced amalgam of the BPS and IPE
+ *	above.
+ *
+ * Design principles:
+ *
+ *   - Fields carry their Q-format and bit-width inline in the kdoc,
+ *     matching what the hardware consumes natively, so userspace
+ *     can produce values at the right precision.
+ *
+ *   - The struct layout is not a register-level layout. Register
+ *     packing, Camera Data Mover (CDM) program composition can be
+ *     specified in any order and is up to the kernel to decide.
+ *
+ * Future work:
+ *
+ *   IPE advanced-processing blocks:
+ *
+ *     - ASF (Adaptive Sharpening Filter) — expected to split by
+ *       sub-function (config / gain LUTs / soft-threshold LUTs /
+ *       skin detection).
+ *     - ANR (Advanced Noise Reduction) — multi-pass pyramid
+ *       denoiser, IPE-only.
+ *     - TF (Temporal Filter) — needs reference-frame management
+ *       across frames; depends on ICP firmware for state.
+ *     - ICA (Image Correction Algorithm) — grid-based geometric
+ *       warp, two instances on IPE (pre and post).
+ *     - CAC (Chromatic Aberration Correction) — IPE-only.
+ *     - HNR / LENR — noise reduction variants. HNR placement
+ *       varies across SoC generations (BPS on older, IPE on
+ *       newer).
+ *     - Skin-colour enhancement, grain adder, HDR10 display block,
+ *       DSX / VSE / upscale chains — all IPE-only.
+ *     - PDPC (PDAF + bad-pixel correction)
+ *     - GIC (green-imbalance correction)
+ *     - LCAC (local CAC)
+ *     - HDR reconstruction variants (v22, v23, v30)
+ *     - Bayer-domain GTM
+ *
+ *   Further work is required to tie-down the formats for IFE/BPS
+ *   components:
+ *
+ *     - STATS_AWB_BFW — Bayer Focus Window stats for AWB
+ *     - STATS_SPARSE_PD — sparse PDAF pixel extraction config
+ *     - STATS_PDAF_V2 — PDAF v2.0 data and SAD
+ *
+ * Copyright (c) 2026 Linaro Ltd.
+ * Author: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
+ */
+
+#ifndef _UAPI_LINUX_CAMSS_STATS_PARAMS_H
+#define _UAPI_LINUX_CAMSS_STATS_PARAMS_H
+
+#include <linux/types.h>
+#include <linux/media/v4l2-isp.h>
+#include <linux/videodev2.h>
+
+/**
+ * enum camss_params_block_type - CAMSS ISP parameter block identifiers
+ *
+ * Each value identifies one ISP processing block. The value is
+ * placed in the @type field of &struct v4l2_isp_params_block_header.
+ *
+ * Reserved fields are added to allow for future additions say in the pre
+ * demosiac phase.
+ */
+enum camss_params_block_type {
+	/* Raw Bayer / pre-demosaic (IFE + BPS) */
+	CAMSS_PARAMS_BLS               = 1,
+	CAMSS_PARAMS_LINEARISATION     = 2,
+	CAMSS_PARAMS_DEMUX             = 3,
+	CAMSS_PARAMS_BINCORRECT        = 4,
+	CAMSS_PARAMS_ABF_FILTER        = 5,
+	CAMSS_PARAMS_ABF_NOISE_LUT     = 6,
+	CAMSS_PARAMS_ABF_ACTIVITY_LUT  = 7,
+	CAMSS_PARAMS_ABF_DARK_LUT      = 8,
+	CAMSS_PARAMS_ABF_RNR           = 9,
+
+	/* Demosaic / post-demosaic RGB (IFE + BPS) */
+	CAMSS_PARAMS_WB_GAIN           = 20,
+	CAMSS_PARAMS_LSC               = 21,
+	CAMSS_PARAMS_DEMOSAIC          = 22,
+
+	/*
+	 * Present on IFE, BPS and IPE.
+	 */
+	CAMSS_PARAMS_COLOR_CORRECT     = 40, /* CCM matrix */
+	CAMSS_PARAMS_COLOR_XFORM       = 41,
+	CAMSS_PARAMS_GLUT              = 42, /* gamma LUT */
+	CAMSS_PARAMS_GTM               = 43,
+	CAMSS_PARAMS_UVG               = 44, /* UV gain */
+
+	/* LTM (Local Tone Mapping).
+	 *
+	 * On newer SoCs there is a Bayer-domain LTM instance on IFE
+	 * and BPS, and a YUV-domain LTM on IPE. The tuning-parameter
+	 * shape is the same in both cases (the algorithm is shared;
+	 * only the pipeline stage and pixel-domain differ).
+	 *
+	 * Split by update cadence so userspace re-uploads only the
+	 * parts that changed.
+	 */
+	CAMSS_PARAMS_LTM_CONFIG        = 60,
+	CAMSS_PARAMS_LTM_CURVES        = 61,
+	CAMSS_PARAMS_LTM_GAMMA         = 62,
+	CAMSS_PARAMS_LTM_MASK_FILTER   = 63,
+
+	/* 64–99 reserved for IPE advanced processing (future work):
+	 * ASF sub-blocks, ANR, TF, ICA, CAC, HNR, LENR, SCE, UPSCALE,
+	 * GRA, HDR10, DSX, VSE, 2DLUT, ...
+	 */
+
+	CAMSS_PARAMS_MAX,
+};
+
+/**
+ * enum camss_stats_block_type - CAMSS ISP statistics block identifiers
+ *
+ * Each value identifies one ISP processing block. The value is
+ * placed in the @type field of &struct v4l2_isp_params_block_header.
+ *
+ */
+enum camss_stats_block_type {
+	/* Stats blocks */
+	CAMSS_STATS_BG          = 1, /* AWB / HDR-BE / Tintless via @usage */
+	CAMSS_STATS_BHIST       = 2, /* AEC / GTM via @usage */
+	CAMSS_STATS_HDR_BHIST   = 3,
+	CAMSS_STATS_IHIST       = 4, /* IFE only */
+	CAMSS_STATS_RS          = 5, /* IFE only — flicker detect */
+	CAMSS_STATS_BAF         = 6, /* IFE only — focus stats */
+	CAMSS_STATS_AWB_BFW     = 7, /* Bayer Focus Window for AWB */
+	CAMSS_STATS_SPARSE_PD   = 8, /* Sparse PDAF pixel extract */
+	CAMSS_STATS_PDAF_V2     = 9, /* PDAF v2.0 data + SAD */
+	CAMSS_STATS_MAX,
+};
+
+/* Maximum number of regions the gridded BG/BE engines can produce
+ * per frame (horizontal_num_max * vertical_num_max).
+ */
+#define CAMSS_STATS_BG_MAX_REGIONS   (64 * 48)
+
+/**
+ * struct camss_stats_bg_region - Per-region BG output, regular mode
+ *
+ * Hardware layout: 4 x 32-bit words per region = 16 bytes.
+ *   Word 0: RSum (bits 29:0), reserved (31:30)
+ *   Word 1: BSum (bits 29:0), reserved (31:30)
+ *   Word 2: GrSum (bits 29:0), reserved (31:30)
+ *   Word 3: GbSum (bits 29:0), reserved (31:30)
+ *   Word 4: RCnt (bits 15:0), BCnt (bits 31:16)
+ *   Word 5: GrCnt (bits 15:0), GbCnt (bits 31:16)
+ *
+ * Note: the layout actually uses 6 32-bit words (24 bytes). Some
+ * variants pack differently; see the *_sat and *_ystats variants.
+ *
+ * Sums accumulate per-channel pixel intensities; counts track how
+ * many pixels contributed to each sum (pixels excluded by channel
+ * gain thresholds are not counted).
+ *
+ * @r_sum:      R channel sum (30u)
+ * @_reserved0: reserved, hardware-zero
+ * @b_sum:      B channel sum (30u)
+ * @_reserved1: reserved, hardware-zero
+ * @gr_sum:     Gr channel sum (30u)
+ * @_reserved2: reserved, hardware-zero
+ * @gb_sum:     Gb channel sum (30u)
+ * @_reserved3: reserved, hardware-zero
+ * @r_count:    count of R pixels contributing to @r_sum (16u)
+ * @b_count:    count of B pixels (16u)
+ * @gr_count:   count of Gr pixels (16u)
+ * @gb_count:   count of Gb pixels (16u)
+ */
+struct camss_stats_bg_region {
+	__u32 r_sum       : 30;
+	__u32 _reserved0  :  2;
+	__u32 b_sum       : 30;
+	__u32 _reserved1  :  2;
+	__u32 gr_sum      : 30;
+	__u32 _reserved2  :  2;
+	__u32 gb_sum      : 30;
+	__u32 _reserved3  :  2;
+	__u32 r_count     : 16;
+	__u32 b_count     : 16;
+	__u32 gr_count    : 16;
+	__u32 gb_count    : 16;
+} __attribute__((packed));
+
+/**
+ * struct camss_stats_bg_region_sat - Per-region BG output, saturation mode
+ *
+ * Extended layout used when the matching params block specifies
+ * @output_mode = CAMSS_STATS_BG_MODE_SATURATION. Carries a second
+ * set of sums and counts, this time for pixels that saturated
+ * (hit the gain threshold). Useful for AWB to avoid bias from
+ * blown highlights.
+ *
+ * @r_sum..@gb_count:       regular-pixel accumulators (as in
+ *                          &struct camss_stats_bg_region)
+ * @sat_r_sum:              saturated R pixels sum (30u)
+ * @_reserved4:             reserved
+ * @sat_b_sum:              saturated B pixels sum (30u)
+ * @_reserved5:             reserved
+ * @sat_gr_sum:             saturated Gr pixels sum (30u)
+ * @_reserved6:             reserved
+ * @sat_gb_sum:             saturated Gb pixels sum (30u)
+ * @_reserved7:             reserved
+ * @sat_r_count:            saturated R pixel count (16u)
+ * @sat_b_count:            saturated B pixel count (16u)
+ * @sat_gr_count:           saturated Gr pixel count (16u)
+ * @sat_gb_count:           saturated Gb pixel count (16u)
+ */
+struct camss_stats_bg_region_sat {
+	__u32 r_sum       : 30;
+	__u32 _reserved0  :  2;
+	__u32 b_sum       : 30;
+	__u32 _reserved1  :  2;
+	__u32 gr_sum      : 30;
+	__u32 _reserved2  :  2;
+	__u32 gb_sum      : 30;
+	__u32 _reserved3  :  2;
+	__u32 r_count     : 16;
+	__u32 b_count     : 16;
+	__u32 gr_count    : 16;
+	__u32 gb_count    : 16;
+	__u32 sat_r_sum   : 30;
+	__u32 _reserved4  :  2;
+	__u32 sat_b_sum   : 30;
+	__u32 _reserved5  :  2;
+	__u32 sat_gr_sum  : 30;
+	__u32 _reserved6  :  2;
+	__u32 sat_gb_sum  : 30;
+	__u32 _reserved7  :  2;
+	__u32 sat_r_count : 16;
+	__u32 sat_b_count : 16;
+	__u32 sat_gr_count: 16;
+	__u32 sat_gb_count: 16;
+} __attribute__((packed));
+
+/**
+ * struct camss_stats_bg_region_ystats - Per-region BG output, Y-stats mode
+ *
+ * Layout used when @output_mode = CAMSS_STATS_BG_MODE_Y_STATS in
+ * the matching params block. Instead of per-channel Gr/Gb
+ * separation, the hardware accumulates a unified G channel and
+ * computes a Y (luma) value using the R/G/B weights from the
+ * params block.
+ *
+ * @r_sum:      R channel sum (30u)
+ * @_reserved0: reserved
+ * @b_sum:      B channel sum (30u)
+ * @_reserved1: reserved
+ * @g_sum:      G channel sum (30u; unified Gr + Gb)
+ * @_reserved2: reserved
+ * @y_sum:      Y (luma) sum (31u)
+ * @_reserved3: reserved
+ * @r_count:    R pixel count (16u)
+ * @b_count:    B pixel count (16u)
+ * @g_count:    G pixel count (16u)
+ * @y_count:    Y pixel count (16u)
+ */
+struct camss_stats_bg_region_ystats {
+	__u32 r_sum       : 30;
+	__u32 _reserved0  :  2;
+	__u32 b_sum       : 30;
+	__u32 _reserved1  :  2;
+	__u32 g_sum       : 30;
+	__u32 _reserved2  :  2;
+	__u32 y_sum       : 31;
+	__u32 _reserved3  :  1;
+	__u32 r_count     : 16;
+	__u32 b_count     : 16;
+	__u32 g_count     : 16;
+	__u32 y_count     : 16;
+} __attribute__((packed));
+
+#define CAMSS_STATS_BHIST_BINS  1024
+
+/**
+ * struct camss_stats_bhist_bin - One histogram bin
+ *
+ * @count:     per-bin pixel count (25u)
+ * @_reserved: reserved, hardware-zero (7u)
+ */
+struct camss_stats_bhist_bin {
+	__u32 count     : 25;
+	__u32 _reserved :  7;
+} __attribute__((packed));
+
+/**
+ * struct camss_stats_bhist_output - BHist stats buffer
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * Single-channel Bayer histogram. The channel is selected by the
+ * matching &struct camss_stats_bhist (@channel field);
+ * see &enum camss_stats_color_channel.
+ *
+ * Bit-width: 25-bit per-bin counts, leaving headroom for the
+ * maximum possible pixel count at full-frame BG region coverage.
+ *
+ * @bins: 1024 per-bin entries, each with a 25-bit count
+ */
+struct camss_stats_bhist_output {
+	struct camss_stats_bhist_bin bins[CAMSS_STATS_BHIST_BINS];
+} __attribute__((packed));
+
+#define CAMSS_STATS_HDR_BHIST_BINS  256
+
+/**
+ * struct camss_stats_hdr_bhist_bins - R/G/B triple for one HDR-BHist bin
+ *
+ * The HDR Bayer histogram accumulates all three colour channels
+ * in parallel, one triple per bin. 12 bytes per bin (3 x 32-bit
+ * words, 25 valid bits each).
+ *
+ * @red_bin:    R channel count (25u)
+ * @_reserved0: reserved (7u)
+ * @green_bin:  G channel count (25u)
+ * @_reserved1: reserved (7u)
+ * @blue_bin:   B channel count (25u)
+ * @_reserved2: reserved (7u)
+ */
+struct camss_stats_hdr_bhist_bins {
+	__u32 red_bin    : 25;
+	__u32 _reserved0 :  7;
+	__u32 green_bin  : 25;
+	__u32 _reserved1 :  7;
+	__u32 blue_bin   : 25;
+	__u32 _reserved2 :  7;
+} __attribute__((packed));
+
+/**
+ * struct camss_stats_hdr_bhist_output - HDR BHist stats buffer
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * Three-channel Bayer histogram for HDR exposure analysis. The
+ * exposure field (long / short / all) is selected by the matching
+ * &struct camss_stats_hdr_bhist (@field_select field).
+ *
+ * @bins: 256 per-bin R/G/B triples
+ */
+struct camss_stats_hdr_bhist_output {
+	struct camss_stats_hdr_bhist_bins bins[CAMSS_STATS_HDR_BHIST_BINS];
+} __attribute__((packed));
+
+#define CAMSS_STATS_IHIST_BINS  256
+
+/**
+ * struct camss_stats_ihist_output - Image histogram stats buffer
+ *
+ * Hardware availability: IFE only.
+ *
+ * Image histogram for post-demosaic luminance / chroma analysis.
+ * The active channel arrays depend on @channel in the matching
+ * &struct camss_stats_ihist:
+ *
+ *   - CAMSS_STATS_IHIST_Y:  @ycc_histogram holds Y
+ *   - CAMSS_STATS_IHIST_CB: @ycc_histogram holds Cb
+ *   - CAMSS_STATS_IHIST_CR: @ycc_histogram holds Cr
+ *
+ * The per-channel R/G/B histograms are always populated; the YCC
+ * slot is multiplexed depending on selection.
+ *
+ * @ycc_histogram:   Y or Cb or Cr histogram (per @channel)
+ * @green_histogram: G histogram
+ * @blue_histogram:  B histogram
+ * @red_histogram:   R histogram
+ */
+struct camss_stats_ihist_output {
+	__u16 ycc_histogram[CAMSS_STATS_IHIST_BINS];
+	__u16 green_histogram[CAMSS_STATS_IHIST_BINS];
+	__u16 blue_histogram[CAMSS_STATS_IHIST_BINS];
+	__u16 red_histogram[CAMSS_STATS_IHIST_BINS];
+} __attribute__((packed));
+
+#define CAMSS_STATS_RS_MAX_REGIONS  (16 * 16)
+
+/**
+ * struct camss_stats_rs_region - One Row-Sum region
+ *
+ * @luma_sum: per-region luma sum (32u; field width is sufficient
+ *            for the maximum full-frame region and full bit depth)
+ */
+struct camss_stats_rs_region {
+	__u32 luma_sum;
+} __attribute__((packed));
+
+/**
+ * struct camss_stats_rs_output - Row Sum stats buffer
+ *
+ * Hardware availability: IFE only.
+ *
+ * Row sums over configurable horizontal regions, used primarily
+ * for 50/60 Hz flicker detection. The region count is set by the
+ * matching &struct camss_stats_rs (@stats_h_num,
+ * @stats_v_num); only (stats_h_num * stats_v_num) entries of
+ * @regions carry valid data.
+ *
+ * Regions are stored in raster order: row 0 left-to-right, then
+ * row 1, and so on.
+ *
+ * @regions: up to 16 x 16 region sums
+ */
+struct camss_stats_rs_output {
+	struct camss_stats_rs_region regions[CAMSS_STATS_RS_MAX_REGIONS];
+} __attribute__((packed));
+
+#define CAMSS_STATS_BAF_MAX_REGIONS  180
+
+/**
+ * struct camss_stats_baf_region - One BAF per-region focus measurement
+ *
+ * Hardware layout: 32 bytes per region.
+ *
+ * Each region carries sums, counts, and sharpness measures from
+ * both the horizontal and vertical filter chains. The sharpness
+ * value is a 40-bit accumulator of squared filter-response
+ * values — larger magnitudes indicate stronger edges in the
+ * region, which is what the focus algorithm maximises.
+ *
+ * The @sel bit selects between two horizontal filter chains
+ * (H1 and H2); the vertical chain is always active in the
+ * vertical fields.
+ *
+ * Region layout in the buffer is not raster-ordered; instead
+ * each region carries explicit @region_id and @output_id fields
+ * identifying which ROI it corresponds to. The last valid entry
+ * sets @end_of_buffer = 1; userspace must stop parsing there.
+ *
+ * @h1_sum:          horizontal filter sum (37u)
+ * @_reserved0:      reserved (2u)
+ * @h1_count:        horizontal pixels contributing (23u)
+ * @_reserved1:      reserved (1u)
+ * @sel:             H-filter selector: 0 = H1, 1 = H2 (1u)
+ * @h1_sharpness:    horizontal sharpness accumulator (40u)
+ * @_reserved2:      reserved (14u)
+ * @region_id:       ROI index from the params block (8u)
+ * @_reserved3:      reserved (2u)
+ * @v_sum:           vertical filter sum (37u)
+ * @_reserved4:      reserved (2u)
+ * @v_count:         vertical pixels contributing (23u)
+ * @_reserved5:      reserved (2u)
+ * @v_sharpness:     vertical sharpness accumulator (40u)
+ * @_reserved6:      reserved (14u)
+ * @output_id:       output-sequence ID from the params block (8u)
+ * @merge:           1 = this region spans a dual-IFE split and must
+ *                   be merged with its peer region
+ * @end_of_buffer:   1 = last valid entry in the buffer (1u)
+ */
+struct camss_stats_baf_region {
+	__u64 h1_sum       : 37;
+	__u64 _reserved0   :  2;
+	__u64 h1_count     : 23;
+	__u64 _reserved1   :  1;
+	__u64 sel          :  1;
+
+	__u64 h1_sharpness : 40;
+	__u64 _reserved2   : 14;
+	__u64 region_id    :  8;
+	__u64 _reserved3   :  2;
+
+	__u64 v_sum        : 37;
+	__u64 _reserved4   :  2;
+	__u64 v_count      : 23;
+	__u64 _reserved5   :  2;
+
+	__u64 v_sharpness  : 40;
+	__u64 _reserved6   : 14;
+	__u64 output_id    :  8;
+	__u64 merge        :  1;
+	__u64 end_of_buffer:  1;
+} __attribute__((packed));
+
+/**
+ * struct camss_stats_baf_output - BAF (focus) stats buffer
+ *
+ * Hardware availability: IFE only.
+ *
+ * Focus statistics per ROI. Up to 180 ROIs can be configured via
+ * the matching &struct camss_stats_baf. The output buffer
+ * is sized for the worst case; the hardware terminates the valid
+ * region list by setting @end_of_buffer = 1 on the last entry.
+ *
+ * Userspace scans @regions in order, parsing each entry until
+ * @end_of_buffer is found. Values past that entry are undefined.
+ *
+ * @regions: up to 180 ROI measurements
+ */
+struct camss_stats_baf_output {
+	struct camss_stats_baf_region regions[CAMSS_STATS_BAF_MAX_REGIONS];
+} __attribute__((packed));
+
+/* ===================================================================
+ * Raw Bayer / pre-demosaic blocks
+ *
+ * Hardware availability: IFE and BPS.
+ *
+ * These blocks are instantiated as the same silicon IP in both
+ * engines. The UAPI struct describes the tuning; the kernel
+ * translator selected by the V4L2 device node programs the
+ * corresponding engine.
+ * =================================================================== */
+
+/**
+ * struct camss_params_bls - Black level subtraction
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * BLS subtracts a constant offset from each Bayer channel and then
+ * scales to restore the dynamic range. Per-channel thresholds gate
+ * the subtraction to avoid clipping dark noise.
+ *
+ * The scale field compensates for the offset removal:
+ *     scale = (1 << PIPELINE_BITWIDTH) /
+ *             ((1 << PIPELINE_BITWIDTH) - offset)
+ *
+ * @header:       block header; @header.type = CAMSS_PARAMS_BLS
+ * @offset:       subtractive offset, identical across 4 channels (14u)
+ * @scale:        gain scaling after subtraction (17u Q11)
+ * @threshold_r:  threshold for R  channel
+ * @threshold_gr: threshold for Gr channel
+ * @threshold_gb: threshold for Gb channel
+ * @threshold_b:  threshold for B  channel
+ * @_pad:         reserved, must be zero
+ *
+ */
+struct camss_params_bls {
+	struct v4l2_isp_params_block_header header;
+	__u16 offset;
+	__u32 scale;
+	__u16 threshold_r;
+	__u16 threshold_gr;
+	__u16 threshold_gb;
+	__u16 threshold_b;
+	__u16 _pad[1];
+} __attribute__((aligned(8)));
+
+#define CAMSS_LINEARISATION_KNEE_POINTS  8
+#define CAMSS_LINEARISATION_SEGMENTS     9
+
+/**
+ * struct camss_params_linearisation_channel - one Bayer channel's curve
+ *
+ * @knee_points: x-axis breakpoints of the piecewise-linear curve
+ * @base:        y-value at each segment start
+ * @delta:       slope of each segment (output per input unit)
+ */
+struct camss_params_linearisation_channel {
+	__u16 knee_points[CAMSS_LINEARISATION_KNEE_POINTS];
+	__u16 base[CAMSS_LINEARISATION_SEGMENTS];
+	__u32 delta[CAMSS_LINEARISATION_SEGMENTS];
+} __attribute__((packed));
+
+/**
+ * struct camss_params_linearisation - Sensor response linearisation
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * Four independent piecewise-linear curves (R/Gr/Gb/B), each with
+ * 8 knee points and 9 segments. Used to undo sensor companding or
+ * non-linear response before downstream blocks (which assume
+ * linear-light data).
+ *
+ * On newer SoCs this block is named CLC_COMPDECOMP (compand /
+ * de-compand) in the hardware; the operation and register layout
+ * are equivalent.
+ *
+ * The hardware uses double-buffered LUTs internally; bank
+ * selection is kernel-managed and not exposed.
+ *
+ * @header: block header; @header.type = CAMSS_PARAMS_LINEARISATION
+ * @r:      R  channel curve
+ * @gr:     Gr channel curve
+ * @gb:     Gb channel curve
+ * @b:      B  channel curve
+ */
+struct camss_params_linearisation {
+	struct v4l2_isp_params_block_header header;
+	struct camss_params_linearisation_channel r;
+	struct camss_params_linearisation_channel gr;
+	struct camss_params_linearisation_channel gb;
+	struct camss_params_linearisation_channel b;
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_demux - Channel demux and per-channel gain
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * Demultiplexes the Bayer stream into four logical channels,
+ * applying a per-channel gain and a per-channel black-level offset.
+ * Used to compensate for per-channel sensor-gain imbalance early
+ * in the pipeline.
+ *
+ * Gain range approximately 0.0 .. 4.0 in 16uQ10; offsets are
+ * signed and channel-specific.
+ *
+ * @header:    block header; @header.type = CAMSS_PARAMS_DEMUX
+ * @gain_even: gain for even-column channel (16uQ10)
+ * @gain_odd:  gain for odd-column channel (16uQ10)
+ * @gain_gr:   Gr channel gain (16uQ10)
+ * @gain_gb:   Gb channel gain (16uQ10)
+ * @gain_r:    R  channel gain (16uQ10)
+ * @gain_b:    B  channel gain (16uQ10)
+ * @period:    blanking period for odd/even alternation
+ * @blk_in:    input black level
+ * @blk_out:   output black level after demux
+ * @_pad:      reserved, must be zero
+ */
+struct camss_params_demux {
+	struct v4l2_isp_params_block_header header;
+	__u16 gain_even;
+	__u16 gain_odd;
+	__u16 gain_gr;
+	__u16 gain_gb;
+	__u16 gain_r;
+	__u16 gain_b;
+	__u16 period;
+	__u16 blk_in;
+	__u16 blk_out;
+	__u16 _pad[3];
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_bincorrect - Binning correction
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * Corrects pixel positions when the sensor has been configured for
+ * binning (combining adjacent photosites to trade resolution for
+ * sensitivity). Sensors in 2x2 binning shift the effective pixel
+ * centres by half a sample; bincorrect re-aligns the Bayer grid to
+ * integer positions so downstream demosaic sees correctly
+ * positioned samples.
+ *
+ * @header:  block header; @header.type = CAMSS_PARAMS_BINCORRECT
+ * @ver_w1:  vertical width 1
+ * @ver_w2:  vertical width 2
+ * @hor_w1:  horizontal width 1
+ * @hor_w2:  horizontal width 2
+ */
+struct camss_params_bincorrect {
+	struct v4l2_isp_params_block_header header;
+	__u16 ver_w1;
+	__u16 ver_w2;
+	__u16 hor_w1;
+	__u16 hor_w2;
+} __attribute__((aligned(8)));
+
+/*
+ * ABF (Adaptive Bilateral Filter) — split into five sub-blocks
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * ABF is the primary Bayer-stage noise reduction block. It
+ * combines a bilateral filter, min/max processing, radial noise
+ * reduction (RNR), activity-based modulation, dark-region
+ * adjustment, and edge handling. The full configuration is
+ * substantial (~80 scalar fields plus three DMI LUTs and an RNR
+ * curve) and different parts update at different rates.
+ *
+ * Sub-block decomposition by update lifecycle:
+ *
+ *   FILTER       — scalar control, bilateral filter coefficients,
+ *                  block-matching levels, per-channel filter
+ *                  strength, min/max filter, edge handling. Can
+ *                  change per frame for adaptive behaviour or with
+ *                  scene/illuminant changes.
+ *   NOISE_LUT    — 64-entry shot-noise LUT. Updates when analog
+ *                  gain changes meaningfully (typically banded:
+ *                  one LUT per analog-gain band, swapped on band
+ *                  crossings).
+ *   ACTIVITY_LUT — 32-entry activity-modulation LUT. Mostly static;
+ *                  may update on tone-preset switch.
+ *   DARK_LUT     — 42-entry dark-region adjustment LUT. Mostly
+ *                  static.
+ *   RNR          — radial NR curve (4 anchor points). Sensor-
+ *                  geometry-derived; effectively static once
+ *                  calibrated.
+ *
+ * Splitting lets userspace re-upload only the pieces that
+ * actually changed; the noise LUT is the most frequent updater
+ * (per-gain-band) while the others typically load once per
+ * session.
+ *
+ */
+
+#define CAMSS_ABF_NUM_CHANNELS         4
+#define CAMSS_ABF_NUM_ANCHORS          4
+#define CAMSS_ABF_NUM_BLOCK_PIX_LEVELS 2
+#define CAMSS_ABF_NOISE_LUT_SIZE       64
+#define CAMSS_ABF_ACTIVITY_LUT_SIZE    32
+#define CAMSS_ABF_DARK_LUT_SIZE        42
+
+/**
+ * struct camss_params_abf_filter - ABF scalar filter configuration
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * Scalar filter fields of the ABF block. Excludes the three DMI
+ * LUTs (noise, activity, dark) and the RNR curve, which are
+ * separate sub-blocks.
+ *
+ * @header:               block header; @header.type = CAMSS_PARAMS_ABF_FILTER
+ *
+ * Sub-function enables:
+ * @bilateral_enable:     enable bilateral filter (FILTER_EN in HW)
+ * @cross_process_enable: enable cross-channel processing
+ * @direct_smooth_enable: enable directional smoothing
+ * @dark_desat_enable:    enable dark-region desaturation
+ * @dark_smooth_enable:   enable dark-region smoothing
+ * @act_adj_enable:       enable activity adjustment
+ * @minmax_enable:        enable built-in min/max pixel filter
+ * @block_opt:            block matching pattern selector, 0..2
+ *
+ * Block matching:
+ * @block_pix_level:      pixel-match levels [0]=R/B, [1]=G
+ * @distance_level:       distance levels [3][6] — 3 levels x
+ *                        6 channels; [][0..2] for R/B, [][3..5]
+ *                        for Gr/Gb
+ *
+ * Per-channel filter strength (channel order R, Gr, Gb, B):
+ * @curve_offset:         curve offset (7u per channel)
+ * @edge_softness:        edge softness (9u per channel)
+ * @filter_strength:      filter strength (9u per channel)
+ *
+ * Min/max filter:
+ * @minmax_max_shift:     MINMAX_MAXSHFT
+ * @minmax_min_shift:     MINMAX_MINSHFT
+ * @minmax_offset:        (bs-2)u — bs is pipeline bit-depth
+ * @minmax_bls:           (bs-2)u
+ *
+ * Activity normalisation:
+ * @activity_factor0:     normalisation factor 0
+ * @activity_factor1:     normalisation factor 1
+ * @activity_threshold0:  normalisation threshold 0
+ * @activity_threshold1:  normalisation threshold 1
+ * @activity_smooth_threshold0: smoothing-kernel threshold 0
+ * @activity_smooth_threshold1: smoothing-kernel threshold 1
+ *
+ * Dark-region:
+ * @dark_threshold:       dark normalisation threshold
+ *
+ * Cross-channel ratios (12u each, AWB-derived):
+ * @gr_ratio:             Green-Red ratio
+ * @rg_ratio:             Red-Green ratio
+ * @gb_ratio:             Green-Blue ratio
+ * @bg_ratio:             Blue-Green ratio
+ * @rb_ratio:             Red-Blue ratio
+ * @br_ratio:             Blue-Red ratio
+ *
+ * Edge handling:
+ * @edge_detect_threshold:    edge detection threshold (4uQ2)
+ * @edge_count_low:           edge-count low parameter
+ * @edge_detect_noise_scalar: edge-detect noise scalar (12uQ6)
+ * @edge_smooth_strength:     edge-smoothing strength (Q6)
+ * @edge_smooth_noise_scalar: per-channel edge-smooth noise scalar
+ *                            (12uQ8, channel order R, Gr, Gb, B)
+ */
+struct camss_params_abf_filter {
+	struct v4l2_isp_params_block_header header;
+
+	__u8  bilateral_enable;
+	__u8  cross_process_enable;
+	__u8  direct_smooth_enable;
+	__u8  dark_desat_enable;
+	__u8  dark_smooth_enable;
+	__u8  act_adj_enable;
+	__u8  minmax_enable;
+	__u8  block_opt;
+
+	__u16 block_pix_level[CAMSS_ABF_NUM_BLOCK_PIX_LEVELS];
+	__u16 distance_level[3][6];
+
+	__u16 curve_offset[CAMSS_ABF_NUM_CHANNELS];
+	__u16 edge_softness[CAMSS_ABF_NUM_CHANNELS];
+	__u16 filter_strength[CAMSS_ABF_NUM_CHANNELS];
+
+	__u16 minmax_max_shift;
+	__u16 minmax_min_shift;
+	__u16 minmax_offset;
+	__u16 minmax_bls;
+
+	__u16 activity_factor0;
+	__u16 activity_factor1;
+	__u16 activity_threshold0;
+	__u16 activity_threshold1;
+	__u16 activity_smooth_threshold0;
+	__u16 activity_smooth_threshold1;
+
+	__u16 dark_threshold;
+
+	__u16 gr_ratio;
+	__u16 rg_ratio;
+	__u16 gb_ratio;
+	__u16 bg_ratio;
+	__u16 rb_ratio;
+	__u16 br_ratio;
+
+	__u16 edge_detect_threshold;
+	__u16 edge_count_low;
+	__u16 edge_detect_noise_scalar;
+	__u16 edge_smooth_strength;
+	__u16 edge_smooth_noise_scalar[CAMSS_ABF_NUM_CHANNELS];
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_abf_noise_lut - ABF shot-noise LUT
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * 64-entry 32-bit LUT representing per-luma shot-noise standard
+ * deviation. Updates when analog gain changes meaningfully —
+ * typically banded so a small number of LUTs are pre-computed and
+ * one is uploaded on each gain-band crossing.
+ *
+ * @header: block header; @header.type = CAMSS_PARAMS_ABF_NOISE_LUT
+ * @lut:    shot-noise LUT
+ */
+struct camss_params_abf_noise_lut {
+	struct v4l2_isp_params_block_header header;
+	__u32 lut[CAMSS_ABF_NOISE_LUT_SIZE];
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_abf_activity_lut - ABF activity-adjust LUT
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * 32-entry adjustment for activity-based filter modulation.
+ *
+ * @header: block header; @header.type = CAMSS_PARAMS_ABF_ACTIVITY_LUT
+ * @lut:    activity-adjustment LUT
+ */
+struct camss_params_abf_activity_lut {
+	struct v4l2_isp_params_block_header header;
+	__u16 lut[CAMSS_ABF_ACTIVITY_LUT_SIZE];
+	__u16 _pad[2];
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_abf_dark_lut - ABF dark-factor LUT
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * 42-entry LUT for dark-region adjustment.
+ *
+ * @header: block header; @header.type = CAMSS_PARAMS_ABF_DARK_LUT
+ * @lut:    dark-factor LUT
+ */
+struct camss_params_abf_dark_lut {
+	struct v4l2_isp_params_block_header header;
+	__u16 lut[CAMSS_ABF_DARK_LUT_SIZE];
+	__u16 _pad[2];
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_abf_rnr - ABF radial noise reduction curve
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * Defines a piecewise curve in r-squared (radius squared) space
+ * with 4 anchor points; outside the lens optical centre the noise
+ * reduction strength is modulated based on radial distance.
+ *
+ * The geometry fields (@bx, @by, @r_square_init) are sensor-derived
+ * and could move to a kernel-only domain if a sensor-driver hook
+ * supplied them; for now exposed in UAPI for direct control.
+ *
+ * @header:           block header; @header.type = CAMSS_PARAMS_ABF_RNR
+ *
+ * Curve geometry:
+ * @bx:               14s — init_h_offset - h_centre
+ * @by:               14s — init_v_offset - v_centre
+ * @r_square_scale:   7u  — r^2 scale factor
+ * @r_square_shift:   4u  — r^2 right-shift
+ * @r_square_init:    28u — initial r^2 for top-left of stripe
+ *
+ * Per-anchor RNR curve (4 anchors):
+ * @rnr_anchor:       12u anchor x-positions
+ * @rnr_base0:        8u, RNR_NOISE_BASE
+ * @rnr_slope0:       12u, RNR_NOISE_SLOPE
+ * @rnr_shift0:       4u, RNR_NOISE_SHIFT
+ * @rnr_base1:        8s, RNR_THRESH_BASE
+ * @rnr_slope1:       10s, RNR_THRESH_SLOPE
+ * @rnr_shift1:       4s, RNR_THRESH_SHIFT
+ *
+ * Noise-preservation curve (2 tables x 4 anchors):
+ * @nprsv_anchor:     12u, NP_ANCHOR
+ * @nprsv_base:       8u, NP_BASE [0]=R/B, [1]=Gr/Gb
+ * @nprsv_shift:      4u, NP_SHIFT [0]=R/B, [1]=Gr/Gb
+ * @nprsv_slope:      9s, NP_SLOPE
+ */
+struct camss_params_abf_rnr {
+	struct v4l2_isp_params_block_header header;
+
+	__s16 bx;
+	__s16 by;
+	__u16 r_square_scale;
+	__u16 r_square_shift;
+	__u32 r_square_init;
+
+	__u16 rnr_anchor[CAMSS_ABF_NUM_ANCHORS];
+	__u16 rnr_base0[CAMSS_ABF_NUM_ANCHORS];
+	__u16 rnr_slope0[CAMSS_ABF_NUM_ANCHORS];
+	__u16 rnr_shift0[CAMSS_ABF_NUM_ANCHORS];
+	__s16 rnr_base1[CAMSS_ABF_NUM_ANCHORS];
+	__s16 rnr_slope1[CAMSS_ABF_NUM_ANCHORS];
+	__s16 rnr_shift1[CAMSS_ABF_NUM_ANCHORS];
+
+	__u16 nprsv_anchor[CAMSS_ABF_NUM_ANCHORS];
+	__u16 nprsv_base[2][CAMSS_ABF_NUM_ANCHORS];
+	__u16 nprsv_shift[2][CAMSS_ABF_NUM_ANCHORS];
+	__s16 nprsv_slope[2][CAMSS_ABF_NUM_ANCHORS];
+} __attribute__((aligned(8)));
+
+/* ===================================================================
+ * Demosaic / post-demosaic RGB blocks
+ *
+ * Hardware availability: IFE and BPS.
+ * =================================================================== */
+
+/**
+ * struct camss_params_wb_gain - White balance gains and offsets
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * Channel ordering follows the hardware output-channel convention:
+ * channel 0 = G, 1 = B, 2 = R.
+ *
+ * Gains are 12uQ10 (12-bit unsigned, 10 fractional bits, range
+ * approximately 0.0 .. 4.0). Offsets are signed and clamped to 13
+ * or 15 bits depending on hardware variant.
+ *
+ * @header:    block header; @header.type = CAMSS_PARAMS_WB_GAIN
+ * @g_gain:    green channel gain  (12uQ10)
+ * @b_gain:    blue  channel gain  (12uQ10)
+ * @r_gain:    red   channel gain  (12uQ10)
+ * @g_offset:  green channel offset
+ * @b_offset:  blue  channel offset
+ * @r_offset:  red   channel offset
+ * @_pad:      reserved, must be zero
+ */
+struct camss_params_wb_gain {
+	struct v4l2_isp_params_block_header header;
+	__u16 g_gain;
+	__u16 b_gain;
+	__u16 r_gain;
+	__u16 g_offset;
+	__u16 b_offset;
+	__u16 r_offset;
+	__u16 _pad[2];
+} __attribute__((aligned(8)));
+
+#define CAMSS_LSC_MESH_H  17
+#define CAMSS_LSC_MESH_V  13
+
+/**
+ * struct camss_params_lsc - Lens shading correction (rolloff)
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * A 13x17 mesh of per-channel gain values (13uQ10, range
+ * ~0.0 .. 8.0) for each of R/Gr/Gb/B. The hardware bicubically
+ * interpolates between mesh points. Grid geometry (subgrid width
+ * and height, start offsets, x/y delta reciprocals) is computed
+ * by the kernel from the sensor output dimensions; userspace
+ * supplies only the 4-channel mesh.
+ *
+ * The mesh values encode the full CCT-interpolated table for the
+ * current illuminant. Userspace computes the interpolation between
+ * calibrated illuminant tables (A, D50, D65, etc.) and sends the
+ * result. The kernel does not maintain per-illuminant calibration
+ * tables.
+ *
+ * @header:  block header; @header.type = CAMSS_PARAMS_LSC
+ * @mesh_r:  R  channel mesh (13uQ10)
+ * @mesh_gr: Gr channel mesh
+ * @mesh_gb: Gb channel mesh
+ * @mesh_b:  B  channel mesh
+ */
+struct camss_params_lsc {
+	struct v4l2_isp_params_block_header header;
+	__u16 mesh_r[CAMSS_LSC_MESH_V][CAMSS_LSC_MESH_H];
+	__u16 mesh_gr[CAMSS_LSC_MESH_V][CAMSS_LSC_MESH_H];
+	__u16 mesh_gb[CAMSS_LSC_MESH_V][CAMSS_LSC_MESH_H];
+	__u16 mesh_b[CAMSS_LSC_MESH_V][CAMSS_LSC_MESH_H];
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_demosaic - Bayer demosaicing tuning
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * The demosaic algorithm itself is fixed in hardware; these
+ * parameters tune the directional interpolation heuristics that
+ * decide how to reconstruct missing colour samples at each pixel.
+ *
+ * @header:           block header; @header.type = CAMSS_PARAMS_DEMOSAIC
+ * @cosited_rgb:      1 = cosited RGB output sampling
+ * @demosaic_v4:      1 = use v4 interpolation filters, 0 = v3
+ * @disable_dir_g:    disable directional G interpolation
+ * @disable_dir_rb:   disable directional R/B interpolation
+ * @dynamic_clamp_g:  enable G dynamic clamping
+ * @dynamic_clamp_rb: enable R/B dynamic clamping
+ * @_pad:             reserved, must be zero
+ * @wk:               directional interpolation weight factor (signed)
+ * @ak:               directional interpolation activity factor
+ * @lambda_g:         lambda for G channel
+ * @lambda_rb:        lambda for R/B channels
+ */
+struct camss_params_demosaic {
+	struct v4l2_isp_params_block_header header;
+	__u8  cosited_rgb;
+	__u8  demosaic_v4;
+	__u8  disable_dir_g;
+	__u8  disable_dir_rb;
+	__u8  dynamic_clamp_g;
+	__u8  dynamic_clamp_rb;
+	__u16 _pad;
+	__s16 wk;
+	__u16 ak;
+	__u16 lambda_g;
+	__u16 lambda_rb;
+} __attribute__((aligned(8)));
+
+/* ===================================================================
+ * Tier 1 blocks — also present on IPE in future work
+ *
+ * These blocks are instantiated on IFE, BPS, and IPE. The UAPI
+ * struct applies regardless of the engine.
+ * =================================================================== */
+
+/**
+ * struct camss_params_color_correct - 3x3 CCM with per-channel offsets
+ *
+ * Hardware availability: IFE, BPS. (IPE: future work.)
+ *
+ * Matrix computation, output channel order G, B, R:
+ *   Out_ch0 (G) = a[0]*G + b[0]*B + c[0]*R + k[0]
+ *   Out_ch1 (B) = a[1]*G + b[1]*B + c[1]*R + k[1]
+ *   Out_ch2 (R) = a[2]*G + b[2]*B + c[2]*R + k[2]
+ *
+ * Input channel order is G, B, R (not R, G, B) — matches the
+ * hardware register layout.
+ *
+ * All 9 coefficients share a single Q-factor selector @qfactor,
+ * which applies a right-shift to the multiplier output.
+ *
+ * @header:  block header; @header.type = CAMSS_PARAMS_COLOR_CORRECT
+ * @a:       G-input coefficients per output channel (12s;
+ *           a[0]=Out_G, a[1]=Out_B, a[2]=Out_R)
+ * @b:       B-input coefficients (12s)
+ * @c:       R-input coefficients (12s)
+ * @k:       per-output-channel offsets (typically 9s effective)
+ * @qfactor: Q-format selector (2u):
+ *               0 = Q7, 1 = Q8, 2 = Q9, 3 = Q10
+ *           Applies uniformly to all 9 matrix coefficients.
+ * @_pad:    reserved, must be zero
+ */
+struct camss_params_color_correct {
+	struct v4l2_isp_params_block_header header;
+	__u16 a[3];
+	__u16 b[3];
+	__u16 c[3];
+	__u16 k[3];
+	__u16 qfactor;
+	__u16 _pad[3];
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_color_xform - Colour-space transform
+ *
+ * Hardware availability: IFE, BPS. (IPE: future work — IPE carries
+ * three instances of this block for pre/mid/post stages.)
+ *
+ * Applies the transform:
+ *     output = M * clamp(input - C) + O
+ * where C is a per-channel input clamp point, M is a 3x3 matrix,
+ * and O is a per-channel output offset. S0..S2 are output
+ * saturation clamps.
+ *
+ * @header: block header; @header.type = CAMSS_PARAMS_COLOR_XFORM
+ * @c0:     input channel-0 clamp
+ * @c1:     input channel-1 clamp
+ * @c2:     input channel-2 clamp
+ * @c01:    input channel-0 secondary clamp
+ * @c11:    input channel-1 secondary clamp
+ * @c21:    input channel-2 secondary clamp
+ * @m:      3x3 matrix, row-major [row][col] (12s)
+ * @o:      output offsets [0..2] (signed)
+ * @s:      output saturation clamps [0..2]
+ * @_pad:   reserved, must be zero
+ */
+struct camss_params_color_xform {
+	struct v4l2_isp_params_block_header header;
+	__u16 c0, c1, c2;
+	__u16 c01, c11, c21;
+	__s16 m[3][3];
+	__s16 o[3];
+	__s16 s[3];
+	__u16 _pad[2];
+} __attribute__((aligned(8)));
+
+#define CAMSS_GLUT_LUT_SIZE  64
+
+/**
+ * struct camss_params_glut - Per-channel gamma LUT
+ *
+ * Hardware availability: IFE, BPS. (IPE: future work. IPE has
+ * multiple gamma instances on modern SoCs; @instance will select
+ * which when IPE support lands. For IFE and BPS there is a single
+ * instance per engine and @instance is reserved.)
+ *
+ * Three independent 64-entry LUTs for R, G, B, flattened inline.
+ *
+ * Entries are packed as pairs in the hardware registers but
+ * exposed here as a flat array; the kernel repacks as needed.
+ * Hardware double-buffer bank selectors are kernel-managed.
+ *
+ * @header:   block header; @header.type = CAMSS_PARAMS_GLUT
+ * @instance: reserved (0 for IFE/BPS); used by IPE extension
+ * @_pad:     reserved, must be zero
+ * @r:        R channel gamma LUT
+ * @g:        G channel gamma LUT
+ * @b:        B channel gamma LUT
+ */
+struct camss_params_glut {
+	struct v4l2_isp_params_block_header header;
+	__u8  instance;
+	__u8  _pad[7];
+	__u16 r[CAMSS_GLUT_LUT_SIZE];
+	__u16 g[CAMSS_GLUT_LUT_SIZE];
+	__u16 b[CAMSS_GLUT_LUT_SIZE];
+} __attribute__((aligned(8)));
+
+#define CAMSS_GTM_LUT_SIZE  64
+
+/**
+ * struct camss_params_gtm - Global tone mapping
+ *
+ * Hardware availability: IFE, BPS. (IPE: future work.)
+ *
+ * Piecewise-linear LUT on luminance ratios, 64 segments. Each
+ * segment is described by a base value and a slope.
+ *
+ * The y_ratio_base values are packed as sign-extended 18-bit in
+ * the hardware register. 32 bits here preserves the full dynamic
+ * range; the kernel packs.
+ *
+ * @header:        block header; @header.type = CAMSS_PARAMS_GTM
+ * @y_ratio_base:  base value at start of each segment (unsigned)
+ * @y_ratio_slope: slope within each segment (signed)
+ */
+struct camss_params_gtm {
+	struct v4l2_isp_params_block_header header;
+	__u32 y_ratio_base[CAMSS_GTM_LUT_SIZE];
+	__s32 y_ratio_slope[CAMSS_GTM_LUT_SIZE];
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_uvg - UV gain
+ *
+ * Hardware availability: IFE, BPS. (IPE: future work.)
+ *
+ * Simple per-channel gain on the U and V chroma components after
+ * colour conversion. Used for chroma saturation control.
+ *
+ * @header:  block header; @header.type = CAMSS_PARAMS_UVG
+ * @u_gain:  U channel gain (10uQ8; 1.0 = no change)
+ * @v_gain:  V channel gain (10uQ8)
+ * @_pad:    reserved, must be zero
+ */
+struct camss_params_uvg {
+	struct v4l2_isp_params_block_header header;
+	__u16 u_gain;
+	__u16 v_gain;
+	__u16 _pad[2];
+} __attribute__((aligned(8)));
+
+/* ===================================================================
+ * Local Tone Mapping (LTM) — split into four sub-blocks
+ *
+ * Hardware availability: IFE, BPS. (IPE: future work — IPE has a
+ * YUV-domain LTM instance using the same tuning parameters; the
+ * structs defined here apply unchanged when IPE support lands.)
+ *
+ * LTM is a heavyweight block combining seven 1D LUTs (weight,
+ * la_curve, mask_rect_curve, ltm_curve, ltm_scale, lce_scale_pos,
+ * lce_scale_neg, igamma64), a 5x5 mask filter kernel, a scalar
+ * control section (data-collection counters, downscaling
+ * parameters, image-processing offsets), and two large across-
+ * frame state buffers (LUT25b, avg3d).
+ *
+ * The cross-frame state buffers (LUT25b, avg3d) are hardware ping-
+ * pong buffers managed by the kernel between frames and are NOT
+ * exposed to userspace.
+ *
+ * The remainder is split into four sub-blocks by expected update
+ * cadence:
+ *
+ *   CONFIG       — scalar control, data collection, image processing
+ *                  (may change per frame for adaptive behaviour)
+ *   CURVES       — six tone-mapping LUTs (la, mask_rect, ltm_curve,
+ *                  ltm_scale, lce_scale_pos, lce_scale_neg); change
+ *                  on tone-preset switch
+ *   GAMMA        — inverse-gamma 64-LUT; typically loaded once per
+ *                  session, occasionally on tone-preset change
+ *   MASK_FILTER  — 5x5 mask kernel; rarely touched after tuning
+ *
+ * Splitting lets userspace re-upload only the pieces that actually
+ * changed; a per-frame adaptive update typically sends only the
+ * CONFIG block while the LUT blocks stay resident in the
+ * hardware.
+ *
+ * Cross-sub-block consistency is the caller's responsibility:
+ * blocks apply on top of previous state, so an update to CURVES
+ * without a corresponding CONFIG update will use the existing
+ * scalar control values.
+ * =================================================================== */
+
+#define CAMSS_LTM_WEIGHT_LUT_SIZE    12
+#define CAMSS_LTM_CURVE_LUT_SIZE     65
+#define CAMSS_LTM_SCALE_LUT_SIZE     65
+#define CAMSS_LTM_LCE_SCALE_LUT_SIZE 17
+#define CAMSS_LTM_GAMMA_LUT_SIZE     64
+#define CAMSS_LTM_MASK_FILTER_SIZE    6
+
+/**
+ * struct camss_params_ltm_config - LTM control, downscale, IP setup
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * Scalar control fields for LTM. Combines three logical groups
+ * (data collection, downscaling, image processing) that share an
+ * update lifetime — they typically all change together when LTM
+ * is reconfigured for a new pipeline geometry or tone preset.
+ *
+ * Fields prefixed @dc_ configure the data-collection stage (3D
+ * bilateral table accumulation), @ds_ configures the downscaler
+ * that feeds it, and @ip_ configures the image-processing stage
+ * that consumes the resulting 3D LUT.
+ *
+ * @header:                  block header; @header.type = CAMSS_PARAMS_LTM_CONFIG
+ *
+ * Top-level enables:
+ * @data_collect_en:         enable data-collection stage
+ * @img_process_en:          enable image-processing stage
+ * @igamma_en:               enable inverse-gamma stage
+ * @la_en:                   enable la_curve application
+ *
+ * Data-collection stage (post-cropping cell layout):
+ * @dc_3dtable_avg_pong_sel: ping-pong selector for 3D avg table
+ * @dc_init_cellnum_x:       starting cell x index (4u)
+ * @dc_init_dx:              initial dx (5u)
+ * @dc_3d_sum_clear:         clear 3D sum accumulator
+ * @bin_init_cnt:            initial bin count (4u)
+ * @dc_conv_start_cell_x:    convolution start cell x (4u)
+ * @dc_conv_end_cell_x:      convolution end cell x (4u)
+ * @_pad0:                   reserved, must be zero
+ * @dc_xstart:               post-cropping x start (8u)
+ * @dc_xend:                 post-cropping x end (8u)
+ *
+ * Downscaler:
+ * @ds_horizontal_skip_cnt:  horizontal skip count, pre-cropping (10u)
+ * @ds_vertical_skip_cnt:    vertical skip count (14u)
+ * @ds_fac:                  downscale factor (2u):
+ *                             0 = 288x216 ds size, cell w/h 24
+ *                             1 = 144x108 ds size, cell w/h 12
+ *                             2 =  72x54  ds size, cell w/h  6
+ * @_pad1:                   reserved, must be zero
+ *
+ * Image-processing stage:
+ * @ip_init_cellnum_x:       starting cell x index (4u)
+ * @ip_init_cellnum_y:       starting cell y index (4u)
+ * @ip_3dtable_avg_pong_sel: ping-pong selector for IP-side 3D avg
+ * @_pad2:                   reserved, must be zero
+ * @ip_init_dx:              initial dx (11u)
+ * @ip_init_dy:              initial dy (11u)
+ * @ip_init_px:              initial px (18u)
+ * @ip_init_py:              initial py (18u)
+ * @ip_inv_cellwidth:        inverse cell width (14u)
+ * @ip_inv_cellheight:       inverse cell height (14u)
+ * @ip_cellwidth:            cell width (11u)
+ * @ip_cellheight:           cell height (11u)
+ *
+ * Mixing coefficients (10/11-bit each):
+ * @c1: coefficient 1 (10u)
+ * @c2: coefficient 2 (10u)
+ * @c3: coefficient 3 (10u)
+ * @c4: coefficient 4 (11u)
+ *
+ * Weight LUT (bundled with config since it is tightly coupled):
+ * @weight_lut: 12-entry weight LUT
+ *
+ * Misc:
+ * @lce_thd:       LCE (local contrast enhancement) threshold
+ * @y_ratio_max:   maximum Y ratio (10u)
+ * @debug_out_sel: hardware debug-out selector (2u)
+ * @_pad3:         reserved, must be zero
+ */
+struct camss_params_ltm_config {
+	struct v4l2_isp_params_block_header header;
+
+	__u8  data_collect_en;
+	__u8  img_process_en;
+	__u8  igamma_en;
+	__u8  la_en;
+
+	__u8  dc_3dtable_avg_pong_sel;
+	__u8  dc_init_cellnum_x;
+	__u8  dc_init_dx;
+	__u8  dc_3d_sum_clear;
+	__u8  bin_init_cnt;
+	__u8  dc_conv_start_cell_x;
+	__u8  dc_conv_end_cell_x;
+	__u8  _pad0;
+	__u16 dc_xstart;
+	__u16 dc_xend;
+
+	__u16 ds_horizontal_skip_cnt;
+	__u16 ds_vertical_skip_cnt;
+	__u8  ds_fac;
+	__u8  _pad1[3];
+
+	__u8  ip_init_cellnum_x;
+	__u8  ip_init_cellnum_y;
+	__u8  ip_3dtable_avg_pong_sel;
+	__u8  _pad2;
+	__u16 ip_init_dx;
+	__u16 ip_init_dy;
+	__u32 ip_init_px;
+	__u32 ip_init_py;
+	__u16 ip_inv_cellwidth;
+	__u16 ip_inv_cellheight;
+	__u16 ip_cellwidth;
+	__u16 ip_cellheight;
+
+	__u16 c1;
+	__u16 c2;
+	__u16 c3;
+	__u16 c4;
+
+	__u16 weight_lut[CAMSS_LTM_WEIGHT_LUT_SIZE];
+
+	__u16 lce_thd;
+	__u16 y_ratio_max;
+	__u8  debug_out_sel;
+	__u8  _pad3[3];
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_ltm_curves - LTM tone-mapping curve LUTs
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * Six 1D tone / scale LUTs:
+ *   - la_curve:        local-adaptation curve (post-igamma)
+ *   - mask_rect_curve: mask-shaping curve
+ *   - ltm_curve:       core LTM transfer curve
+ *   - ltm_scale:       scale modulation
+ *   - lce_scale_pos:   LCE positive-direction scale
+ *   - lce_scale_neg:   LCE negative-direction scale
+ *
+ * The la_curve and mask_rect_curve are 65-entry, the LCE scales
+ * are 17-entry, matching the hardware LUT sizes.
+ *
+ * Per hardware spec: la_curve and mask_rect_curve are unsigned
+ * (10+e)u; ltm_curve, ltm_scale, lce_scale_pos, lce_scale_neg are
+ * signed (10+e)s.
+ *
+ * @header:        block header; @header.type = CAMSS_PARAMS_LTM_CURVES
+ * @la_curve:      local-adaptation curve (10+e)u
+ * @mask_rect_curve: mask-shaping curve (10+e)u
+ * @ltm_curve:     LTM transfer curve (10+e)s
+ * @ltm_scale:     scale modulation (10+e)s
+ * @lce_scale_pos: LCE positive scale (10+e)s
+ * @lce_scale_neg: LCE negative scale (10+e)s
+ */
+struct camss_params_ltm_curves {
+	struct v4l2_isp_params_block_header header;
+	__u16 la_curve[CAMSS_LTM_CURVE_LUT_SIZE];
+	__u16 mask_rect_curve[CAMSS_LTM_CURVE_LUT_SIZE];
+	__s16 ltm_curve[CAMSS_LTM_CURVE_LUT_SIZE];
+	__s16 ltm_scale[CAMSS_LTM_SCALE_LUT_SIZE];
+	__s16 lce_scale_pos[CAMSS_LTM_LCE_SCALE_LUT_SIZE];
+	__s16 lce_scale_neg[CAMSS_LTM_LCE_SCALE_LUT_SIZE];
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_ltm_gamma - LTM inverse-gamma LUT
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * 64-entry inverse gamma applied before LTM processing. Typically
+ * loaded once per session or on tone-preset change. Separate from
+ * the main gamma block (CAMSS_PARAMS_GLUT) because that drives the
+ * global post-LTM gamma stage.
+ *
+ * @header: block header; @header.type = CAMSS_PARAMS_LTM_GAMMA
+ * @lut:    inverse gamma LUT, 64 entries (10+e)s
+ */
+struct camss_params_ltm_gamma {
+	struct v4l2_isp_params_block_header header;
+	__s16 lut[CAMSS_LTM_GAMMA_LUT_SIZE];
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_ltm_mask_filter - LTM 5x5 mask filter kernel
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * The 5x5 mask filter kernel is symmetric and stored as 6 unique
+ * coefficients.
+ *
+ * @header: block header; @header.type = CAMSS_PARAMS_LTM_MASK_FILTER
+ * @kernel: 6 unique 5x5 symmetric kernel coefficients (4u)
+ * @scale:  output scale factor (12u)
+ * @shift:  output shift (5u)
+ * @enable: 1 = apply mask filter
+ * @_pad:   reserved, must be zero
+ */
+struct camss_params_ltm_mask_filter {
+	struct v4l2_isp_params_block_header header;
+	__u16 kernel[CAMSS_LTM_MASK_FILTER_SIZE];
+	__u16 scale;
+	__u16 shift;
+	__u8  enable;
+	__u8  _pad[3];
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_params_roi - Rectangle-of-interest coordinates
+ *
+ * @left:   x coordinate of the ROI
+ * @top:    y coordinate of the ROI
+ * @width:  width of the ROI
+ * @height: height of the ROI
+ */
+struct camss_params_roi {
+	__u32 left;
+	__u32 top;
+	__u32 width;
+	__u32 height;
+} __attribute__((packed));
+
+/**
+ * enum camss_stats_bg_usage - Which BG/BE consumer this block targets
+ */
+enum camss_stats_bg_usage {
+	CAMSS_STATS_BG_AWB      = 0, /* AWB Bayer Grid */
+	CAMSS_STATS_BG_HDR_BE   = 1, /* HDR Bayer Exposure */
+	CAMSS_STATS_BG_TINTLESS = 2, /* Tintless Bayer Grid */
+};
+
+/**
+ * enum camss_stats_bg_output_mode - BG/BE stats output mode
+ */
+enum camss_stats_bg_output_mode {
+	CAMSS_STATS_BG_MODE_REGULAR    = 0, /* regular */
+	CAMSS_STATS_BG_MODE_SATURATION = 1, /* saturation enabled */
+	CAMSS_STATS_BG_MODE_Y_STATS    = 2, /* Y-stats enabled */
+};
+
+/**
+ * enum camss_stats_bg_green_type - Which green pixels to use for Y-stats
+ */
+enum camss_stats_bg_green_type {
+	CAMSS_STATS_BG_GREEN_GR      = 0, /* use Gr */
+	CAMSS_STATS_BG_GREEN_GB      = 1, /* use Gb */
+	CAMSS_STATS_BG_GREEN_AVERAGE = 2, /* use (Gr + Gb) / 2 */
+};
+
+/**
+ * struct camss_stats_bg - Bayer Grid / Bayer Exposure stats
+ *
+ * Hardware availability: IFE (AWB-BG, HDR-BE, Tintless-BG),
+ * BPS (BG).
+ *
+ * Drives the gridded Bayer accumulator hardware primitive, which
+ * is instantiated up to four times across IFE and BPS for
+ * different consumers. The @usage field selects which.
+ *
+ * The hardware divides the @roi rectangle into @horizontal_num by
+ * @vertical_num regions and accumulates per-channel statistics
+ * over each region. @channel_gain_threshold caps per-pixel
+ * contribution before accumulation. Channel indexing is Bayer
+ * order: 0 = R, 1 = B, 2 = Gr, 3 = Gb.
+ *
+ * @header:                 block header; @header.type = CAMSS_PARAMS_STATS_BG
+ * @usage:                  see &enum camss_stats_bg_usage
+ * @output_mode:            see &enum camss_stats_bg_output_mode
+ * @green_type:             see &enum camss_stats_bg_green_type
+ * @_pad0:                  reserved, must be zero
+ * @horizontal_num:         number of horizontal regions
+ * @vertical_num:           number of vertical regions
+ * @roi:                    active region on the sensor
+ * @channel_gain_threshold: max gain threshold for [R, B, Gr, Gb]
+ * @output_bit_depth:       requested stats bit depth (0 = hw default)
+ * @region_width:           width of each subgrid region in pixels
+ * @region_height:          height of each subgrid region in pixels
+ * @y_stats_weights_q8:     R/G/B weights for Y-stats mode (Q8)
+ */
+struct camss_stats_bg {
+	struct v4l2_isp_params_block_header header;
+	__u8  usage;
+	__u8  output_mode;
+	__u8  green_type;
+	__u8  _pad0;
+	__s32 horizontal_num;
+	__s32 vertical_num;
+	struct camss_params_roi roi;
+	__u32 channel_gain_threshold[4];
+	__u32 output_bit_depth;
+	__u32 region_width;
+	__u32 region_height;
+	__u32 y_stats_weights_q8[3];
+} __attribute__((aligned(8)));
+
+/**
+ * enum camss_stats_color_channel - Colour channel selector
+ */
+enum camss_stats_color_channel {
+	CAMSS_STATS_CHANNEL_INVALID = 0,
+	CAMSS_STATS_CHANNEL_R       = 1,
+	CAMSS_STATS_CHANNEL_GR      = 2,
+	CAMSS_STATS_CHANNEL_GB      = 3,
+	CAMSS_STATS_CHANNEL_B       = 4,
+	CAMSS_STATS_CHANNEL_G       = 5,
+	CAMSS_STATS_CHANNEL_Y       = 6,
+};
+
+/**
+ * enum camss_stats_bhist_usage - Which consumer this BHist instance feeds
+ *
+ * The same hardware histogram primitive is instantiated multiple
+ * times on IFE for different consumers (one dedicated to AEC, one
+ * driving GTM tone-mapping decisions). The @usage field in &struct
+ * camss_stats_bhist routes to the correct hardware instance.
+ */
+enum camss_stats_bhist_usage {
+	CAMSS_STATS_BHIST_AEC = 0, /* AEC-dedicated histogram */
+	CAMSS_STATS_BHIST_GTM = 1, /* GTM-dedicated histogram */
+};
+
+/**
+ * struct camss_stats_bhist - Bayer histogram
+ *
+ * Hardware availability: IFE, BPS.
+ *
+ * On IFE the hardware instantiates this block multiple times for
+ * different consumers (AEC, GTM). The @usage field selects which.
+ * On BPS there is a single instance; @usage is reserved (0).
+ *
+ * @header:  block header; @header.type = CAMSS_PARAMS_STATS_BHIST
+ * @roi:     ROI rectangle to collect statistics from
+ * @channel: channel selector (see &enum camss_stats_color_channel)
+ * @uniform: 1 = uniform histogram binning
+ * @usage:   consumer selector on IFE; see &enum camss_stats_bhist_usage
+ * @_pad:    reserved, must be zero
+ */
+struct camss_stats_bhist {
+	struct v4l2_isp_params_block_header header;
+	struct camss_params_roi roi;
+	__u8  channel;
+	__u8  uniform;
+	__u8  usage;
+	__u8  _pad[5];
+} __attribute__((aligned(8)));
+
+/**
+ * enum camss_stats_hdr_bhist_green - HDR-BHist green channel selection
+ */
+enum camss_stats_hdr_bhist_green {
+	CAMSS_STATS_HDR_BHIST_GR = 0,
+	CAMSS_STATS_HDR_BHIST_GB = 1,
+};
+
+/**
+ * enum camss_stats_hdr_bhist_field - HDR-BHist input field selection
+ *
+ * Selects which exposure the stats are collected from in HDR
+ * staggered modes.
+ */
+enum camss_stats_hdr_bhist_field {
+	CAMSS_STATS_HDR_BHIST_ALL   = 0, /* all lines, non-HDR */
+	CAMSS_STATS_HDR_BHIST_LONG  = 1, /* HDR T1 (long exposure) */
+	CAMSS_STATS_HDR_BHIST_SHORT = 2, /* HDR T2 (short exposure) */
+};
+
+/**
+ * struct camss_stats_hdr_bhist - HDR Bayer histogram
+ *
+ * Hardware availability: IFE (AEC variant), BPS.
+ *
+ * @header:        block header; @header.type = CAMSS_PARAMS_STATS_HDR_BHIST
+ * @roi:           ROI rectangle
+ * @green_channel: green channel selector
+ *                 (see &enum camss_stats_hdr_bhist_green)
+ * @field_select:  exposure field selector
+ *                 (see &enum camss_stats_hdr_bhist_field)
+ * @_pad:          reserved, must be zero
+ */
+struct camss_stats_hdr_bhist {
+	struct v4l2_isp_params_block_header header;
+	struct camss_params_roi roi;
+	__u8  green_channel;
+	__u8  field_select;
+	__u8  _pad[6];
+} __attribute__((aligned(8)));
+
+/**
+ * enum camss_stats_ihist_channel - Image histogram channel selection
+ *
+ * IHist operates after the YCbCr colour conversion, so the
+ * channels are YCC not Bayer.
+ */
+enum camss_stats_ihist_channel {
+	CAMSS_STATS_IHIST_Y  = 0,
+	CAMSS_STATS_IHIST_CB = 1,
+	CAMSS_STATS_IHIST_CR = 2,
+};
+
+/**
+ * struct camss_stats_ihist - Image histogram
+ *
+ * Hardware availability: IFE only.
+ *
+ * @header:                block header; @header.type = CAMSS_PARAMS_STATS_IHIST
+ * @roi:                   ROI rectangle
+ * @channel:               YCC channel selector
+ * @_pad:                  reserved, must be zero
+ * @max_pixel_sum_per_bin: histogram output bit-shift control.
+ *                         When image data concentrates in few bins
+ *                         the output may saturate; this field
+ *                         tells the hardware how many bits to
+ *                         shift so pixel sums fit in the output
+ *                         bin width. 0 = use hardware default.
+ */
+struct camss_stats_ihist {
+	struct v4l2_isp_params_block_header header;
+	struct camss_params_roi roi;
+	__u8  channel;
+	__u8  _pad[3];
+	__u32 max_pixel_sum_per_bin;
+} __attribute__((aligned(8)));
+
+/**
+ * struct camss_stats_rs - Row Sum stats
+ *
+ * Hardware availability: IFE only.
+ *
+ * @header:          block header; @header.type = CAMSS_PARAMS_STATS_RS
+ * @stats_h_num:     horizontal region count
+ * @stats_v_num:     vertical region count
+ * @rscs_color_conv: 1 = enable RGB->Y conversion on input pixels
+ * @_pad:            reserved, must be zero
+ */
+struct camss_stats_rs {
+	struct v4l2_isp_params_block_header header;
+	__u32 stats_h_num;
+	__u32 stats_v_num;
+	__u8  rscs_color_conv;
+	__u8  _pad[7];
+} __attribute__((aligned(8)));
+
+/* ------------------------------------------------------------------
+ * Bayer Auto-Focus (BAF) stats — the large one
+ *
+ * BAF configuration splits into sub-sections (input channel
+ * select, gamma LUT, scaler, FIR filter, IIR filter, ROI layout,
+ * coring, filter-chain composition). For UAPI we flatten this
+ * into a single block with the sub-structures inline and caps on
+ * variable-length arrays.
+ * ------------------------------------------------------------------ */
+
+#define CAMSS_BAF_MAX_GAMMA_ENTRIES      32
+#define CAMSS_BAF_MAX_FIR_COEFFICIENTS   13
+#define CAMSS_BAF_MAX_CORING_ENTRIES     17
+#define CAMSS_BAF_FILTER_TYPE_COUNT       3
+#define CAMSS_BAF_MAX_ROI_REGIONS       180
+#define CAMSS_BAF_Y_CONFIG_COUNT          3
+
+/**
+ * enum camss_baf_channel_select - BAF primary channel selection
+ *
+ * Picks whether BAF operates on green or luma.
+ */
+enum camss_baf_channel_select {
+	CAMSS_BAF_CHANNEL_G = 0,
+	CAMSS_BAF_CHANNEL_Y = 1,
+};
+
+/**
+ * enum camss_baf_input_g_select - BAF Green input channel
+ *
+ * When channel select is G, picks which Bayer green to use.
+ */
+enum camss_baf_input_g_select {
+	CAMSS_BAF_INPUT_GR = 0,
+	CAMSS_BAF_INPUT_GB = 1,
+};
+
+/**
+ * enum camss_baf_stats_region_type - BAF ROI region classification
+ */
+enum camss_baf_stats_region_type {
+	CAMSS_BAF_REGION_PRIMARY   = 0,
+	CAMSS_BAF_REGION_SECONDARY = 1,
+};
+
+/**
+ * enum camss_baf_stats_roi_type - BAF ROI mode
+ */
+enum camss_baf_stats_roi_type {
+	CAMSS_BAF_ROI_DEFAULT = 0, /* hardware default grid */
+	CAMSS_BAF_ROI_CUSTOM  = 1, /* user-provided ROI list */
+};
+
+/**
+ * struct camss_baf_roi - One BAF statistics ROI
+ *
+ * @region:      see &enum camss_baf_stats_region_type
+ * @_pad:        reserved, must be zero
+ * @roi:         ROI coordinates on the sensor
+ * @region_num:  index within the region's group
+ * @output_id:   output ordering ID
+ * @need_merge:  1 if region spans a dual-IFE split and must be merged
+ * @_pad2:       reserved, must be zero
+ */
+struct camss_baf_roi {
+	__u8  region;
+	__u8  _pad[3];
+	struct camss_params_roi roi;
+	__u32 region_num;
+	__u32 output_id;
+	__u8  need_merge;
+	__u8  _pad2[7];
+} __attribute__((packed));
+
+/**
+ * struct camss_baf_fir_filter - BAF FIR filter configuration
+ *
+ * @enable:       1 = FIR filter active
+ * @_pad:         reserved, must be zero
+ * @num_coeffs:   how many leading entries of @coefficients are valid
+ * @coefficients: FIR filter taps
+ */
+struct camss_baf_fir_filter {
+	__u8  enable;
+	__u8  _pad[3];
+	__u32 num_coeffs;
+	__s32 coefficients[CAMSS_BAF_MAX_FIR_COEFFICIENTS];
+} __attribute__((packed));
+
+/**
+ * struct camss_baf_iir_filter - BAF IIR filter configuration
+ *
+ * Second-order sections with floating-point coefficients. The
+ * hardware's IIR stage natively consumes float, so we keep float
+ * in UAPI.
+ *
+ * @enable: 1 = IIR filter active
+ * @_pad:   reserved, must be zero
+ * @b10..a22: biquad coefficients
+ */
+struct camss_baf_iir_filter {
+	__u8  enable;
+	__u8  _pad[7];
+	float b10, b11, b12, a11, a12;
+	float b20, b21, b22, a21, a22;
+} __attribute__((packed));
+
+/**
+ * struct camss_baf_filter_coring - BAF coring configuration
+ *
+ * @threshold:   filter threshold
+ * @gain:        filter gain
+ * @num_entries: number of valid entries in @core
+ * @_pad:        reserved, must be zero
+ * @core:        coring LUT
+ */
+struct camss_baf_filter_coring {
+	__s32 threshold;
+	__u32 gain;
+	__u32 num_entries;
+	__u32 _pad;
+	__u32 core[CAMSS_BAF_MAX_CORING_ENTRIES];
+} __attribute__((packed));
+
+/**
+ * struct camss_baf_filter - One BAF filter
+ *
+ * Indexed in the outer block by filter type:
+ *   0 = horizontal 1
+ *   1 = horizontal 2
+ *   2 = vertical
+ *
+ * @is_valid:                1 = this filter slot carries valid config
+ * @horizontal_scale_enable: 1 = enable horizontal scaling
+ * @_pad:                    reserved, must be zero
+ * @shift_bits:              post-filter bit-shift
+ * @fir:                     FIR stage
+ * @iir:                     IIR stage
+ * @coring:                  coring stage
+ */
+struct camss_baf_filter {
+	__u8  is_valid;
+	__u8  horizontal_scale_enable;
+	__u8  _pad[2];
+	__s32 shift_bits;
+	struct camss_baf_fir_filter fir;
+	struct camss_baf_iir_filter iir;
+	struct camss_baf_filter_coring coring;
+} __attribute__((packed));
+
+/**
+ * struct camss_stats_baf - Bayer Auto-Focus stats
+ *
+ * Hardware availability: IFE only.
+ *
+ * BAF is the most complex stats block. The hardware can be
+ * programmed with up to 180 ROIs, three filter chains (2
+ * horizontal + 1 vertical) each with independent FIR/IIR/coring
+ * config, a 32-entry gamma LUT, and a scaling stage.
+ *
+ * Most fields carry an @is_valid / @enable companion to allow
+ * partial configuration: userspace can leave sections disabled
+ * and the kernel will skip them when composing the CDM program.
+ *
+ * @header:                   block header; @header.type = CAMSS_PARAMS_STATS_BAF
+ *
+ * Input channel configuration:
+ * @input_is_valid:           1 = input config section valid
+ * @input_channel_select:     G vs Y (see &enum camss_baf_channel_select)
+ * @input_g_select:           Gr vs Gb (see &enum camss_baf_input_g_select)
+ * @_pad0:                    reserved, must be zero
+ * @y_config_q8:              Y-channel mix weights (Q8)
+ *
+ * Gamma LUT:
+ * @gamma_is_valid:           1 = gamma LUT section valid
+ * @_pad1:                    reserved, must be zero
+ * @num_gamma_entries:        valid leading entries of @gamma_lut
+ * @gamma_lut:                pre-filter gamma correction LUT
+ *
+ * Scaler:
+ * @scale_is_valid:           1 = scaler section valid
+ * @scale_enable:             1 = enable scaler
+ * @_pad2:                    reserved, must be zero
+ * @scale_m:                  M scalar
+ * @scale_n:                  N scalar
+ * @pixel_offset:             pixel offset
+ * @mn_init:                  MN init value
+ * @interpolation_resolution: scaler interpolation resolution
+ * @phase_init:               phase init
+ * @phase_step:               phase step
+ * @input_image_width:        input image width for scaler
+ *
+ * Filters:
+ * @filters:                  one entry per filter type (H1, H2, V)
+ *
+ * ROIs:
+ * @roi_type:                 default grid vs custom list
+ *                            (see &enum camss_baf_stats_roi_type)
+ * @_pad3:                    reserved, must be zero
+ * @num_rois:                 valid leading entries of @rois
+ * @last_primary_region:      index of last primary-type ROI
+ * @rois:                     ROI list
+ */
+struct camss_stats_baf {
+	struct v4l2_isp_params_block_header header;
+
+	/* Input channel config */
+	__u8  input_is_valid;
+	__u8  input_channel_select;
+	__u8  input_g_select;
+	__u8  _pad0;
+	__u32 y_config_q8[CAMSS_BAF_Y_CONFIG_COUNT];
+
+	/* Gamma LUT */
+	__u8  gamma_is_valid;
+	__u8  _pad1[3];
+	__u32 num_gamma_entries;
+	__u32 gamma_lut[CAMSS_BAF_MAX_GAMMA_ENTRIES];
+
+	/* Scaler */
+	__u8  scale_is_valid;
+	__u8  scale_enable;
+	__u8  _pad2[2];
+	__s32 scale_m;
+	__s32 scale_n;
+	__s32 pixel_offset;
+	__s32 mn_init;
+	__s32 interpolation_resolution;
+	__s32 phase_init;
+	__s32 phase_step;
+	__s32 input_image_width;
+
+	/* Three filter chains (H1, H2, V) */
+	struct camss_baf_filter filters[CAMSS_BAF_FILTER_TYPE_COUNT];
+
+	/* ROI list */
+	__u8  roi_type;
+	__u8  _pad3[3];
+	__u32 num_rois;
+	__u32 last_primary_region;
+	struct camss_baf_roi rois[CAMSS_BAF_MAX_ROI_REGIONS];
+} __attribute__((aligned(8)));
+
+/* ===================================================================
+ * Buffer sizing helpers
+ * =================================================================== */
+
+/**
+ * CAMSS_PARAMS_MAX_SIZE - Maximum total size of CAMSS IQ parameters
+ *
+ * Sum of every IQ parameter block's sizeof. The driver uses this
+ * to size the params buffer so userspace can set all IQ parameter
+ * blocks in a single frame if needed.
+ *
+ * Stats blocks are excluded from this sum:
+ *   - Stats engine configuration (&struct camss_stats_*) is sized
+ *     separately by &CAMSS_STATS_CONFIG_MAX_SIZE.
+ *   - Stats output buffers (&struct camss_stats_*_output) are
+ *     sized separately per stats engine; they are kernel ->
+ *     userspace buffers and do not travel in the params buffer.
+ *
+ * Where the same struct is used for multiple block types in
+ * &enum camss_params_block_type, it is counted once per usage so
+ * userspace can set every distinct block in a single frame.
+ */
+#define CAMSS_PARAMS_MAX_SIZE                                      \
+	(sizeof(struct camss_params_bls)                          + \
+	 sizeof(struct camss_params_linearisation)                + \
+	 sizeof(struct camss_params_demux)                        + \
+	 sizeof(struct camss_params_bincorrect)                   + \
+	 sizeof(struct camss_params_abf_filter)                   + \
+	 sizeof(struct camss_params_abf_noise_lut)                + \
+	 sizeof(struct camss_params_abf_activity_lut)             + \
+	 sizeof(struct camss_params_abf_dark_lut)                 + \
+	 sizeof(struct camss_params_abf_rnr)                      + \
+	 sizeof(struct camss_params_wb_gain)                      + \
+	 sizeof(struct camss_params_lsc)                          + \
+	 sizeof(struct camss_params_demosaic)                     + \
+	 sizeof(struct camss_params_color_correct)                + \
+	 sizeof(struct camss_params_color_xform)                  + \
+	 sizeof(struct camss_params_glut)                         + \
+	 sizeof(struct camss_params_gtm)                          + \
+	 sizeof(struct camss_params_uvg)                          + \
+	 sizeof(struct camss_params_ltm_config)                   + \
+	 sizeof(struct camss_params_ltm_curves)                   + \
+	 sizeof(struct camss_params_ltm_gamma)                    + \
+	 sizeof(struct camss_params_ltm_mask_filter))
+
+/**
+ * CAMSS_STATS_CONFIG_MAX_SIZE - Maximum total size of CAMSS stats config
+ *
+ * Sum of every stats engine configuration block's sizeof. The
+ * driver uses this to size the stats configuration buffer so
+ * userspace can configure all stats engines in a single frame if
+ * needed.
+ *
+ * Stats output buffers are NOT included; those are sized
+ * separately per stats engine based on the relevant
+ * &struct camss_stats_*_output.
+ */
+#define CAMSS_STATS_CONFIG_MAX_SIZE                                \
+	(sizeof(struct camss_stats_bg)                            + \
+	 sizeof(struct camss_stats_bhist)                         + \
+	 sizeof(struct camss_stats_hdr_bhist)                     + \
+	 sizeof(struct camss_stats_ihist)                         + \
+	 sizeof(struct camss_stats_rs)                            + \
+	 sizeof(struct camss_stats_baf))
+
+#endif /* _UAPI_LINUX_CAMSS_STATS_PARAMS_H */

-- 
2.54.0


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

* Re: [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE
  2026-07-03 15:51 ` [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE Bryan O'Donoghue
                     ` (6 preceding siblings ...)
  2026-07-03 15:51   ` [PATCH 7/7] media: uapi: qcom-camss-stats-params Bryan O'Donoghue
@ 2026-07-03 16:06   ` Bryan O'Donoghue
  7 siblings, 0 replies; 10+ messages in thread
From: Bryan O'Donoghue @ 2026-07-03 16:06 UTC (permalink / raw)
  To: Bryan O'Donoghue, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Robert Foss, Todor Tomov,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-arm-msm, devicetree, linux-kernel, linux-media

On 03/07/2026 16:51, Bryan O'Donoghue wrote:
> Speaking/listening to several people at the media summit in Nice this year
> got me to thinking v4l2 m2m might be a another solution for an offline
> engine like this one,

got me to thinking drm/accel might be another solution

proof read your stuff please.

---
bod

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

* Re: [PATCH 6/7] media: qcom: camss: Switch on ICP and BPS as make options
  2026-07-03 15:51   ` [PATCH 6/7] media: qcom: camss: Switch on ICP and BPS as make options Bryan O'Donoghue
@ 2026-07-03 17:09     ` Julian Braha
  0 siblings, 0 replies; 10+ messages in thread
From: Julian Braha @ 2026-07-03 17:09 UTC (permalink / raw)
  To: Bryan O'Donoghue, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Robert Foss, Todor Tomov,
	Vladimir Zapolskiy, Mauro Carvalho Chehab
  Cc: linux-arm-msm, devicetree, linux-kernel, linux-media,
	Bryan O'Donoghue

Hi Bryan,

On 7/3/26 16:51, Bryan O'Donoghue wrote:
> +	  be called camss-icp, camss-ipe, and camss-bps, respecitvely.

Small thing: typo in your kconfig help text.

- Julian Braha

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

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

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <w94ETgA980W0Endv5HbqmfhQ2P9OKvdDKCK0cG7r6NWTt9iDZNMZWm900gZoKHs8Pu3gj91rUQeZVH4M-WclDA==@protonmail.internalid>
2026-07-03 15:51 ` [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE Bryan O'Donoghue
2026-07-03 15:51   ` [PATCH 1/7] arm64: dts: qcom: x1e80100: Add ICP/BPS/IPE nodes Bryan O'Donoghue
2026-07-03 15:51   ` [PATCH 2/7] media: qcom: camss: Launch ICP from CAMSS Bryan O'Donoghue
2026-07-03 15:51   ` [PATCH 3/7] media: qcom: camss: qcom-icp: Add minimal ICP driver with HFI infrastrucutre Bryan O'Donoghue
2026-07-03 15:51   ` [PATCH 4/7] media: qcom: camss: qcom-icp: bps: Add initial v4l2 m2m BPS driver Bryan O'Donoghue
2026-07-03 15:51   ` [PATCH 5/7] media: qcom: camss: qcom-icp: ipe: Add initial v4l2 m2m IPE driver Bryan O'Donoghue
2026-07-03 15:51   ` [PATCH 6/7] media: qcom: camss: Switch on ICP and BPS as make options Bryan O'Donoghue
2026-07-03 17:09     ` Julian Braha
2026-07-03 15:51   ` [PATCH 7/7] media: uapi: qcom-camss-stats-params Bryan O'Donoghue
2026-07-03 16:06   ` [PATCH 0/7] media: qcom: camss: icp: Add HFI/ICP v4l m2m driver for x1e80100 BPS/IPE Bryan O'Donoghue

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